Some updates
parent
6df7b216ec
commit
3b3430db5e
30
files.php
30
files.php
|
@ -9,6 +9,24 @@
|
|||
"path" => "./",
|
||||
"local-path" => "./shared/html/"
|
||||
],
|
||||
|
||||
[ /* javascript loader */
|
||||
"type" => "js",
|
||||
"search-pattern" => "/.*\.js$/",
|
||||
"build-target" => "dev",
|
||||
|
||||
"path" => "loader/",
|
||||
"local-path" => "./shared/loader/"
|
||||
],
|
||||
[ /* javascript loader for releases */
|
||||
"type" => "js",
|
||||
"search-pattern" => "/.*loader_[\S]+.min.js$/",
|
||||
"build-target" => "rel",
|
||||
|
||||
"path" => "loader/",
|
||||
"local-path" => "./shared/generated/"
|
||||
],
|
||||
|
||||
[ /* shared javascript files (WebRTC adapter) */
|
||||
"type" => "js",
|
||||
"search-pattern" => "/.*\.js$/",
|
||||
|
@ -17,6 +35,7 @@
|
|||
"path" => "adapter/",
|
||||
"local-path" => "./shared/adapter/"
|
||||
],
|
||||
|
||||
[ /* shared javascript files (development mode only) */
|
||||
"type" => "js",
|
||||
"search-pattern" => "/.*\.js$/",
|
||||
|
@ -36,6 +55,7 @@
|
|||
"local-path" => "./shared/js/",
|
||||
"req-parm" => ["--mappings"]
|
||||
],
|
||||
|
||||
[ /* shared generated worker codec */
|
||||
"type" => "js",
|
||||
"search-pattern" => "/(WorkerPOW.js)$/",
|
||||
|
@ -231,16 +251,6 @@
|
|||
"path" => "js/",
|
||||
"local-path" => "./web/generated/"
|
||||
],
|
||||
[ /* Add the shared generated files. Exclude the shared file because we're including it already */
|
||||
"web-only" => true,
|
||||
"type" => "js",
|
||||
"search-pattern" => "/.*\.js$/",
|
||||
"search-exclude" => "/shared\.js(.map)?$/",
|
||||
"build-target" => "rel",
|
||||
|
||||
"path" => "js/",
|
||||
"local-path" => "./shared/generated/"
|
||||
],
|
||||
[ /* web css files */
|
||||
"web-only" => true,
|
||||
"type" => "css",
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
"ttsc": "ttsc",
|
||||
"csso": "csso",
|
||||
"rebuild-structure-web-dev": "php files.php generate web dev",
|
||||
"minify-web-rel-file": "minify web/generated/client.js --outFile web/generated/client.min.js --evaluate --removeDebugger --undefinedToVoid --mangle.keepClassName --deadcode.keepFnArgs"
|
||||
"minify-web-rel-file": "terser --compress --mangle --ecma 6 --keep_classnames --keep_fnames --output"
|
||||
},
|
||||
"author": "TeaSpeak (WolverinDEV)",
|
||||
"license": "ISC",
|
||||
|
@ -25,15 +25,15 @@
|
|||
"@types/node": "^12.7.2",
|
||||
"@types/sha256": "^0.2.0",
|
||||
"@types/websocket": "0.0.40",
|
||||
"babel-minify": "^0.5.1",
|
||||
"clean-css": "^4.2.1",
|
||||
"csso-cli": "^2.0.2",
|
||||
"gulp": "^4.0.2",
|
||||
"sass": "^1.22.10",
|
||||
"sha256": "^0.2.0",
|
||||
"terser": "^4.2.1",
|
||||
"ttypescript": "^1.5.7",
|
||||
"typescript": "^3.5.3",
|
||||
"wat2wasm": "^1.0.2",
|
||||
"clean-css": "^4.2.1"
|
||||
"wat2wasm": "^1.0.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -61,4 +61,7 @@ fi
|
|||
|
||||
#Last but not least the client imports
|
||||
generate_link shared/declarations/exports.d.ts web/declarations/imports_shared.d.ts
|
||||
generate_link shared/declarations/exports_loader_app.d.ts web/declarations/imports_shared_loader.d.ts
|
||||
|
||||
generate_link shared/declarations/exports.d.ts client/declarations/imports_shared.d.ts
|
||||
generate_link shared/declarations/exports_loader_app.d.ts client/declarations/imports_shared_loader.d.ts
|
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
cd $(dirname $0)
|
||||
#find css/static/ -name '*.css' -exec cat {} \; | npm run csso -- --output `pwd`/generated/static/base.css
|
||||
|
||||
#File order
|
||||
files=(
|
||||
"css/static/main-layout.css"
|
||||
"css/static/helptag.css"
|
||||
"css/static/scroll.css"
|
||||
"css/static/channel-tree.css"
|
||||
"css/static/ts/tab.css"
|
||||
"css/static/ts/chat.css"
|
||||
"css/static/ts/icons.css"
|
||||
"css/static/ts/icons_em.css"
|
||||
"css/static/ts/country.css"
|
||||
"css/static/general.css"
|
||||
"css/static/modal.css"
|
||||
"css/static/modals.css"
|
||||
"css/static/modal-about.css"
|
||||
"css/static/modal-avatar.css"
|
||||
"css/static/modal-icons.css"
|
||||
"css/static/modal-bookmarks.css"
|
||||
"css/static/modal-connect.css"
|
||||
"css/static/modal-channel.css"
|
||||
"css/static/modal-query.css"
|
||||
"css/static/modal-invite.css"
|
||||
"css/static/modal-playlist.css"
|
||||
"css/static/modal-banlist.css"
|
||||
"css/static/modal-bancreate.css"
|
||||
"css/static/modal-clientinfo.css"
|
||||
"css/static/modal-serverinfo.css"
|
||||
"css/static/modal-identity.css"
|
||||
"css/static/modal-settings.css"
|
||||
"css/static/modal-poke.css"
|
||||
"css/static/modal-server.css"
|
||||
"css/static/modal-keyselect.css"
|
||||
"css/static/modal-permissions.css"
|
||||
"css/static/modal-group-assignment.css"
|
||||
"css/static/music/info_plate.css"
|
||||
"css/static/frame/SelectInfo.css"
|
||||
"css/static/control_bar.css"
|
||||
"css/static/context_menu.css"
|
||||
"css/static/frame-chat.css"
|
||||
"css/static/connection_handlers.css"
|
||||
"css/static/server-log.css"
|
||||
"css/static/htmltags.css"
|
||||
"css/static/hostbanner.css"
|
||||
"css/static/menu-bar.css"
|
||||
)
|
||||
|
||||
target_file=`pwd`/../generated/static/base.css
|
||||
echo "/* Auto generated merged CSS file */" > ${target_file}
|
||||
for file in "${files[@]}"; do
|
||||
if [[ ${file} =~ css/* ]]; then
|
||||
file="./${file:4}"
|
||||
fi
|
||||
cat ${file} >> ${target_file}
|
||||
done
|
||||
|
||||
cat ${target_file} | npm run csso -- --output `pwd`/../generated/static/base.css
|
|
@ -36,6 +36,7 @@ $background: #222222;
|
|||
.loader .half.right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.loader .half.left {
|
||||
left: 0;
|
||||
}
|
||||
|
@ -298,3 +299,64 @@ $background: #222222;
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/* Automated loader timeout */
|
||||
$loader_timeout: 2.5s;
|
||||
.loader:not(.started) {
|
||||
&, & > .half, & > .bookshelf_wrapper {
|
||||
-moz-animation: _loader_hide 0s ease-in $loader_timeout forwards;
|
||||
-webkit-animation: _loader_hide 0s ease-in $loader_timeout forwards;
|
||||
-o-animation: _loader_hide 0s ease-in $loader_timeout forwards;
|
||||
animation: _loader_hide 0s ease-in $loader_timeout forwards;
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
}
|
||||
|
||||
.loader:not(.started) + #critical-load {
|
||||
display: block !important;
|
||||
opacity: 0;
|
||||
|
||||
-moz-animation: _loader_show 0s ease-in $loader_timeout forwards;
|
||||
-webkit-animation: _loader_show 0s ease-in $loader_timeout forwards;
|
||||
-o-animation: _loader_show 0s ease-in $loader_timeout forwards;
|
||||
animation: _loader_show 0s ease-in $loader_timeout forwards;
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
animation-fill-mode: forwards;
|
||||
|
||||
.error::before {
|
||||
content: 'Failed to startup app loader!';
|
||||
}
|
||||
|
||||
.detail::before {
|
||||
content: 'Lookup the console for more details';
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes _loader_hide {
|
||||
to {
|
||||
width: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes _loader_hide {
|
||||
to {
|
||||
width: 0;
|
||||
height: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes _loader_show {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes _loader_show {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
|
@ -51,11 +51,21 @@ $client_info_avatar_size: 10em;
|
|||
|
||||
&.right {
|
||||
text-align: right;
|
||||
|
||||
&.mode-client_info {
|
||||
max-width: calc(50% - #{$client_info_avatar_size / 2});
|
||||
margin-left: calc(#{$client_info_avatar_size / 2});
|
||||
}
|
||||
}
|
||||
|
||||
&.left {
|
||||
text-align: left;
|
||||
padding-right: 10px;
|
||||
|
||||
&.mode-client_info {
|
||||
max-width: calc(50% - #{$client_info_avatar_size / 2});
|
||||
margin-right: calc(#{$client_info_avatar_size} / 2);
|
||||
}
|
||||
}
|
||||
|
||||
.title, .value, .small-value {
|
||||
|
@ -167,11 +177,6 @@ $client_info_avatar_size: 10em;
|
|||
}
|
||||
@include transition(background-color $button_hover_animation_time ease-in-out);
|
||||
}
|
||||
|
||||
&.mode-client_info {
|
||||
min-width: 50%;
|
||||
margin-right: calc(#{$client_info_avatar_size} / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -507,6 +512,21 @@ $client_info_avatar_size: 10em;
|
|||
flex-direction: column;
|
||||
|
||||
min-height: 2em;
|
||||
|
||||
.container-typing {
|
||||
font-size: .85em;
|
||||
padding-left: .6em;
|
||||
line-height: 1;
|
||||
color: hsla(0, 0%, 30%, 1);
|
||||
|
||||
opacity: 1;
|
||||
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@include transition($button_hover_animation_time ease-in-out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1201,8 +1221,6 @@ $client_info_avatar_size: 10em;
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.button-close {
|
||||
font-size: 4em;
|
||||
|
||||
|
@ -1243,6 +1261,32 @@ $client_info_avatar_size: 10em;
|
|||
transform: rotate(-45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.button-more {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
height: 1.5em;
|
||||
font-size: 1.25em;
|
||||
|
||||
text-align: center;
|
||||
|
||||
color: #999999;
|
||||
cursor: pointer;
|
||||
|
||||
margin-left: -5px;
|
||||
margin-right: -5px;
|
||||
|
||||
background-color: #2d2d2d;
|
||||
|
||||
border-bottom-right-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
|
||||
&:hover {
|
||||
background-color: #393939;
|
||||
}
|
||||
@include transition($button_hover_animation_time ease-in-out);
|
||||
}
|
||||
}
|
||||
|
||||
.container-private-conversations, .container-channel-chat {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
@import "./mixin.scss";
|
||||
|
||||
#hostbanner {
|
||||
.hostbanner {
|
||||
.container-hostbanner {
|
||||
position: relative;
|
||||
|
||||
|
|
|
@ -0,0 +1,618 @@
|
|||
@import "mixin";
|
||||
@import "properties";
|
||||
|
||||
.modal-body.modal-client-info {
|
||||
padding: 0!important;
|
||||
|
||||
$avatar_size: 12em;
|
||||
.head {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
z-index: 1;
|
||||
|
||||
height: 7em;
|
||||
background-color: #212125;
|
||||
|
||||
.status-row {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
.status-entry {
|
||||
font-size: 1.5em;
|
||||
|
||||
margin: .25em;
|
||||
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.container-away-message {
|
||||
$offset_left: (.25em) * 1.5 /* 1.5 is the font size of the icons */;
|
||||
|
||||
position: relative;
|
||||
margin-left: $offset_left;
|
||||
margin-top: .25em;
|
||||
|
||||
background-color: #1c1c1c;
|
||||
border: 1px solid #161515;
|
||||
border-radius: 3px;
|
||||
|
||||
max-width: calc(50% - #{$avatar_size / 2 + $offset_left + 1em}); /* do actual 1em space to the avatar */
|
||||
max-height: 4em; /* else it will overflow the header */
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
|
||||
width: max-content;
|
||||
padding: .15em;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
//A verry long away message, because I want to tell a story. There was a child....
|
||||
a {
|
||||
font-size: .85em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
max-height: 200em;
|
||||
}
|
||||
|
||||
@include transition(.5s ease-in-out);
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
flex-shrink: 1;
|
||||
flex-grow: 0;
|
||||
|
||||
//TODO: Min height here!
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
background-color: #2f2f35;
|
||||
|
||||
.container-avatar {
|
||||
z-index: 2; /* overlay the header */
|
||||
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin: calc(#{$avatar_size} / -2) 0.75em 0.5em 0.5em;
|
||||
align-self: center;
|
||||
|
||||
.avatar {
|
||||
height: $avatar_size;
|
||||
width: $avatar_size;
|
||||
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.container-name {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
|
||||
.htmltag-client {
|
||||
text-align: center;
|
||||
font-size: 1.5em;
|
||||
color: #cccccc;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.container-description {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
padding-right: calc(10em / 2);
|
||||
padding-left: calc(10em / 2);
|
||||
|
||||
text-align: center;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
.client-description {
|
||||
color: #6f6f6f;
|
||||
max-width: 100%;
|
||||
flex-shrink: 1;
|
||||
flex-grow: 1;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.container-categories {
|
||||
margin-top: 1em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
min-height: 14em;
|
||||
|
||||
.categories {
|
||||
height: 2.5em;
|
||||
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
padding-left: 2.5em;
|
||||
padding-right: 2.5em;
|
||||
|
||||
border-bottom: 1px solid #1d1d1d;
|
||||
|
||||
.entry {
|
||||
padding: .5em;
|
||||
|
||||
text-align: center;
|
||||
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #b6c4d6;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-bottom: 3px solid #245184;
|
||||
margin-bottom: -2px;
|
||||
|
||||
color: #245184;
|
||||
}
|
||||
|
||||
@include transition(color $button_hover_animation_time, border-bottom-color $button_hover_animation_time);
|
||||
}
|
||||
}
|
||||
|
||||
.bodies {
|
||||
position: relative;
|
||||
|
||||
flex-shrink: 1;
|
||||
flex-grow: 1;
|
||||
|
||||
display: flex;
|
||||
justify-content: stretch;
|
||||
|
||||
padding-left: .5em;
|
||||
padding-right: .5em;
|
||||
|
||||
min-height: 10em;
|
||||
height: 21em; /* body size 20 + .5 padding */
|
||||
|
||||
.container-tooltip {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
font-size: .8em; /* shrink the tip a bit */
|
||||
|
||||
position: relative;
|
||||
width: 1.6em;
|
||||
margin-left: .5em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
|
||||
align-self: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
padding: .5em;
|
||||
|
||||
display: flex;
|
||||
justify-content: stretch;
|
||||
|
||||
overflow: auto; /* else the tooltip will trigger the scrollbar */
|
||||
@include chat-scrollbar-vertical();
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.container-basic {
|
||||
flex-direction: row;
|
||||
|
||||
.spacer {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.left, .right {
|
||||
height: 20em;
|
||||
width: calc(50% - .5em); /* the spacer in the middle thats why -.5 em */
|
||||
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
|
||||
border-radius: .2em;
|
||||
border: 1px solid #1f2122;
|
||||
background-color: #28292b;
|
||||
padding: .5em;
|
||||
|
||||
.property {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
.title, .value {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
> * {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
a {
|
||||
flex-shrink: 1;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #254d7b;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #bdbdbd;
|
||||
|
||||
a, a:visited {
|
||||
color: #bdbdbd!important;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 1.6em;
|
||||
height: 1.6em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
|
||||
cursor: pointer;
|
||||
opacity: .5;
|
||||
|
||||
> div {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@include transition($button_hover_animation_time ease-in-out);
|
||||
}
|
||||
|
||||
.country {
|
||||
margin-right: .25em;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:first-of-type) {
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
&.property-unique-id, &.property-ip {
|
||||
.value {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
&.property-version {
|
||||
.a-on {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
margin-left: .25em;
|
||||
margin-right: .25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.container-packets {
|
||||
flex-direction: row;
|
||||
|
||||
.spacer {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.left, .right {
|
||||
height: 20em;
|
||||
width: calc(50% - .5em); /* the spacer in the middle thats why -.5 em */
|
||||
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
|
||||
border-radius: .2em;
|
||||
border: 1px solid #1f2122;
|
||||
background-color: #28292b;
|
||||
padding: .5em;
|
||||
|
||||
.statistic {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
.title, .upstream, .downstream {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
> * {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
a {
|
||||
flex-shrink: 1;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #254d7b;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.upstream, .downstream {
|
||||
padding-top: .25em;
|
||||
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
> a {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.upstream {
|
||||
color: #fd3913;
|
||||
}
|
||||
|
||||
.downstream {
|
||||
color: #0e8afd;
|
||||
}
|
||||
|
||||
&:not(:first-of-type) {
|
||||
margin-top: .5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.container-groups {
|
||||
flex-direction: row;
|
||||
|
||||
.spacer {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.left, .right {
|
||||
height: 20em;
|
||||
width: calc(50% - .5em); /* the spacer in the middle thats why -.5 em */
|
||||
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
.title {
|
||||
align-self: center;
|
||||
color: #254d7b;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin-top: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
.container {
|
||||
border-radius: .2em .2em 0 0;
|
||||
border: 1px solid #1f2122;
|
||||
border-bottom: 0;
|
||||
|
||||
padding: 0!important;
|
||||
background-color: #28292b;
|
||||
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
min-height: 4em;
|
||||
position: relative;
|
||||
|
||||
@include chat-scrollbar-vertical();
|
||||
|
||||
.entries {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
min-height: 4em;
|
||||
|
||||
.entry {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
height: 1.6em;
|
||||
padding-left: .5em;
|
||||
padding-right: .5em;
|
||||
|
||||
&:hover {
|
||||
background-color: #232425;
|
||||
}
|
||||
|
||||
> * {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
margin-right: .25em;
|
||||
}
|
||||
|
||||
.name {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
min-width: 1em;
|
||||
line-height: normal;
|
||||
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.button-delete {
|
||||
height: 1.3em;
|
||||
width: 1.3em;
|
||||
|
||||
cursor: pointer;
|
||||
border-radius: .2em;
|
||||
|
||||
&:hover {
|
||||
background-color: #2c2d2e;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
|
||||
> div {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@include transition($button_hover_animation_time ease-in-out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-default-groups {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
a {
|
||||
align-self: center;
|
||||
font-size: 1.25em;
|
||||
color: hsla(0, 0%, 30%, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
border-radius: 0 0 .2em .2em;
|
||||
border: 1px solid #1f2122;
|
||||
background-color: #28292b;
|
||||
|
||||
padding: .5em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
|
||||
.button {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
.container {
|
||||
padding: 0!important;
|
||||
|
||||
select {
|
||||
font-size: .8em;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -259,7 +259,7 @@
|
|||
align-self: center;
|
||||
.country, .icon-container {
|
||||
align-self: center;
|
||||
margin-right: 0.1em;
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
@import "mixin";
|
||||
|
||||
.modal-body.modal-identity-improve {
|
||||
padding: 0!important;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
.container-tooltip {
|
||||
flex-grow: 0!important;
|
||||
flex-shrink: 0!important;
|
||||
min-width: unset!important;
|
||||
|
||||
position: relative;
|
||||
width: .8em;
|
||||
margin-right: .5em;
|
||||
font-size: .9em;
|
||||
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
vertical-align: middle;
|
||||
margin-bottom: .1em;
|
||||
|
||||
img {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
|
||||
align-self: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.options, .status {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
padding: 1em;
|
||||
|
||||
.title {
|
||||
color: #387fb5;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
div {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
min-width: 4em;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
border-top: 3px solid #20262d;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
margin: 0 1em 1em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
|
||||
button {
|
||||
min-width: 8em;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body.modal-identity-import {
|
||||
padding: 0!important;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
@include user-select(none);
|
||||
|
||||
.container-status {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
|
||||
margin: 1em 1em -1em;
|
||||
height: 1.6em;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
color: #389738;
|
||||
&.error {
|
||||
color: #973838;
|
||||
}
|
||||
|
||||
&.loading {
|
||||
color: #96903a;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@include transition(.2s ease-in-out);
|
||||
}
|
||||
|
||||
.container-text, .container-file {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
> label {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
width: 10em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
padding-left: .25em;
|
||||
align-self: center;
|
||||
|
||||
margin-right: 1em;
|
||||
|
||||
.radio-button {
|
||||
align-self: center;
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
> * {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.75em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
min-width: 8em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
padding: 1em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
|
||||
button {
|
||||
min-width: 8em;
|
||||
}
|
||||
}
|
||||
|
||||
.file-selector {
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
@import "mixin";
|
||||
|
||||
.modal-body.modal-server-info {
|
||||
padding: 0!important;
|
||||
width: 55em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
background-color: #2f2f35;
|
||||
|
||||
.container-tooltip {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
position: relative;
|
||||
width: 1.6em;
|
||||
margin-left: .5em;
|
||||
font-size: .9em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
|
||||
align-self: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.hostbanner {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
max-height: 9em;
|
||||
//width: 30em; /* set a default width where we have to grow/shrink */
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
.container-hostbanner {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
//background-color: #261f30;
|
||||
background-color: hsla(265, 10%, 15%, 1);
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.group {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
margin: 1em;
|
||||
padding: .5em;
|
||||
|
||||
border-radius: .2em;
|
||||
border: 1px solid #1f2122;
|
||||
|
||||
background-color: #28292b;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
height: 10em;
|
||||
max-height: 10em;
|
||||
|
||||
.container-image {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
max-width: 15em;
|
||||
max-height: 9em; /* minus one padding */
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
object-fit: contain;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
margin-right: 2em;
|
||||
@include transition(.25s ease-in-out);
|
||||
}
|
||||
|
||||
.container-properties {
|
||||
flex-shrink: 1;
|
||||
flex-grow: 1;
|
||||
|
||||
min-width: 25em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
height: inherit;
|
||||
|
||||
.row {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
height: 1.8em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
.key {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
color: #557edc;
|
||||
text-transform: uppercase;
|
||||
align-self: center;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #d6d6d7;
|
||||
align-self: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.country {
|
||||
display: inline-block;
|
||||
margin-right: .25em;
|
||||
}
|
||||
|
||||
&.server-version {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-network {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
|
||||
.container-button {
|
||||
margin-right: 1em;
|
||||
|
||||
flex-shrink: 1;
|
||||
min-width: 5em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
|
||||
button {
|
||||
height: 2.5em;
|
||||
width: 12em;
|
||||
|
||||
max-width: 100%;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.reverse {
|
||||
flex-direction: row-reverse;
|
||||
text-align: right;
|
||||
|
||||
.container-image {
|
||||
margin-right: 0;
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
.container-properties {
|
||||
.row {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-buttons {
|
||||
margin: 1em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
|
||||
button {
|
||||
min-width: 8em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 50em) {
|
||||
.modal-body.modal-server-info {
|
||||
.container-image {
|
||||
margin: 0!important;
|
||||
max-width: 0!important;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -119,6 +119,10 @@
|
|||
flex-shrink: 1;
|
||||
|
||||
color: #9d9d9e;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
h5 {
|
||||
|
@ -461,7 +465,7 @@
|
|||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .14), 0 3px 1px -2px rgba(0, 0, 0, .2), 0 1px 5px 0 rgba(0, 0, 0, .12);
|
||||
|
||||
&:hover {
|
||||
background-color: #151515;
|
||||
background-color: #0a0a0a;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
|
@ -473,21 +477,36 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.btn-success {
|
||||
&.btn-success, &.btn-green {
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #389738;
|
||||
}
|
||||
|
||||
&.btn-info {
|
||||
&.btn-info, &.btn-blue {
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #386896;
|
||||
}
|
||||
|
||||
&.btn-warning, &.btn-danger {
|
||||
&.btn-warning, &.btn-danger, &.btn-red {
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #973838;
|
||||
}
|
||||
|
||||
&.btn-purple {
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #5f3586;
|
||||
}
|
||||
|
||||
&.btn-brown {
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #965238;
|
||||
}
|
||||
|
||||
&.btn-yellow {
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #96903a;
|
||||
}
|
||||
|
||||
@include transition(background-color $button_hover_animation_time ease-in-out);
|
||||
}
|
||||
|
||||
|
@ -595,8 +614,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* general ratio button look */
|
||||
.ratio-button {
|
||||
/* general radio button look */
|
||||
.ratio-button, .radio-button {
|
||||
$button_size: 1.2em;
|
||||
$mark_size: .6em;
|
||||
|
||||
|
@ -647,14 +666,18 @@
|
|||
box-shadow: inset 0 0 4px 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
label:hover > .ratio-button, .ratio-button:hover {
|
||||
&.ratio-button, > .ratio-button {
|
||||
label:hover > .ratio-button, .ratio-button:hover,
|
||||
label:hover > .radio-button, .radio-button:hover{
|
||||
&.ratio-button, > .ratio-button,
|
||||
&.radio-button, > .radio-button{
|
||||
background-color: #2c2b2b;
|
||||
}
|
||||
}
|
||||
|
||||
label.disabled > .ratio-button, .ratio-button.disabled, .ratio-button:disabled {
|
||||
&.ratio-button, > .ratio-button {
|
||||
label.disabled > .ratio-button, .ratio-button.disabled, .ratio-button:disabled,
|
||||
label.disabled > .radio-button, .radio-button.disabled, .radio-button:disabled {
|
||||
&.ratio-button, > .ratio-button,
|
||||
&.radio-button, > .radio-button {
|
||||
pointer-events: none!important;
|
||||
background-color: #1a1919!important;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ cd "$BASEDIR"
|
|||
source ../scripts/resolve_commands.sh
|
||||
|
||||
#Generate the loader definitions first
|
||||
LOADER_FILE="declarations/exports_loader.d.ts"
|
||||
LOADER_FILE="declarations/exports_loader_app.d.ts"
|
||||
if [[ -e ${LOADER_FILE} ]]; then
|
||||
rm ${LOADER_FILE}
|
||||
if [[ $? -ne 0 ]]; then
|
||||
|
@ -13,10 +13,9 @@ if [[ -e ${LOADER_FILE} ]]; then
|
|||
fi
|
||||
fi
|
||||
|
||||
npm run dtsgen -- --config $(pwd)/tsconfig/dtsconfig_loader.json -v
|
||||
npm run dtsgen -- --config $(pwd)/tsconfig/dtsconfig_loader_app.json -v
|
||||
if [[ ! -e ${LOADER_FILE} ]]; then
|
||||
echo "Failed to generate definitions"
|
||||
echo "$result"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
@ -26,12 +25,19 @@ if [[ $? -ne 0 ]]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
execute_ttsc -p tsconfig/tsconfig_packed_loader.json
|
||||
execute_ttsc -p tsconfig/tsconfig_packed_loader_app.json
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Failed to generate packed loader file!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
npm run minify-web-rel-file `pwd`/generated/loader_app.min.js `pwd`/generated/loader_app.js
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Failed to minimize packed loader file!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
execute_ttsc -p tsconfig/tsconfig_packed.json
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Failed to generate packed file!"
|
||||
|
@ -49,7 +55,7 @@ if [[ ! -d generated/static/ ]]; then
|
|||
fi
|
||||
|
||||
# Create packed CSS file
|
||||
find css/static/ -name '*.css' -exec cat {} \; | npm run csso -- --output `pwd`/generated/static/base.css
|
||||
./css/generate_packed.sh
|
||||
|
||||
echo "Packed file generated!"
|
||||
exit 0
|
|
@ -93,7 +93,9 @@
|
|||
<link rel="stylesheet" href="css/loader/loader.css">
|
||||
</div>
|
||||
<div id="scripts">
|
||||
<script type="application/javascript" src="js/load.js" defer></script>
|
||||
<script type="application/javascript" src="loader/loader_app.min.js" defer></script>
|
||||
<script type="application/javascript" src="loader/loader_app.js" defer></script>
|
||||
<script type="application/javascript" src="loader/loader.js" defer></script>
|
||||
</div>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -113,7 +115,7 @@
|
|||
</script>
|
||||
|
||||
<!-- Loading screen -->
|
||||
<div class="loader">
|
||||
<div class="loader" id="loader-overlay">
|
||||
<div class="half right"></div>
|
||||
<div class="half left"></div>
|
||||
<div class="bookshelf_wrapper">
|
||||
|
@ -133,12 +135,12 @@
|
|||
<div class="fulloverlay" id="critical-load">
|
||||
<div class="container">
|
||||
<img src="img/loading_error_right.svg" height="192px">
|
||||
<h1 class="error" style="color: red">Ooops, we encountered some trouble while loading important files!</h1>
|
||||
<h1 class="error" style="color: red"></h1>
|
||||
<h3 class="detail"></h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if($localhost) { ?>
|
||||
<?php if($localhost && false) { ?>
|
||||
<div id="spoiler-style" style="z-index: 1000000; position: absolute; display: block; background: white; right: 5px; left: 5px; top: 34px;">
|
||||
<!-- <img src="https://www.chromatic-solutions.de/teaspeak/window/connect_opened.png"> -->
|
||||
<!-- <img src="http://puu.sh/DZDgO/9149c0a1aa.png"> -->
|
||||
|
@ -155,8 +157,29 @@
|
|||
<!-- <img src="http://puu.sh/E4lHJ/1a4afcdf0b.png"> -->
|
||||
<!-- <img src="http://puu.sh/E4HKK/5ee74d4cc7.png"> -->
|
||||
<!-- <img src="http://puu.sh/E6LN1/8518c10898.png"> -->
|
||||
<!--
|
||||
http://puu.sh/E8IoF/242ed5ca3a.png
|
||||
http://puu.sh/E8Ip9/9632d33591.png
|
||||
http://puu.sh/E8Ips/6c314253e5.png
|
||||
http://puu.sh/E8IpG/015a38b184.png
|
||||
http://puu.sh/E8IpY/5be454a15e.png
|
||||
|
||||
<img src="http://puu.sh/E6NXv/eb2f19c7c3.png">
|
||||
Identity imporve: http://puu.sh/E9jTp/380a734677.png
|
||||
Identity select: http://puu.sh/E9jYi/3003c58a2f.png
|
||||
|
||||
Server Info Bandwidth: http://puu.sh/E9jTe/b41f6386de.png
|
||||
Server Info: http://puu.sh/E9jT6/302912ae34.png
|
||||
|
||||
Bookmarks: http://puu.sh/Eb5w4/8d38fe5b8f.png
|
||||
|
||||
serveredit_1.png https://www.hypixel-koo.cf/tsapoijdsadpoijsadsapj.png
|
||||
serveredit_2.png https://www.hypixel-koo.cf/tsandljsandljsamndoj3oiwejlkjmnlksandljsadmnlmsadnlsa.png
|
||||
serveredit_3.png https://www.hypixel-koo.cf/toiuhsadouhgdsapoiugdsapouhdsapouhdsaouhwouhwwouhwwoiuhwoihwwoihwoijhwwoknw.png
|
||||
-->
|
||||
|
||||
<!-- <img src="http://puu.sh/E6NXv/eb2f19c7c3.png"> -->
|
||||
<!-- <img src="http://puu.sh/E9jT6/302912ae34.png"> -->
|
||||
<img src="http://puu.sh/E9jYi/3003c58a2f.png">
|
||||
</div>
|
||||
<button class="toggle-spoiler-style" style="height: 30px; width: 100px; z-index: 100000000; position: absolute; bottom: 2px;">toggle style</button>
|
||||
<script>
|
||||
|
|
|
@ -241,7 +241,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="lane">
|
||||
<div class="block left mode-based mode-channel_chat">
|
||||
<div class="block left mode-based mode-channel_chat channel">
|
||||
<div class="title"></div>
|
||||
<div class="value value-text-channel"></div>
|
||||
<div class="small-value value-text-limit"></div>
|
||||
|
@ -253,7 +253,7 @@
|
|||
<div class="spacer-client-info"></div>
|
||||
</div>
|
||||
|
||||
<div class="block right">
|
||||
<div class="block right mode-based mode-private_chat mode-channel_chat">
|
||||
<div class="title">{{tr "Private chats" /}}
|
||||
<div class="container-indicator">
|
||||
<div class="chat-unread-counter">-1</div>
|
||||
|
@ -261,6 +261,11 @@
|
|||
</div>
|
||||
<div class="value button chat-counter">Hmm... Something seems to be wrong!</div>
|
||||
</div>
|
||||
|
||||
<div class="block right mode-based mode-client_info">
|
||||
<div class="title"> </div>
|
||||
<div class="value button open-conversation">error: open conversation</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
|
@ -275,7 +280,10 @@
|
|||
<div class="conversation">
|
||||
<div class="spacer"></div>
|
||||
<div class="messages"></div>
|
||||
<div class="chatbox"></div>
|
||||
<div class="chatbox">
|
||||
<div class="container-typing">{{tr "Partner is typing..." /}}</div>
|
||||
<node key="chatbox"></node>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
@ -469,6 +477,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="button-close"></div>
|
||||
<div class="button-more">{{tr "Full Info" /}}</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
|
@ -2747,64 +2756,87 @@
|
|||
</script>
|
||||
|
||||
<script class="jsrender-template" id="tmpl_settings-teamspeak_import" type="text/html">
|
||||
<div class="container-teamspeak-import">
|
||||
<div class="error">{{tr "Failed to import identity (my little error)" /}}</div>
|
||||
<div class="load-data">
|
||||
<div>{{tr "Import an identity from TeamSpeak or an exported identity" /}}</div>
|
||||
<div class="buttons">
|
||||
<button class="button-load-text">{{tr "Load from text" /}}</button>
|
||||
<button class="button-load-file">{{tr "Load from file" /}}</button>
|
||||
<input class="input-file" type="file">
|
||||
<div>
|
||||
<div class="container-status"><a></a></div>
|
||||
<fieldset class="">
|
||||
<div class="container-text">
|
||||
<label>
|
||||
<div class="radio-button">
|
||||
<input type="radio" name="type" value="text"/>
|
||||
<div class="mark"></div>
|
||||
</div>
|
||||
<a>{{tr "Import from text" /}}</a>
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<label>{{tr "Identity Data" /}}</label>
|
||||
<input class="form-control input-identity-text" placeholder="{{tr 'Please paste your identity data here' /}}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="success">
|
||||
{{tr "Identity successfully loaded. Ready to import" /}}
|
||||
<div class="container-file">
|
||||
<label>
|
||||
<div class="radio-button">
|
||||
<input type="radio" name="type" value="file"/>
|
||||
<div class="mark"></div>
|
||||
</div>
|
||||
<a>{{tr "Import from file" /}}</a>
|
||||
</label>
|
||||
<button class="btn btn-brown button-load-file">{{tr "Select file" /}}</button>
|
||||
<a class="current-file">{{tr "No file selected" /}}</a>
|
||||
</div>
|
||||
</fieldset>
|
||||
<input type="file" class="file-selector"/>
|
||||
<div class="footer">
|
||||
<button class="button-import">{{tr "Import" /}}</button>
|
||||
<button class="btn btn-success button-import">{{tr "Import" /}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script class="jsrender-template" id="tmpl_settings-teamspeak_improve" type="text/html">
|
||||
<div class="container-teamspeak-improve">
|
||||
<div>
|
||||
<div class="options">
|
||||
<div class="title">{{tr "Identity: "/}} {{>identity_name}}</div>
|
||||
<div class="row">
|
||||
<div class="form-group identity-unique-id">
|
||||
<label>{{tr "Identity:" /}}</label>
|
||||
<label>{{tr "Unique ID:" /}}</label>
|
||||
<input class="form-control" value="" readonly>
|
||||
</div>
|
||||
<div class="form-group identity-level">
|
||||
<label>{{tr "Current level: " /}}</label>
|
||||
<input class="form-control" value="loading" readonly>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group identity-target-level">
|
||||
<div class="bmd-label-static">
|
||||
<div class="help-tagged">{{tr "Target level: " /}}</div>
|
||||
<div class="help-tip-container"> <!-- lets be absolute -->
|
||||
<div class="help-tip tip-right tip-small">
|
||||
<p>
|
||||
{{tr "A value of zero means unlimited. Improve until you abort"/}}
|
||||
</p>
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip.svg"/>
|
||||
<div class="tooltip">
|
||||
<a>
|
||||
{{tr "The number of threads used to improve your identity" /}}<br>
|
||||
{{tr "The optimal value is equal to the amount of kernel threads." /}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{tr "Target level: " /}}
|
||||
</div>
|
||||
<input class="form-control" value="0" type="number" min="0" max="160">
|
||||
</div>
|
||||
<div class="form-group threads">
|
||||
<div class="bmd-label-static">
|
||||
<div class="help-tagged">{{tr "Threads: " /}}</div>
|
||||
<div class="help-tip-container"> <!-- lets be absolute -->
|
||||
<div class="help-tip tip-right tip-small">
|
||||
<p>
|
||||
{{tr "The number of threads used to improve your identity<br>The optimal value is
|
||||
equal to the amount of kernal threads."/}}
|
||||
</p>
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip.svg"/>
|
||||
<div class="tooltip">
|
||||
<a>{{tr "A value of zero means unlimited. Improve until you abort" /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
{{tr "Threads: " /}}
|
||||
</div>
|
||||
<input class="form-control" value="2" type="number" min="1" max="32">
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-row">
|
||||
</div>
|
||||
</div>
|
||||
<div class="status">
|
||||
<div class="row">
|
||||
<div class="form-group hash-rate">
|
||||
<label>{{tr "Hashes/second:" /}}</label>
|
||||
<input class="form-control" value="0" readonly>
|
||||
|
@ -2814,8 +2846,9 @@
|
|||
<input class="form-control" value="00:00" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-secondary btn-raised button-close">{{tr "Close" /}}</button>
|
||||
<button class="btn btn-danger btn-raised button-close">{{tr "Close" /}}</button>
|
||||
<button class="btn btn-success btn-raised button-start-stop">{{tr "Start" /}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4753,5 +4786,399 @@
|
|||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script class="jsrender-template" id="tmpl_client_info" type="text/html">
|
||||
<div> <!-- Important for the renderer -->
|
||||
<div class="head">
|
||||
<div class="status-row">
|
||||
<div class="status-entry status-away">
|
||||
<div class="icon_em client-away"></div>
|
||||
</div>
|
||||
<div class="status-entry status-output-muted">
|
||||
<div class="icon_em client-output_muted"></div>
|
||||
</div>
|
||||
<div class="status-entry status-output-disabled">
|
||||
<div class="icon_em client-hardware_output_muted"></div>
|
||||
</div>
|
||||
<div class="status-entry status-input-muted">
|
||||
<div class="icon_em client-input_muted"></div>
|
||||
</div>
|
||||
<div class="status-entry status-input-disabled">
|
||||
<div class="icon_em client-hardware_input_muted"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-away-message" title="{{tr 'Away message' /}}">
|
||||
<a>A verry long away message, because I want to tell a story. There was a child....A verry long away message, because I want to tell a story. There was a child....A verry long away message, because I want to tell a story. There was a child....A verry long away message, because I want to tell a story. There was a child....A verry long away message, because I want to tell a story. There was a child....A verry long away message, because I want to tell a story. There was a child....A verry long away message, because I want to tell a story. There was a child....A verry long away message, because I want to tell a story. There was a child....</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="container-avatar">
|
||||
</div>
|
||||
<div class="container-name"></div>
|
||||
<div class="container-description">
|
||||
<div class="client-description"></div>
|
||||
</div>
|
||||
|
||||
<div class="container-categories">
|
||||
<div class="categories">
|
||||
<div class="entry" container="container-basic">{{tr "Basic Info" /}}</div>
|
||||
<div class="entry" container="container-groups">{{tr "Groups" /}}</div>
|
||||
<div class="entry" container="container-packets">{{tr "Packets" /}}</div>
|
||||
</div>
|
||||
<div class="bodies">
|
||||
<div class="body container-basic">
|
||||
<div class="left">
|
||||
<div class="property property-unique-id">
|
||||
<div class="title">
|
||||
<a>{{tr "Unique ID" /}}</a>
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip.svg"/>
|
||||
<div class="tooltip">
|
||||
<a>{{tr "Database ID:" /}} <span class="value-dbid">error: database id</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="value">
|
||||
<a>error: unique id</a>
|
||||
<div class="button button-copy">
|
||||
<div class="icon_em client-copy"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property property-teaforo">
|
||||
<div class="title">
|
||||
<a>{{tr "TeaForo Connection" /}}</a>
|
||||
</div>
|
||||
<div class="value">
|
||||
<a>This will be a really long string...</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property property-version">
|
||||
<div class="title">
|
||||
<a>{{tr "Version" /}}</a>
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip.svg"/>
|
||||
<div class="tooltip">
|
||||
<a>{{tr "Build timestamp:" /}} <span class="value-timestamp">error: no timestamp</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="value">
|
||||
<a>This will be a really long string...</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property property-country">
|
||||
<div class="title">
|
||||
<a>{{tr "Country" /}}</a>
|
||||
</div>
|
||||
<div class="value">
|
||||
<a>This will be a really long string...</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property property-ip">
|
||||
<div class="title">
|
||||
<a>{{tr "IP Address" /}}</a>
|
||||
</div>
|
||||
<div class="value">
|
||||
<a>error: IP</a>
|
||||
<div class="button button-copy">
|
||||
<div class="icon_em client-copy"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
<div class="right">
|
||||
<div class="property property-first-connected">
|
||||
<div class="title">
|
||||
<a>{{tr "First connected" /}}</a>
|
||||
</div>
|
||||
<div class="value">
|
||||
<a>error: first connected</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property property-connect-count">
|
||||
<div class="title">
|
||||
<a>{{tr "Connect count" /}}</a>
|
||||
</div>
|
||||
<div class="value">
|
||||
<a>error: connect count</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property property-online-since">
|
||||
<div class="title">
|
||||
<a>{{tr "Online since" /}}</a>
|
||||
</div>
|
||||
<div class="value">
|
||||
<a>error: online since</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property property-idle-time">
|
||||
<div class="title">
|
||||
<a>{{tr "Idle time" /}}</a>
|
||||
</div>
|
||||
<div class="value">
|
||||
<a>error: idle time</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property property-ping">
|
||||
<div class="title">
|
||||
<a>{{tr "Ping" /}}</a>
|
||||
</div>
|
||||
<div class="value">
|
||||
<a>error: ping</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body container-groups">
|
||||
<div class="left">
|
||||
<!-- <div class="title">{{tr "Server Groups" /}}</div> -->
|
||||
<div class="container">
|
||||
<div class="entries">
|
||||
</div>
|
||||
<div class="container-default-groups">
|
||||
<a>{{tr "Client has no own server group" /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-success button-group-add">{{tr "Manage client server groups" /}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="spacer"></div>
|
||||
<div class="right">
|
||||
<div class="title">{{tr "Channel Groups" /}}</div>
|
||||
<div class="container">
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
<div class="body container-packets">
|
||||
<div class="left">
|
||||
<div class="statistic statistic-packet-loss">
|
||||
<div class="title">
|
||||
<a>{{tr "Packetloss" /}}</a>
|
||||
</div>
|
||||
<div class="downstream">
|
||||
<a>{{tr "Downstream" /}}</a>
|
||||
<a class="value"></a>
|
||||
</div>
|
||||
<div class="upstream">
|
||||
<a>{{tr "Upstream" /}}</a>
|
||||
<a class="value"></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="statistic statistic-transmitted-packets">
|
||||
<div class="title">
|
||||
<a>{{tr "Transmitted Packets" /}}</a>
|
||||
</div>
|
||||
<div class="downstream">
|
||||
<a>{{tr "Downstream" /}}</a>
|
||||
<a class="value"></a>
|
||||
</div>
|
||||
<div class="upstream">
|
||||
<a>{{tr "Upstream" /}}</a>
|
||||
<a class="value"></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="statistic statistic-transmitted-bytes">
|
||||
<div class="title">
|
||||
<a>{{tr "Transmitted Bytes" /}}</a>
|
||||
</div>
|
||||
<div class="downstream">
|
||||
<a>{{tr "Downstream" /}}</a>
|
||||
<a class="value"></a>
|
||||
</div>
|
||||
<div class="upstream">
|
||||
<a>{{tr "Upstream" /}}</a>
|
||||
<a class="value"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
<div class="right">
|
||||
<div class="statistic statistic-quota">
|
||||
<div class="title">
|
||||
<a>{{tr "Used/Max file transfer quota" /}}</a>
|
||||
</div>
|
||||
<div class="downstream">
|
||||
<a>{{tr "Downstream" /}}</a>
|
||||
<a class="value"></a>
|
||||
</div>
|
||||
<div class="upstream">
|
||||
<a>{{tr "Upstream" /}}</a>
|
||||
<a class="value"></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="statistic statistic-bandwidth-second">
|
||||
<div class="title">
|
||||
<a>{{tr "Bandwidth last second" /}}</a>
|
||||
</div>
|
||||
<div class="downstream">
|
||||
<a>{{tr "Downstream" /}}</a>
|
||||
<a class="value"></a>
|
||||
</div>
|
||||
<div class="upstream">
|
||||
<a>{{tr "Upstream" /}}</a>
|
||||
<a class="value"></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="statistic statistic-bandwidth-minute">
|
||||
<div class="title">
|
||||
<a>{{tr "Bandwidth last minute" /}}</a>
|
||||
</div>
|
||||
<div class="downstream">
|
||||
<a>{{tr "Downstream" /}}</a>
|
||||
<a class="value"></a>
|
||||
</div>
|
||||
<div class="upstream">
|
||||
<a>{{tr "Upstream" /}}</a>
|
||||
<a class="value"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script class="jsrender-template" id="tmpl_server_info" type="text/html">
|
||||
<div> <!-- required for the renderer -->
|
||||
<div class="container-top hostbanner">
|
||||
</div>
|
||||
<div class="container-body">
|
||||
<div class="group">
|
||||
<div class="container-image">
|
||||
<img src="img/serveredit_1.png">
|
||||
</div>
|
||||
<div class="container-properties">
|
||||
<div class="row">
|
||||
<a class="key">{{tr "Server name" /}}</a>
|
||||
<div class="value server-name">
|
||||
error: name
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a class="key">{{tr "Server region" /}}</a>
|
||||
<div class="value server-region">
|
||||
error: region
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a class="key">{{tr "Slots" /}}</a>
|
||||
<div class="value server-slots">
|
||||
error: slots
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a class="key">{{tr "First run" /}}</a>
|
||||
<div class="value server-first-run">
|
||||
error: first run
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a class="key">{{tr "Uptime" /}}</a>
|
||||
<div class="value server-uptime">
|
||||
error: uptime
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group reverse">
|
||||
<div class="container-image">
|
||||
<img src="img/serveredit_2.png">
|
||||
</div>
|
||||
<div class="container-properties">
|
||||
<div class="row">
|
||||
<a class="key">{{tr "IP Address" /}}</a>
|
||||
<div class="value server-ip">
|
||||
error: ip
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a class="key">{{tr "Version" /}}</a>
|
||||
<div class="value server-version">
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip.svg"/>
|
||||
<div class="tooltip">
|
||||
<a></a>
|
||||
</div>
|
||||
</div>
|
||||
<a></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a class="key">{{tr "Platform" /}}</a>
|
||||
<div class="value server-platform">
|
||||
error: platform
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-network">
|
||||
<div class="container-button">
|
||||
<button class="btn btn-purple button-show-bandwidth">{{tr "Show Bandwidth" /}}</button>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="row">
|
||||
<a class="key">{{tr "Average ping" /}}</a>
|
||||
<div class="value server-ping">
|
||||
error: average ping
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a class="key">{{tr "Average packet loss" /}}</a>
|
||||
<div class="value server-packet-loss">
|
||||
error: average packet loss
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="container-image">
|
||||
<img src="img/serveredit_3.png">
|
||||
</div>
|
||||
<div class="container-properties">
|
||||
<div class="row">
|
||||
<a class="key">{{tr "Global unique ID" /}}</a>
|
||||
<div class="value server-unique-id">
|
||||
error: unique id
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a class="key">{{tr "Current Channels" /}}</a>
|
||||
<div class="value server-channel-count">
|
||||
error: channel count
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a class="key">{{tr "Voice data encoded" /}}</a>
|
||||
<div class="value server-voice-encryption">
|
||||
error: voice encryption
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a class="key">{{tr "Minimal security level" /}}</a>
|
||||
<div class="value server-min-security-level">
|
||||
error: security level
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a class="key">{{tr "Complains until ban" /}}</a>
|
||||
<div class="value server-complains">
|
||||
error: complains
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-buttons">
|
||||
<button class="btn btn-danger button-close">{{tr "Close" /}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
After Width: | Height: | Size: 1.6 MiB |
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
Binary file not shown.
After Width: | Height: | Size: 88 KiB |
Binary file not shown.
After Width: | Height: | Size: 294 KiB |
|
@ -61,7 +61,7 @@ namespace bipc {
|
|||
abstract send_message(type: string, data: any, target?: string);
|
||||
|
||||
protected handle_message(message: BroadcastMessage) {
|
||||
console.log("Received: %o", message);
|
||||
log.trace(LogCategory.IPC, tr("Received message %o"), message);
|
||||
|
||||
if(message.receiver === BasicIPCHandler.BROADCAST_UNIQUE_ID) {
|
||||
if(message.type == "process-query") {
|
||||
|
@ -327,10 +327,10 @@ namespace bipc {
|
|||
protected register_method<R>(method: (...args: any[]) => Promise<R> | string) {
|
||||
let method_name: string;
|
||||
if(typeof method === "function") {
|
||||
console.log("Proxy method: %o", method.name);
|
||||
log.debug(LogCategory.IPC, tr("Registering method proxy for %s"), method.name);
|
||||
method_name = method.name;
|
||||
} else {
|
||||
console.log("Proxy method: %o", method);
|
||||
log.debug(LogCategory.IPC, tr("Registering method proxy for %s"), method);
|
||||
method_name = method;
|
||||
}
|
||||
|
||||
|
@ -415,11 +415,11 @@ namespace bipc {
|
|||
}
|
||||
|
||||
try {
|
||||
console.log(tr("Invoking method %s with arguments: %o"), data.method_name, data.arguments);
|
||||
log.info(LogCategory.IPC, tr("Invoking method %s with arguments: %o"), data.method_name, data.arguments);
|
||||
|
||||
const promise = this[data.method_name](...data.arguments);
|
||||
promise.then(result => {
|
||||
console.log(tr("Result: %o"), result);
|
||||
log.info(LogCategory.IPC, tr("Result: %o"), result);
|
||||
this._send_result(data.promise_id, true, result);
|
||||
}).catch(error => {
|
||||
this._send_result(data.promise_id, false, error);
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
/// <reference path="proto.ts" />
|
||||
/// <reference path="ui/view.ts" />
|
||||
/// <reference path="settings.ts" />
|
||||
/// <reference path="ui/frames/SelectedItemInfo.ts" />
|
||||
/// <reference path="FileManager.ts" />
|
||||
/// <reference path="permission/PermissionManager.ts" />
|
||||
/// <reference path="permission/GroupManager.ts" />
|
||||
|
@ -90,7 +89,6 @@ class ConnectionHandler {
|
|||
groups: GroupManager;
|
||||
|
||||
side_bar: chat.Frame;
|
||||
select_info: InfoBar;
|
||||
|
||||
settings: ServerSettings;
|
||||
sound: sound.SoundManager;
|
||||
|
@ -126,7 +124,6 @@ class ConnectionHandler {
|
|||
this.settings = new ServerSettings();
|
||||
|
||||
this.log = new log.ServerLog(this);
|
||||
this.select_info = new InfoBar(this);
|
||||
this.channelTree = new ChannelTree(this);
|
||||
this.side_bar = new chat.Frame(this);
|
||||
this.sound = new sound.SoundManager(this);
|
||||
|
@ -192,7 +189,7 @@ class ConnectionHandler {
|
|||
server_address.port = 9987;
|
||||
}
|
||||
}
|
||||
console.log(tr("Start connection to %s:%d"), server_address.host, server_address.port);
|
||||
log.info(LogCategory.CLIENT, tr("Start connection to %s:%d"), server_address.host, server_address.port);
|
||||
this.log.log(log.server.Type.CONNECTION_BEGIN, {
|
||||
address: {
|
||||
server_hostname: server_address.host,
|
||||
|
@ -210,7 +207,7 @@ class ConnectionHandler {
|
|||
password: password
|
||||
}
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to hash connect password: %o"), error);
|
||||
log.error(LogCategory.CLIENT, tr("Failed to hash connect password: %o"), error);
|
||||
createErrorModal(tr("Error while hashing password"), tr("Failed to hash server password!<br>") + error).open();
|
||||
}
|
||||
}
|
||||
|
@ -278,7 +275,7 @@ class ConnectionHandler {
|
|||
* LISTENER
|
||||
*/
|
||||
onConnected() {
|
||||
console.log("Client connected!");
|
||||
log.info(LogCategory.CLIENT, tr("Client connected"));
|
||||
this.permissions.requestPermissionList();
|
||||
if(this.groups.serverGroups.length == 0)
|
||||
this.groups.requestGroups();
|
||||
|
@ -416,7 +413,7 @@ class ConnectionHandler {
|
|||
this.sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||
break;
|
||||
case DisconnectReason.DNS_FAILED:
|
||||
console.error(tr("Failed to resolve hostname: %o"), data);
|
||||
log.error(LogCategory.CLIENT, tr("Failed to resolve hostname: %o"), data);
|
||||
this.log.log(log.server.Type.CONNECTION_HOSTNAME_RESOLVE_ERROR, {
|
||||
message: data as any
|
||||
});
|
||||
|
@ -428,7 +425,7 @@ class ConnectionHandler {
|
|||
this.log.log(log.server.Type.CONNECTION_FAILED, {});
|
||||
break;
|
||||
}
|
||||
console.error(tr("Could not connect to remote host! Error: %o"), data);
|
||||
log.error(LogCategory.CLIENT, tr("Could not connect to remote host! Error: %o"), data);
|
||||
|
||||
if(native_client) {
|
||||
createErrorModal(
|
||||
|
@ -452,7 +449,7 @@ class ConnectionHandler {
|
|||
break;
|
||||
case DisconnectReason.HANDSHAKE_FAILED:
|
||||
//TODO sound
|
||||
console.error(tr("Failed to process handshake: %o"), data);
|
||||
log.error(LogCategory.CLIENT, tr("Failed to process handshake: %o"), data);
|
||||
createErrorModal(
|
||||
tr("Could not connect"),
|
||||
tr("Failed to process handshake: ") + data as string
|
||||
|
@ -476,7 +473,7 @@ class ConnectionHandler {
|
|||
auto_reconnect = false;
|
||||
break;
|
||||
case DisconnectReason.CONNECTION_CLOSED:
|
||||
console.error(tr("Lost connection to remote server!"));
|
||||
log.error(LogCategory.CLIENT, tr("Lost connection to remote server!"));
|
||||
createErrorModal(
|
||||
tr("Connection closed"),
|
||||
tr("The connection was closed by remote host")
|
||||
|
@ -486,7 +483,7 @@ class ConnectionHandler {
|
|||
auto_reconnect = true;
|
||||
break;
|
||||
case DisconnectReason.CONNECTION_PING_TIMEOUT:
|
||||
console.error(tr("Connection ping timeout"));
|
||||
log.error(LogCategory.CLIENT, tr("Connection ping timeout"));
|
||||
this.sound.play(Sound.CONNECTION_DISCONNECTED_TIMEOUT);
|
||||
createErrorModal(
|
||||
tr("Connection lost"),
|
||||
|
@ -561,9 +558,8 @@ class ConnectionHandler {
|
|||
this.sound.play(Sound.CONNECTION_BANNED); //TODO findout if it was a disconnect or a connect refuse
|
||||
break;
|
||||
default:
|
||||
console.error(tr("Got uncaught disconnect!"));
|
||||
console.error(tr("Type: %o Data:"), type);
|
||||
console.error(data);
|
||||
log.error(LogCategory.CLIENT, tr("Got uncaught disconnect!"));
|
||||
log.error(LogCategory.CLIENT, tr("Type: %o Data: %o"), type, data);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -574,18 +570,17 @@ class ConnectionHandler {
|
|||
|
||||
if(control_bar.current_connection_handler() == this)
|
||||
control_bar.update_connection_state();
|
||||
this.select_info.setCurrentSelected(null);
|
||||
this.side_bar.private_conversations().clear_client_ids();
|
||||
this.hostbanner.update();
|
||||
|
||||
if(auto_reconnect) {
|
||||
if(!this.serverConnection) {
|
||||
console.log(tr("Allowed to auto reconnect but cant reconnect because we dont have any information left..."));
|
||||
log.info(LogCategory.NETWORKING, tr("Allowed to auto reconnect but cant reconnect because we dont have any information left..."));
|
||||
return;
|
||||
}
|
||||
this.log.log(log.server.Type.RECONNECT_SCHEDULED, {timeout: 50000});
|
||||
|
||||
console.log(tr("Allowed to auto reconnect. Reconnecting in 5000ms"));
|
||||
log.info(LogCategory.NETWORKING, tr("Allowed to auto reconnect. Reconnecting in 5000ms"));
|
||||
const server_address = this.serverConnection.remote_address();
|
||||
const profile = this.serverConnection.handshake_handler().profile;
|
||||
|
||||
|
@ -695,11 +690,11 @@ class ConnectionHandler {
|
|||
if(vconnection.voice_recorder().input.current_state() === audio.recorder.InputState.PAUSED) {
|
||||
vconnection.voice_recorder().input.start().then(result => {
|
||||
if(result != audio.recorder.InputStartResult.EOK) {
|
||||
console.warn(tr("Failed to start microphone input (%s)."), result);
|
||||
log.warn(LogCategory.VOICE, tr("Failed to start microphone input (%s)."), result);
|
||||
createErrorModal(tr("Failed to start recording"), MessageHelper.formatMessage(tr("Microphone start failed.{:br:}Error: {}"), result)).open();
|
||||
}
|
||||
}).catch(error => {
|
||||
console.warn(tr("Failed to start microphone input (%s)."), error);
|
||||
log.warn(LogCategory.VOICE, tr("Failed to start microphone input (%s)."), error);
|
||||
createErrorModal(tr("Failed to start recording"), MessageHelper.formatMessage(tr("Microphone start failed.{:br:}Error: {}"), error)).open();
|
||||
});
|
||||
}
|
||||
|
@ -751,14 +746,13 @@ class ConnectionHandler {
|
|||
|
||||
resize_elements() {
|
||||
this.channelTree.handle_resized();
|
||||
this.select_info.handle_resize();
|
||||
this.invoke_resized_on_activate = false;
|
||||
}
|
||||
|
||||
acquire_recorder(voice_recoder: RecorderProfile, update_control_bar: boolean) {
|
||||
const vconnection = this.serverConnection.voice_connection();
|
||||
(vconnection ? vconnection.acquire_voice_recorder(voice_recoder) : Promise.resolve()).catch(error => {
|
||||
console.error(tr("Failed to acquire recorder (%o)"), error);
|
||||
log.warn(LogCategory.VOICE, tr("Failed to acquire recorder (%o)"), error);
|
||||
}).then(() => {
|
||||
this.update_voice_status(undefined);
|
||||
});
|
||||
|
@ -785,7 +779,7 @@ class ConnectionHandler {
|
|||
if(typeof(data) === "undefined")
|
||||
return;
|
||||
if(data === null) {
|
||||
console.log(tr("Deleting existing avatar"));
|
||||
log.info(LogCategory.CLIENT, tr("Deleting existing avatar"));
|
||||
this.serverConnection.send_command('ftdeletefile', {
|
||||
name: "/avatar_", /* delete own avatar */
|
||||
path: "",
|
||||
|
@ -793,7 +787,7 @@ class ConnectionHandler {
|
|||
}).then(() => {
|
||||
createInfoModal(tr("Avatar deleted"), tr("Avatar successfully deleted")).open();
|
||||
}).catch(error => {
|
||||
console.error(tr("Failed to reset avatar flag: %o"), error);
|
||||
log.error(LogCategory.GENERAL, tr("Failed to reset avatar flag: %o"), error);
|
||||
|
||||
let message;
|
||||
if(error instanceof CommandResult)
|
||||
|
@ -804,7 +798,7 @@ class ConnectionHandler {
|
|||
return;
|
||||
});
|
||||
} else {
|
||||
console.log(tr("Uploading new avatar"));
|
||||
log.info(LogCategory.CLIENT, tr("Uploading new avatar"));
|
||||
(async () => {
|
||||
let key: transfer.UploadKey;
|
||||
try {
|
||||
|
@ -817,7 +811,7 @@ class ConnectionHandler {
|
|||
channel_password: undefined
|
||||
});
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to initialize avatar upload: %o"), error);
|
||||
log.error(LogCategory.GENERAL, tr("Failed to initialize avatar upload: %o"), error);
|
||||
let message;
|
||||
if(error instanceof CommandResult) {
|
||||
//TODO: Resolve permission name
|
||||
|
@ -837,7 +831,7 @@ class ConnectionHandler {
|
|||
try {
|
||||
await transfer.spawn_upload_transfer(key).put_data(data);
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to upload avatar: %o"), error);
|
||||
log.error(LogCategory.GENERAL, tr("Failed to upload avatar: %o"), error);
|
||||
|
||||
let message;
|
||||
if(typeof(error) === "string")
|
||||
|
@ -853,7 +847,7 @@ class ConnectionHandler {
|
|||
client_flag_avatar: guid()
|
||||
});
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to update avatar flag: %o"), error);
|
||||
log.error(LogCategory.GENERAL, tr("Failed to update avatar flag: %o"), error);
|
||||
|
||||
let message;
|
||||
if(error instanceof CommandResult)
|
||||
|
@ -889,9 +883,6 @@ class ConnectionHandler {
|
|||
this.side_bar && this.side_bar.destroy();
|
||||
this.side_bar = undefined;
|
||||
|
||||
this.select_info && this.select_info.destroy();
|
||||
this.select_info = undefined;
|
||||
|
||||
this.log && this.log.destroy();
|
||||
this.log = undefined;
|
||||
|
||||
|
|
|
@ -235,7 +235,7 @@ class FileManager extends connection.AbstractCommandHandler {
|
|||
}
|
||||
|
||||
if(!entry) {
|
||||
console.error(tr("Invalid file list entry. Path: %s"), json[0]["path"]);
|
||||
log.error(LogCategory.CLIENT, tr("Invalid file list entry. Path: %s"), json[0]["path"]);
|
||||
return;
|
||||
}
|
||||
for(let e of (json as Array<FileEntry>)) {
|
||||
|
@ -258,7 +258,7 @@ class FileManager extends connection.AbstractCommandHandler {
|
|||
}
|
||||
|
||||
if(!entry) {
|
||||
console.error(tr("Invalid file list entry finish. Path: "), json[0]["path"]);
|
||||
log.error(LogCategory.CLIENT, tr("Invalid file list entry finish. Path: "), json[0]["path"]);
|
||||
return;
|
||||
}
|
||||
entry.callback(entry.entries);
|
||||
|
@ -659,7 +659,7 @@ class IconManager {
|
|||
try {
|
||||
download_key = await this.create_icon_download(id);
|
||||
} catch(error) {
|
||||
console.error(tr("Could not request download for icon %d: %o"), id, error);
|
||||
log.error(LogCategory.CLIENT, tr("Could not request download for icon %d: %o"), id, error);
|
||||
throw "Failed to request icon";
|
||||
}
|
||||
|
||||
|
@ -668,7 +668,7 @@ class IconManager {
|
|||
try {
|
||||
response = await downloader.request_file();
|
||||
} catch(error) {
|
||||
console.error(tr("Could not download icon %d: %o"), id, error);
|
||||
log.error(LogCategory.CLIENT, tr("Could not download icon %d: %o"), id, error);
|
||||
throw "failed to download icon";
|
||||
}
|
||||
|
||||
|
@ -713,7 +713,7 @@ class IconManager {
|
|||
return result;
|
||||
throw "load result is empty";
|
||||
} catch(error) {
|
||||
console.error(tr("Icon download failed of icon %d: %o"), id, error);
|
||||
log.error(LogCategory.CLIENT, tr("Icon download failed of icon %d: %o"), id, error);
|
||||
}
|
||||
|
||||
throw "icon not found";
|
||||
|
@ -759,7 +759,7 @@ class IconManager {
|
|||
};
|
||||
if(icon instanceof Promise) {
|
||||
icon.then(_apply).catch(error => {
|
||||
console.error(tr("Could not load icon. Reason: %s"), error);
|
||||
log.error(LogCategory.CLIENT, tr("Could not load icon. Reason: %s"), error);
|
||||
icon_load_image.removeClass("icon_loading").addClass("icon client-warning").attr("tag", "Could not load icon");
|
||||
});
|
||||
} else {
|
||||
|
@ -858,7 +858,7 @@ class AvatarManager {
|
|||
}
|
||||
|
||||
create_avatar_download(client_avatar_id: string) : Promise<transfer.DownloadKey> {
|
||||
console.log(tr("Downloading avatar %s"), client_avatar_id);
|
||||
log.debug(LogCategory.GENERAL, "Requesting download for avatar %s", client_avatar_id);
|
||||
return this.handle.download_file("", "/avatar_" + client_avatar_id);
|
||||
}
|
||||
|
||||
|
@ -868,7 +868,7 @@ class AvatarManager {
|
|||
try {
|
||||
download_key = await this.create_avatar_download(client_avatar_id);
|
||||
} catch(error) {
|
||||
console.error(tr("Could not request download for avatar %s: %o"), client_avatar_id, error);
|
||||
log.error(LogCategory.GENERAL, tr("Could not request download for avatar %s: %o"), client_avatar_id, error);
|
||||
throw "failed to request avatar download";
|
||||
}
|
||||
|
||||
|
@ -877,7 +877,7 @@ class AvatarManager {
|
|||
try {
|
||||
response = await downloader.request_file();
|
||||
} catch(error) {
|
||||
console.error(tr("Could not download avatar %s: %o"), client_avatar_id, error);
|
||||
log.error(LogCategory.GENERAL, tr("Could not download avatar %s: %o"), client_avatar_id, error);
|
||||
throw "failed to download avatar";
|
||||
}
|
||||
|
||||
|
@ -915,7 +915,7 @@ class AvatarManager {
|
|||
if(_cached.avatar_id === avatar_id)
|
||||
return; /* cache is up2date */
|
||||
|
||||
console.log(tr("Deleting cached avatar for client %s. Cached version: %s; New version: %s"), client_avatar_id, _cached.avatar_id, avatar_id);
|
||||
log.info(LogCategory.GENERAL, tr("Deleting cached avatar for client %s. Cached version: %s; New version: %s"), client_avatar_id, _cached.avatar_id, avatar_id);
|
||||
delete this._cached_avatars[client_avatar_id];
|
||||
AvatarManager.cache.delete("avatar_" + client_avatar_id).catch(error => {
|
||||
log.error(LogCategory.GENERAL, tr("Failed to delete cached avatar for client %o: %o"), client_avatar_id, error);
|
||||
|
@ -961,7 +961,7 @@ class AvatarManager {
|
|||
try {
|
||||
avatar = await this.resolved_cached(client_avatar_id, avatar_id);
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
log.error(LogCategory.CLIENT, error);
|
||||
}
|
||||
|
||||
if(!avatar)
|
||||
|
@ -984,7 +984,7 @@ class AvatarManager {
|
|||
});
|
||||
});
|
||||
})().catch(reason => {
|
||||
console.error(tr("Could not load avatar for id %s. Reason: %s"), client_avatar_id, reason);
|
||||
log.error(LogCategory.CLIENT, tr("Could not load avatar for id %s. Reason: %s"), client_avatar_id, reason);
|
||||
//TODO Broken image
|
||||
loader_image.addClass("icon client-warning").attr("tag", tr("Could not load avatar ") + client_avatar_id);
|
||||
})
|
||||
|
@ -1049,7 +1049,7 @@ class AvatarManager {
|
|||
if(avatar_id) {
|
||||
if(this._cached_avatars[avatar_id]) { /* Test if we're may able to load the client avatar sync without a loading screen */
|
||||
const cache: Avatar = this._cached_avatars[avatar_id];
|
||||
console.log("[AVATAR] Using cached avatar. ID: %o | Version: %o (Cached: %o)", avatar_id, client_handle ? client_handle.properties.client_flag_avatar : undefined, cache.avatar_id);
|
||||
log.debug(LogCategory.GENERAL, tr("Using cached avatar. ID: %o | Version: %o (Cached: %o)"), avatar_id, client_handle ? client_handle.properties.client_flag_avatar : undefined, cache.avatar_id);
|
||||
if(!client_handle || client_handle.properties.client_flag_avatar == cache.avatar_id) {
|
||||
const image = $.spawn("img").attr("src", cache.url).css({width: '100%', height: '100%'});
|
||||
return container.append(image);
|
||||
|
@ -1063,13 +1063,13 @@ class AvatarManager {
|
|||
let avatar: Avatar;
|
||||
let loaded_image = this.generate_default_image();
|
||||
|
||||
console.log("[AVATAR] Resolving avatar. ID: %o | Version: %o", avatar_id, client_handle ? client_handle.properties.client_flag_avatar : undefined);
|
||||
log.debug(LogCategory.GENERAL, tr("Resolving avatar. ID: %o | Version: %o"), avatar_id, client_handle ? client_handle.properties.client_flag_avatar : undefined);
|
||||
try {
|
||||
//TODO: Cache if avatar load failed and try again in some minutes/may just even consider using the default avatar 'till restart
|
||||
try {
|
||||
avatar = await this.resolved_cached(avatar_id, client_handle ? client_handle.properties.client_flag_avatar : undefined);
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to use cached avatar: %o"), error);
|
||||
log.error(LogCategory.GENERAL, tr("Failed to use cached avatar: %o"), error);
|
||||
}
|
||||
|
||||
if(!avatar)
|
||||
|
|
|
@ -94,7 +94,7 @@ namespace bookmarks {
|
|||
try {
|
||||
bookmarks = JSON.parse(bookmark_json) || {} as BookmarkConfig;
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to load bookmarks: %o"), error);
|
||||
log.error(LogCategory.BOOKMARKS, tr("Failed to load bookmarks: %o"), error);
|
||||
bookmarks = {} as any;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,8 @@ namespace connection {
|
|||
this["notifychannelshow"] = this.handleCommandChannelShow;
|
||||
|
||||
this["notifyserverconnectioninfo"] = this.handleNotifyServerConnectionInfo;
|
||||
this["notifyconnectioninfo"] = this.handleNotifyConnectionInfo;
|
||||
|
||||
this["notifycliententerview"] = this.handleCommandClientEnterView;
|
||||
this["notifyclientleftview"] = this.handleCommandClientLeftView;
|
||||
this["notifyclientmoved"] = this.handleNotifyClientMoved;
|
||||
|
@ -33,6 +35,7 @@ namespace connection {
|
|||
this["notifychannelmoved"] = this.handleNotifyChannelMoved;
|
||||
this["notifychanneledited"] = this.handleNotifyChannelEdited;
|
||||
this["notifytextmessage"] = this.handleNotifyTextMessage;
|
||||
this["notifyclientchatcomposing"] = this.notifyClientChatComposing;
|
||||
this["notifyclientchatclosed"] = this.handleNotifyClientChatClosed;
|
||||
this["notifyclientupdated"] = this.handleNotifyClientUpdated;
|
||||
this["notifyserveredited"] = this.handleNotifyServerEdited;
|
||||
|
@ -78,8 +81,7 @@ namespace connection {
|
|||
} else if(typeof(ex) === "string") {
|
||||
this.connection_handler.log.log(log.server.Type.CONNECTION_COMMAND_ERROR, {error: ex});
|
||||
} else {
|
||||
console.error(tr("Invalid promise result type: %o. Result:"), typeof (ex));
|
||||
console.error(ex);
|
||||
log.error(LogCategory.NETWORKING, tr("Invalid promise result type: %s. Result: %o"), typeof (ex), ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,7 +112,7 @@ namespace connection {
|
|||
|
||||
let code : string = json["return_code"];
|
||||
if(!code || code.length == 0) {
|
||||
console.log(tr("Invalid return code! (%o)"), json);
|
||||
log.warn(LogCategory.NETWORKING, tr("Invalid return code! (%o)"), json);
|
||||
return;
|
||||
}
|
||||
let retListeners = this.connection["_retListener"];
|
||||
|
@ -130,9 +132,9 @@ namespace connection {
|
|||
handleCommandServerInit(json){
|
||||
//We could setup the voice channel
|
||||
if(this.connection.support_voice()) {
|
||||
console.log(tr("Setting up voice"));
|
||||
log.debug(LogCategory.NETWORKING, tr("Setting up voice"));
|
||||
} else {
|
||||
console.log(tr("Skipping voice setup (No voice bridge available)"));
|
||||
log.debug(LogCategory.NETWORKING, tr("Skipping voice setup (No voice bridge available)"));
|
||||
}
|
||||
|
||||
|
||||
|
@ -222,11 +224,32 @@ namespace connection {
|
|||
|
||||
/* everything is a number, so lets parse it */
|
||||
for(const key of Object.keys(json))
|
||||
json[key] = parseInt(json[key]);
|
||||
json[key] = parseFloat(json[key]);
|
||||
|
||||
this.connection_handler.channelTree.server.set_connection_info(json);
|
||||
}
|
||||
|
||||
handleNotifyConnectionInfo(json) {
|
||||
json = json[0];
|
||||
|
||||
const object = new ClientConnectionInfo();
|
||||
/* everything is a number (except ip), so lets parse it */
|
||||
for(const key of Object.keys(json)) {
|
||||
if(key === "connection_client_ip")
|
||||
object[key] = json[key];
|
||||
else
|
||||
object[key] = parseFloat(json[key]);
|
||||
}
|
||||
|
||||
const client = this.connection_handler.channelTree.findClient(parseInt(json["clid"]));
|
||||
if(!client) {
|
||||
log.warn(LogCategory.NETWORKING, tr("Received client connection info for unknown client (%o)"), json["clid"]);
|
||||
return;
|
||||
}
|
||||
|
||||
client.set_connection_info(object);
|
||||
}
|
||||
|
||||
private createChannelFromJson(json, ignoreOrder: boolean = false) {
|
||||
let tree = this.connection.client.channelTree;
|
||||
|
||||
|
@ -236,14 +259,14 @@ namespace connection {
|
|||
let prev = tree.findChannel(json["channel_order"]);
|
||||
if(!prev && json["channel_order"] != 0) {
|
||||
if(!ignoreOrder) {
|
||||
console.error(tr("Invalid channel order id!"));
|
||||
log.error(LogCategory.NETWORKING, tr("Invalid channel order id!"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let parent = tree.findChannel(json["cpid"]);
|
||||
if(!parent && json["cpid"] != 0) {
|
||||
console.error(tr("Invalid channel parent"));
|
||||
log.error(LogCategory.NETWORKING, tr("Invalid channel parent"));
|
||||
return;
|
||||
}
|
||||
tree.moveChannel(channel, prev, parent); //TODO test if channel exists!
|
||||
|
@ -275,7 +298,7 @@ namespace connection {
|
|||
|
||||
handleCommandChannelList(json) {
|
||||
this.connection.client.channelTree.hide_channel_tree(); /* dont perform channel inserts on the dom to prevent style recalculations */
|
||||
console.log(tr("Got %d new channels"), json.length);
|
||||
log.debug(LogCategory.NETWORKING, tr("Got %d new channels"), json.length);
|
||||
for(let index = 0; index < json.length; index++)
|
||||
this.createChannelFromJson(json[index], true);
|
||||
}
|
||||
|
@ -297,12 +320,12 @@ namespace connection {
|
|||
let tree = this.connection.client.channelTree;
|
||||
const conversations = this.connection.client.side_bar.channel_conversations();
|
||||
|
||||
console.log(tr("Got %d channel deletions"), json.length);
|
||||
log.info(LogCategory.NETWORKING, tr("Got %d channel deletions"), json.length);
|
||||
for(let index = 0; index < json.length; index++) {
|
||||
conversations.delete_conversation(parseInt(json[index]["cid"]));
|
||||
let channel = tree.findChannel(json[index]["cid"]);
|
||||
if(!channel) {
|
||||
console.error(tr("Invalid channel onDelete (Unknown channel)"));
|
||||
log.error(LogCategory.NETWORKING, tr("Invalid channel onDelete (Unknown channel)"));
|
||||
continue;
|
||||
}
|
||||
tree.deleteChannel(channel);
|
||||
|
@ -313,12 +336,12 @@ namespace connection {
|
|||
let tree = this.connection.client.channelTree;
|
||||
const conversations = this.connection.client.side_bar.channel_conversations();
|
||||
|
||||
console.log(tr("Got %d channel hides"), json.length);
|
||||
log.info(LogCategory.NETWORKING, tr("Got %d channel hides"), json.length);
|
||||
for(let index = 0; index < json.length; index++) {
|
||||
conversations.delete_conversation(parseInt(json[index]["cid"]));
|
||||
let channel = tree.findChannel(json[index]["cid"]);
|
||||
if(!channel) {
|
||||
console.error(tr("Invalid channel on hide (Unknown channel)"));
|
||||
log.error(LogCategory.NETWORKING, tr("Invalid channel on hide (Unknown channel)"));
|
||||
continue;
|
||||
}
|
||||
tree.deleteChannel(channel);
|
||||
|
@ -443,7 +466,7 @@ namespace connection {
|
|||
let tree = this.connection.client.channelTree;
|
||||
let client = tree.findClient(entry["clid"]);
|
||||
if(!client) {
|
||||
console.error(tr("Unknown client left!"));
|
||||
log.error(LogCategory.NETWORKING, tr("Unknown client left!"));
|
||||
return 0;
|
||||
}
|
||||
if(client == this.connection.client.getClient()) {
|
||||
|
@ -500,7 +523,7 @@ namespace connection {
|
|||
if(channel_from == own_channel)
|
||||
this.connection_handler.sound.play(Sound.USER_LEFT_TIMEOUT);
|
||||
} else {
|
||||
console.error(tr("Unknown client left reason!"));
|
||||
log.error(LogCategory.NETWORKING, tr("Unknown client left reason!"));
|
||||
}
|
||||
|
||||
if(!channel_to) {
|
||||
|
@ -531,16 +554,16 @@ namespace connection {
|
|||
let channel_from = tree.findChannel(json["cfid"]);
|
||||
|
||||
if(!client) {
|
||||
console.error(tr("Unknown client move (Client)!"));
|
||||
log.error(LogCategory.NETWORKING, tr("Unknown client move (Client)!"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(!channel_to) {
|
||||
console.error(tr("Unknown client move (Channel to)!"));
|
||||
log.error(LogCategory.NETWORKING, tr("Unknown client move (Channel to)!"));
|
||||
return 0;
|
||||
}
|
||||
if(!channel_from) //Not critical
|
||||
console.error(tr("Unknown client move (Channel from)!"));
|
||||
log.error(LogCategory.NETWORKING, tr("Unknown client move (Channel from)!"));
|
||||
|
||||
let self = client instanceof LocalClientEntry;
|
||||
let current_clients: ClientEntry[];
|
||||
|
@ -626,25 +649,23 @@ namespace connection {
|
|||
|
||||
handleNotifyChannelMoved(json) {
|
||||
json = json[0]; //Only one bulk
|
||||
for(let key in json)
|
||||
console.log("Key: " + key + " Value: " + json[key]);
|
||||
|
||||
let tree = this.connection.client.channelTree;
|
||||
let channel = tree.findChannel(json["cid"]);
|
||||
if(!channel) {
|
||||
console.error(tr("Unknown channel move (Channel)!"));
|
||||
log.error(LogCategory.NETWORKING, tr("Unknown channel move (Channel)!"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
let prev = tree.findChannel(json["order"]);
|
||||
if(!prev && json["order"] != 0) {
|
||||
console.error(tr("Unknown channel move (prev)!"));
|
||||
log.error(LogCategory.NETWORKING, tr("Unknown channel move (prev)!"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
let parent = tree.findChannel(json["cpid"]);
|
||||
if(!parent && json["cpid"] != 0) {
|
||||
console.error(tr("Unknown channel move (parent)!"));
|
||||
log.error(LogCategory.NETWORKING, tr("Unknown channel move (parent)!"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -657,7 +678,7 @@ namespace connection {
|
|||
let tree = this.connection.client.channelTree;
|
||||
let channel = tree.findChannel(json["cid"]);
|
||||
if(!channel) {
|
||||
console.error(tr("Unknown channel edit (Channel)!"));
|
||||
log.error(LogCategory.NETWORKING, tr("Unknown channel edit (Channel)!"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -686,7 +707,7 @@ namespace connection {
|
|||
const target_own = target_client_id === this.connection.client.getClientId();
|
||||
|
||||
if(target_own && target_client_id === json["invokerid"]) {
|
||||
console.error(tr("Received conversation message from invalid client id. Data: %o", json));
|
||||
log.error(LogCategory.NETWORKING, tr("Received conversation message from invalid client id. Data: %o", json));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -700,7 +721,7 @@ namespace connection {
|
|||
attach: target_own
|
||||
});
|
||||
if(!conversation) {
|
||||
console.error(tr("Received conversation message for unknown conversation! (%s)"), target_own ? tr("Remote message") : tr("Own message"));
|
||||
log.error(LogCategory.NETWORKING, tr("Received conversation message for unknown conversation! (%s)"), target_own ? tr("Remote message") : tr("Own message"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -764,6 +785,24 @@ namespace connection {
|
|||
}
|
||||
}
|
||||
|
||||
notifyClientChatComposing(json) {
|
||||
json = json[0];
|
||||
|
||||
const conversation_manager = this.connection_handler.side_bar.private_conversations();
|
||||
const conversation = conversation_manager.find_conversation({
|
||||
client_id: parseInt(json["clid"]),
|
||||
unique_id: json["cluid"],
|
||||
name: undefined
|
||||
}, {
|
||||
create: false,
|
||||
attach: false
|
||||
});
|
||||
if(!conversation)
|
||||
return;
|
||||
|
||||
conversation.trigger_typing();
|
||||
}
|
||||
|
||||
handleNotifyClientChatClosed(json) {
|
||||
json = json[0]; //Only one bulk
|
||||
|
||||
|
@ -793,7 +832,7 @@ namespace connection {
|
|||
|
||||
let client = this.connection.client.channelTree.findClient(json["clid"]);
|
||||
if(!client) {
|
||||
console.error(tr("Tried to update an non existing client"));
|
||||
log.error(LogCategory.NETWORKING, tr("Tried to update an non existing client"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -806,8 +845,6 @@ namespace connection {
|
|||
updates.push({key: key, value: json[key]});
|
||||
}
|
||||
client.updateVariables(...updates);
|
||||
if(this.connection.client.select_info.currentSelected == client)
|
||||
this.connection.client.select_info.update();
|
||||
}
|
||||
|
||||
handleNotifyServerEdited(json) {
|
||||
|
@ -826,8 +863,6 @@ namespace connection {
|
|||
updates.push({key: key, value: json[key]});
|
||||
}
|
||||
this.connection.client.channelTree.server.updateVariables(false, ...updates);
|
||||
if(this.connection.client.select_info.currentSelected == this.connection.client.channelTree.server)
|
||||
this.connection.client.select_info.update();
|
||||
}
|
||||
|
||||
handleNotifyServerUpdated(json) {
|
||||
|
@ -846,9 +881,6 @@ namespace connection {
|
|||
updates.push({key: key, value: json[key]});
|
||||
}
|
||||
this.connection.client.channelTree.server.updateVariables(true, ...updates);
|
||||
let info = this.connection.client.select_info;
|
||||
if(info.currentSelected instanceof ServerEntry)
|
||||
info.update();
|
||||
}
|
||||
|
||||
handleNotifyMusicPlayerInfo(json) {
|
||||
|
|
1152
shared/js/load.ts
1152
shared/js/load.ts
File diff suppressed because it is too large
Load Diff
|
@ -2,14 +2,17 @@ enum LogCategory {
|
|||
CHANNEL,
|
||||
CHANNEL_PROPERTIES, /* separating channel and channel properties because on channel init logging is a big bottleneck */
|
||||
CLIENT,
|
||||
BOOKMARKS,
|
||||
SERVER,
|
||||
PERMISSIONS,
|
||||
GENERAL,
|
||||
NETWORKING,
|
||||
VOICE,
|
||||
AUDIO,
|
||||
I18N,
|
||||
IPC,
|
||||
IDENTITIES
|
||||
IDENTITIES,
|
||||
STATISTICS
|
||||
}
|
||||
|
||||
namespace log {
|
||||
|
@ -26,13 +29,16 @@ namespace log {
|
|||
[LogCategory.CHANNEL_PROPERTIES, "Channel "],
|
||||
[LogCategory.CLIENT, "Client "],
|
||||
[LogCategory.SERVER, "Server "],
|
||||
[LogCategory.BOOKMARKS, "Bookmark "],
|
||||
[LogCategory.PERMISSIONS, "Permission "],
|
||||
[LogCategory.GENERAL, "General "],
|
||||
[LogCategory.NETWORKING, "Network "],
|
||||
[LogCategory.VOICE, "Voice "],
|
||||
[LogCategory.AUDIO, "Audio "],
|
||||
[LogCategory.I18N, "I18N "],
|
||||
[LogCategory.IDENTITIES, "IDENTITIES "],
|
||||
[LogCategory.IPC, "IPC "]
|
||||
[LogCategory.IDENTITIES, "Identities "],
|
||||
[LogCategory.IPC, "IPC "],
|
||||
[LogCategory.STATISTICS, "Statistics "]
|
||||
]);
|
||||
|
||||
export let enabled_mapping = new Map<number, boolean>([
|
||||
|
@ -40,13 +46,25 @@ namespace log {
|
|||
[LogCategory.CHANNEL_PROPERTIES, false],
|
||||
[LogCategory.CLIENT, true],
|
||||
[LogCategory.SERVER, true],
|
||||
[LogCategory.BOOKMARKS, true],
|
||||
[LogCategory.PERMISSIONS, true],
|
||||
[LogCategory.GENERAL, true],
|
||||
[LogCategory.NETWORKING, true],
|
||||
[LogCategory.VOICE, true],
|
||||
[LogCategory.AUDIO, true],
|
||||
[LogCategory.I18N, false],
|
||||
[LogCategory.IDENTITIES, true],
|
||||
[LogCategory.IPC, true]
|
||||
[LogCategory.IPC, true],
|
||||
[LogCategory.STATISTICS, true]
|
||||
]);
|
||||
|
||||
//Values will be overridden by initialize()
|
||||
export let level_mapping = new Map<LogType, boolean>([
|
||||
[LogType.TRACE, true],
|
||||
[LogType.DEBUG, true],
|
||||
[LogType.INFO, true],
|
||||
[LogType.WARNING, true],
|
||||
[LogType.ERROR, true]
|
||||
]);
|
||||
|
||||
enum GroupMode {
|
||||
|
@ -55,22 +73,36 @@ namespace log {
|
|||
}
|
||||
const group_mode: GroupMode = GroupMode.PREFIX;
|
||||
|
||||
loader.register_task(loader.Stage.LOADED, {
|
||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||
name: "log enabled initialisation",
|
||||
function: async () => initialize(),
|
||||
priority: 10
|
||||
priority: 150
|
||||
});
|
||||
|
||||
//Example: <url>?log.i18n.enabled=0
|
||||
//Category Example: <url>?log.i18n.enabled=0
|
||||
//Level Example A: <url>?log.level.trace.enabled=0
|
||||
//Level Example B: <url>?log.level=0
|
||||
export function initialize() {
|
||||
for(const category of Object.keys(LogCategory).map(e => parseInt(e))) {
|
||||
if(isNaN(category)) continue;
|
||||
const category_name = LogCategory[category];
|
||||
enabled_mapping[category] = settings.static_global<boolean>("log." + category_name.toLowerCase() + ".enabled", enabled_mapping.get(category));
|
||||
const category_name = LogCategory[category].toLowerCase();
|
||||
enabled_mapping.set(category, settings.static_global<boolean>("log." + category_name.toLowerCase() + ".enabled", enabled_mapping.get(category)));
|
||||
}
|
||||
|
||||
const base_level = settings.static_global<number>("log.level", app.type === app.Type.CLIENT_DEBUG || app.type === app.Type.WEB_DEBUG ? LogType.TRACE : LogType.INFO);
|
||||
|
||||
for(const level of Object.keys(LogType).map(e => parseInt(e))) {
|
||||
if(isNaN(level)) continue;
|
||||
|
||||
const level_name = LogType[level].toLowerCase();
|
||||
level_mapping.set(level, settings.static_global<boolean>("log." + level_name + ".enabled", level >= base_level));
|
||||
}
|
||||
}
|
||||
|
||||
function logDirect(type: LogType, message: string, ...optionalParams: any[]) {
|
||||
if(!level_mapping.get(type))
|
||||
return;
|
||||
|
||||
switch (type) {
|
||||
case LogType.TRACE:
|
||||
case LogType.DEBUG:
|
||||
|
@ -86,11 +118,10 @@ namespace log {
|
|||
console.error(message, ...optionalParams);
|
||||
break;
|
||||
}
|
||||
//console.log("This is %cMy stylish message", "color: yellow; font-style: italic; background-color: blue;padding: 2px");
|
||||
}
|
||||
|
||||
export function log(type: LogType, category: LogCategory, message: string, ...optionalParams: any[]) {
|
||||
if(!enabled_mapping[category]) return;
|
||||
if(!enabled_mapping.get(category)) return;
|
||||
|
||||
optionalParams.unshift(category_mapping.get(category));
|
||||
message = "[%s] " + message;
|
||||
|
@ -124,13 +155,15 @@ namespace log {
|
|||
return new Group(group_mode, level, category, name, optionalParams);
|
||||
}
|
||||
|
||||
export function table(title: string, arguments: any) {
|
||||
export function table(level: LogType, category: LogCategory, title: string, arguments: any) {
|
||||
if(group_mode == GroupMode.NATIVE) {
|
||||
console.groupCollapsed(title);
|
||||
console.table(arguments);
|
||||
console.groupEnd();
|
||||
} else {
|
||||
console.log("Snipped table %s", title);
|
||||
if(!enabled_mapping.get(category) || !level_mapping.get(level))
|
||||
return;
|
||||
logDirect(level, tr("Snipped table \"%s\""), title);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,7 +187,7 @@ namespace log {
|
|||
this.category = category;
|
||||
this.name = name;
|
||||
this.optionalParams = optionalParams;
|
||||
this.enabled = enabled_mapping[category];
|
||||
this.enabled = enabled_mapping.get(category);
|
||||
}
|
||||
|
||||
group(level: LogType, name: string, ...optionalParams: any[]) : Group {
|
||||
|
@ -190,8 +223,9 @@ namespace log {
|
|||
}
|
||||
if(this.mode == GroupMode.NATIVE)
|
||||
logDirect(this.level, message, ...optionalParams);
|
||||
else
|
||||
logDirect(this.level, this._log_prefix + message, ...optionalParams);
|
||||
else {
|
||||
logDirect(this.level, "[%s] " + this._log_prefix + message, category_mapping.get(this.category), ...optionalParams);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,11 +84,11 @@ function setup_close() {
|
|||
declare function moment(...arguments) : any;
|
||||
function setup_jsrender() : boolean {
|
||||
if(!js_render) {
|
||||
displayCriticalError("Missing jsrender extension!");
|
||||
loader.critical_error("Missing jsrender extension!");
|
||||
return false;
|
||||
}
|
||||
if(!js_render.views) {
|
||||
displayCriticalError("Missing jsrender viewer extension!");
|
||||
loader.critical_error("Missing jsrender viewer extension!");
|
||||
return false;
|
||||
}
|
||||
js_render.views.settings.allowCode(true);
|
||||
|
@ -108,10 +108,10 @@ function setup_jsrender() : boolean {
|
|||
});
|
||||
|
||||
$(".jsrender-template").each((idx, _entry) => {
|
||||
if(!js_render.templates(_entry.id, _entry.innerHTML)) { //, _entry.innerHTML
|
||||
console.error("Failed to cache template " + _entry.id + " for js render!");
|
||||
if(!js_render.templates(_entry.id, _entry.innerHTML)) {
|
||||
log.error(LogCategory.GENERAL, tr("Failed to setup cache for js renderer template %s!"), _entry.id);
|
||||
} else
|
||||
console.debug("Successfully loaded jsrender template " + _entry.id);
|
||||
log.info(LogCategory.GENERAL, tr("Successfully loaded jsrender template %s"), _entry.id);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ async function initialize() {
|
|||
await i18n.initialize();
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to initialized the translation system!\nError: %o"), error);
|
||||
displayCriticalError("Failed to setup the translation system");
|
||||
loader.critical_error("Failed to setup the translation system");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -131,13 +131,6 @@ async function initialize() {
|
|||
}
|
||||
|
||||
async function initialize_app() {
|
||||
const display_load_error = message => {
|
||||
if(typeof(display_critical_load) !== "undefined")
|
||||
display_critical_load(message);
|
||||
else
|
||||
displayCriticalError(message);
|
||||
};
|
||||
|
||||
try { //Initialize main template
|
||||
const main = $("#tmpl_main").renderTag({
|
||||
multi_session: !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION),
|
||||
|
@ -146,8 +139,8 @@ async function initialize_app() {
|
|||
|
||||
$("body").append(main);
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
display_load_error(tr("Failed to setup main page!"));
|
||||
log.error(LogCategory.GENERAL, error);
|
||||
loader.critical_error(tr("Failed to setup main page!"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -158,7 +151,7 @@ async function initialize_app() {
|
|||
if(audio.player.set_master_volume)
|
||||
audio.player.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER) / 100);
|
||||
else
|
||||
console.warn("Client does not support audio.player.set_master_volume()... May client is too old?");
|
||||
log.warn(LogCategory.GENERAL, tr("Client does not support audio.player.set_master_volume()... May client is too old?"));
|
||||
if(audio.recorder.device_refresh_available())
|
||||
await audio.recorder.refresh_devices();
|
||||
|
||||
|
@ -166,7 +159,7 @@ async function initialize_app() {
|
|||
await default_recorder.initialize();
|
||||
|
||||
sound.initialize().then(() => {
|
||||
console.log(tr("Sounds initialitzed"));
|
||||
log.info(LogCategory.AUDIO, tr("Sounds initialized"));
|
||||
});
|
||||
sound.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER_SOUNDS) / 100);
|
||||
|
||||
|
@ -175,8 +168,8 @@ async function initialize_app() {
|
|||
try {
|
||||
await ppt.initialize();
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to initialize ppt!\nError: %o"), error);
|
||||
displayCriticalError(tr("Failed to initialize ppt!"));
|
||||
log.error(LogCategory.GENERAL, tr("Failed to initialize ppt!\nError: %o"), error);
|
||||
loader.critical_error(tr("Failed to initialize ppt!"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -274,15 +267,15 @@ class TestProxy extends bipc.MethodProxy {
|
|||
}
|
||||
|
||||
protected on_connected() {
|
||||
console.log("Test proxy connected");
|
||||
log.info(LogCategory.IPC, "Test proxy connected");
|
||||
}
|
||||
|
||||
protected on_disconnected() {
|
||||
console.log("Test proxy disconnected");
|
||||
log.info(LogCategory.IPC, "Test proxy disconnected");
|
||||
}
|
||||
|
||||
private async say_hello() : Promise<void> {
|
||||
console.log("Hello World");
|
||||
log.info(LogCategory.IPC, "Hello World");
|
||||
}
|
||||
|
||||
private async add_slave(a: number, b: number) : Promise<number> {
|
||||
|
@ -363,7 +356,7 @@ function main() {
|
|||
volatile_collection_only: false
|
||||
});
|
||||
stats.register_user_count_listener(status => {
|
||||
console.log("Received user count update: %o", status);
|
||||
log.info(LogCategory.STATISTICS, tr("Received user count update: %o"), status);
|
||||
});
|
||||
|
||||
(<any>window).test_upload = (message?: string) => {
|
||||
|
@ -377,7 +370,6 @@ function main() {
|
|||
name: '/HelloWorld.txt',
|
||||
path: ''
|
||||
}).then(key => {
|
||||
console.log("Got key: %o", key);
|
||||
const upload = new RequestFileUpload(key);
|
||||
|
||||
const buffer = new Uint8Array(message.length);
|
||||
|
@ -396,7 +388,6 @@ function main() {
|
|||
|
||||
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);
|
||||
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");
|
||||
|
@ -431,9 +422,10 @@ function main() {
|
|||
|
||||
});
|
||||
*/
|
||||
// Modals.openServerInfo(connection.channelTree.server);
|
||||
//Modals.createServerModal(connection.channelTree.server, properties => Promise.resolve());
|
||||
}, 1000);
|
||||
//Modals.spawnSettingsModal("audio-sounds");
|
||||
Modals.spawnSettingsModal("identity-profiles");
|
||||
//Modals.spawnKeySelect(console.log);
|
||||
}
|
||||
|
||||
|
@ -454,7 +446,7 @@ const task_teaweb_starter: loader.Task = {
|
|||
console.error(ex.stack);
|
||||
if(ex instanceof ReferenceError || ex instanceof TypeError)
|
||||
ex = ex.name + ": " + ex.message;
|
||||
displayCriticalError("Failed to invoke main function:<br>" + ex);
|
||||
loader.critical_error("Failed to invoke main function:<br>" + ex);
|
||||
}
|
||||
},
|
||||
priority: 10
|
||||
|
@ -519,7 +511,7 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|||
if(!setup_jsrender())
|
||||
throw "invalid load";
|
||||
} catch (error) {
|
||||
displayCriticalError(tr("Failed to setup jsrender"));
|
||||
loader.critical_error(tr("Failed to setup jsrender"));
|
||||
console.error(tr("Failed to load jsrender! %o"), error);
|
||||
return;
|
||||
}
|
||||
|
@ -543,7 +535,7 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|||
|
||||
if(ex instanceof ReferenceError || ex instanceof TypeError)
|
||||
ex = ex.name + ": " + ex.message;
|
||||
displayCriticalError("Failed to boot app function:<br>" + ex);
|
||||
loader.critical_error("Failed to boot app function:<br>" + ex);
|
||||
}
|
||||
},
|
||||
priority: 1000
|
||||
|
|
|
@ -51,7 +51,6 @@ class Group {
|
|||
|
||||
if(key == "iconid") {
|
||||
this.properties.iconid = (new Uint32Array([this.properties.iconid]))[0];
|
||||
console.log("Icon id " + this.properties.iconid);
|
||||
this.handle.handle.channelTree.clientsByGroup(this).forEach(client => {
|
||||
client.updateGroupIcon(this);
|
||||
});
|
||||
|
@ -139,7 +138,7 @@ class GroupManager extends connection.AbstractCommandHandler {
|
|||
if(json[0]["sgid"]) target = GroupTarget.SERVER;
|
||||
else if(json[0]["cgid"]) target = GroupTarget.CHANNEL;
|
||||
else {
|
||||
console.error(tr("Could not resolve group target! => %o"), json[0]);
|
||||
log.error(LogCategory.CLIENT, tr("Could not resolve group target! => %o"), json[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -155,8 +154,7 @@ class GroupManager extends connection.AbstractCommandHandler {
|
|||
case 1: type = GroupType.NORMAL; break;
|
||||
case 2: type = GroupType.QUERY; break;
|
||||
default:
|
||||
//TODO tr
|
||||
console.error("Invalid group type: " + groupData["type"] + " for group " + groupData["name"]);
|
||||
log.error(LogCategory.CLIENT, tr("Invalid group type: %o for group %s"), groupData["type"],groupData["name"]);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -180,7 +178,6 @@ class GroupManager extends connection.AbstractCommandHandler {
|
|||
this.channelGroups.push(group);
|
||||
}
|
||||
|
||||
console.log("Got " + json.length + " new " + target + " groups:");
|
||||
for(const client of this.handle.channelTree.clients)
|
||||
client.update_displayed_client_groups();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
/// <reference path="../ConnectionHandler.ts" />
|
||||
/// <reference path="../connection/ConnectionBase.ts" />
|
||||
|
||||
import LogType = log.LogType;
|
||||
|
||||
enum PermissionType {
|
||||
B_SERVERINSTANCE_HELP_VIEW = "b_serverinstance_help_view", /* Permission ID: 1 */
|
||||
B_SERVERINSTANCE_VERSION_VIEW = "b_serverinstance_version_view", /* Permission ID: 2 */
|
||||
|
@ -601,7 +603,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
|||
"description": perm.description
|
||||
});
|
||||
}
|
||||
log.table("Permission list", table_entries);
|
||||
log.table(LogType.DEBUG, LogCategory.PERMISSIONS, "Permission list", table_entries);
|
||||
group.end();
|
||||
|
||||
log.info(LogCategory.PERMISSIONS, tr("Got %i permissions"), this.permissionList.length);
|
||||
|
@ -658,7 +660,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
|||
});
|
||||
}
|
||||
|
||||
log.table("Needed client permissions", table_entries);
|
||||
log.table(LogType.DEBUG, LogCategory.PERMISSIONS, "Needed client permissions", table_entries);
|
||||
group.end();
|
||||
|
||||
log.debug(LogCategory.PERMISSIONS, tr("Dropping %o needed permissions and added %o permissions."), copy.length, addcount);
|
||||
|
|
|
@ -201,7 +201,7 @@ namespace sound {
|
|||
|
||||
manager = new SoundManager(undefined);
|
||||
audio.player.on_ready(reinitialisize_audio);
|
||||
return new Promise<void>(resolve => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
$.ajax({
|
||||
url: "audio/speech/mapping.json",
|
||||
success: response => {
|
||||
|
@ -211,9 +211,9 @@ namespace sound {
|
|||
register_sound(entry.key, "speech/" + entry.file);
|
||||
resolve();
|
||||
},
|
||||
error: () => {
|
||||
console.log("error!");
|
||||
console.dir(...arguments);
|
||||
error: error => {
|
||||
log.error(LogCategory.AUDIO, "error: %o", error);
|
||||
reject();
|
||||
},
|
||||
timeout: 5000,
|
||||
async: true,
|
||||
|
@ -254,19 +254,17 @@ namespace sound {
|
|||
if(context.decodeAudioData) {
|
||||
if(!file.cached) {
|
||||
const decode_data = buffer => {
|
||||
console.log(buffer);
|
||||
try {
|
||||
console.log(tr("Decoding data"));
|
||||
log.info(LogCategory.AUDIO, tr("Decoding data"));
|
||||
context.decodeAudioData(buffer, result => {
|
||||
file.cached = result;
|
||||
}, error => {
|
||||
console.error(tr("Failed to decode audio data for %o"), sound);
|
||||
console.error(error);
|
||||
log.error(LogCategory.AUDIO, tr("Failed to decode audio data for %o: %o"), sound, error);
|
||||
file.not_supported = true;
|
||||
file.not_supported_timeout = Date.now() + 1000 * 60 * 2; //Try in 2min again!
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
log.error(LogCategory.AUDIO, error);
|
||||
file.not_supported = true;
|
||||
file.not_supported_timeout = Date.now() + 1000 * 60 * 2; //Try in 2min again!
|
||||
}
|
||||
|
@ -288,15 +286,15 @@ namespace sound {
|
|||
if (xhr.status != 200)
|
||||
throw "invalid response code (" + xhr.status + ")";
|
||||
|
||||
console.log(tr("Decoding data"));
|
||||
log.debug(LogCategory.AUDIO, tr("Decoding data"));
|
||||
try {
|
||||
file.cached = await context.decodeAudioData(xhr.response);
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
log.error(LogCategory.AUDIO, error);
|
||||
throw "failed to decode audio data";
|
||||
}
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to load audio file %s. Error: %o"), sound, error);
|
||||
log.error(LogCategory.AUDIO, tr("Failed to load audio file %s. Error: %o"), sound, error);
|
||||
file.not_supported = true;
|
||||
file.not_supported_timeout = Date.now() + 1000 * 60 * 2; //Try in 2min again!
|
||||
}
|
||||
|
@ -305,7 +303,7 @@ namespace sound {
|
|||
if(!file.node) {
|
||||
if(!warned) {
|
||||
warned = true;
|
||||
console.warn(tr("Your browser does not support decodeAudioData! Using a node to playback! This bypasses the audio output and volume regulation!"));
|
||||
log.warn(LogCategory.AUDIO, tr("Your browser does not support decodeAudioData! Using a node to playback! This bypasses the audio output and volume regulation!"));
|
||||
}
|
||||
const container = $("#sounds");
|
||||
const node = $.spawn("audio").attr("src", path);
|
||||
|
@ -332,7 +330,7 @@ namespace sound {
|
|||
options = options || {};
|
||||
|
||||
const volume = get_sound_volume(_sound, options.default_volume);
|
||||
console.log(tr("Replaying sound %s (Sound volume: %o | Master volume %o)"), _sound, volume, master_volume);
|
||||
log.info(LogCategory.AUDIO, tr("Replaying sound %s (Sound volume: %o | Master volume %o)"), _sound, volume, master_volume);
|
||||
|
||||
if(volume == 0 || master_volume == 0)
|
||||
return;
|
||||
|
@ -342,7 +340,7 @@ namespace sound {
|
|||
|
||||
const context = audio.player.context();
|
||||
if(!context) {
|
||||
console.warn(tr("Tried to replay a sound without an audio context (Sound: %o). Dropping playback"), _sound);
|
||||
log.warn(LogCategory.AUDIO, tr("Tried to replay a sound without an audio context (Sound: %o). Dropping playback"), _sound);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -351,7 +349,7 @@ namespace sound {
|
|||
return;
|
||||
|
||||
if(!options.ignore_overlap && (this._playing_sounds[_sound] > 0) && !sound.overlap_activated()) {
|
||||
console.log(tr("Dropping requested playback for sound %s because it would overlap."), _sound);
|
||||
log.info(LogCategory.AUDIO, tr("Dropping requested playback for sound %s because it would overlap."), _sound);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -387,18 +385,18 @@ namespace sound {
|
|||
if(options.callback)
|
||||
options.callback(true);
|
||||
}).catch(error => {
|
||||
console.warn(tr("Sound playback for sound %o resulted in an error: %o"), sound, error);
|
||||
log.warn(LogCategory.AUDIO, tr("Sound playback for sound %o resulted in an error: %o"), sound, error);
|
||||
if(options.callback)
|
||||
options.callback(false);
|
||||
});
|
||||
} else {
|
||||
console.warn(tr("Failed to replay sound %o because of missing handles."), sound);
|
||||
log.warn(LogCategory.AUDIO, tr("Failed to replay sound %o because of missing handles."), sound);
|
||||
if(options.callback)
|
||||
options.callback(false);
|
||||
return;
|
||||
}
|
||||
}).catch(error => {
|
||||
console.warn(tr("Failed to replay sound %o because it could not be resolved: %o"), sound, error);
|
||||
log.warn(LogCategory.AUDIO, tr("Failed to replay sound %o because it could not be resolved: %o"), sound, error);
|
||||
if(options.callback)
|
||||
options.callback(false);
|
||||
});
|
||||
|
|
|
@ -73,7 +73,7 @@ namespace stats {
|
|||
export function initialize(config: Config) {
|
||||
current_config = initialize_config_object(config || {}, DEFAULT_CONFIG);
|
||||
if(current_config.verbose)
|
||||
console.log(LOG_PREFIX + tr("Initializing statistics with this config: %o"), current_config);
|
||||
log.info(LogCategory.STATISTICS, tr("Initializing statistics with this config: %o"), current_config);
|
||||
|
||||
connection.start_connection();
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ namespace stats {
|
|||
if(connection_copy !== connection) return;
|
||||
|
||||
if(current_config.verbose)
|
||||
console.log(LOG_PREFIX + tr("Lost connection to statistics server (Connection closed). Reason: %o. Event object: %o"), CloseCodes[event.code] || event.code, event);
|
||||
log.warn(LogCategory.STATISTICS, tr("Lost connection to statistics server (Connection closed). Reason: %o. Event object: %o"), CloseCodes[event.code] || event.code, event);
|
||||
|
||||
if(event.code != CloseCodes.BANNED)
|
||||
invoke_reconnect();
|
||||
|
@ -120,7 +120,7 @@ namespace stats {
|
|||
if(connection_copy !== connection) return;
|
||||
|
||||
if(current_config.verbose)
|
||||
console.log(LOG_PREFIX + tr("Successfully connected to server. Initializing session."));
|
||||
log.info(LogCategory.STATISTICS, tr("Successfully connected to server. Initializing session."));
|
||||
|
||||
connection_state = ConnectionState.INITIALIZING;
|
||||
initialize_session();
|
||||
|
@ -130,7 +130,7 @@ namespace stats {
|
|||
if(connection_copy !== connection) return;
|
||||
|
||||
if(current_config.verbose)
|
||||
console.log(LOG_PREFIX + tr("Received an error. Closing connection. Object: %o"), event);
|
||||
log.warn(LogCategory.STATISTICS, tr("Received an error. Closing connection. Object: %o"), event);
|
||||
|
||||
connection.close(CloseCodes.INTERNAL_ERROR);
|
||||
invoke_reconnect();
|
||||
|
@ -141,7 +141,7 @@ namespace stats {
|
|||
|
||||
if(typeof(event.data) !== 'string') {
|
||||
if(current_config.verbose)
|
||||
console.warn(LOG_PREFIX + tr("Received an message which isn't a string. Event object: %o"), event);
|
||||
log.info(LogCategory.STATISTICS, tr("Received an message which isn't a string. Event object: %o"), event);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -170,11 +170,11 @@ namespace stats {
|
|||
}
|
||||
|
||||
if(current_config.verbose)
|
||||
console.log(LOG_PREFIX + tr("Scheduled reconnect in %dms"), current_config.reconnect_interval);
|
||||
log.info(LogCategory.STATISTICS, tr("Scheduled reconnect in %dms"), current_config.reconnect_interval);
|
||||
|
||||
reconnect_timer = setTimeout(() => {
|
||||
if(current_config.verbose)
|
||||
console.log(LOG_PREFIX + tr("Reconnecting"));
|
||||
log.info(LogCategory.STATISTICS, tr("Reconnecting"));
|
||||
start_connection();
|
||||
}, current_config.reconnect_interval);
|
||||
}
|
||||
|
@ -212,10 +212,10 @@ namespace stats {
|
|||
|
||||
if(typeof(handler[type]) === 'function') {
|
||||
if(current_config.verbose)
|
||||
console.debug(LOG_PREFIX + tr("Handling message of type %s"), type);
|
||||
log.debug(LogCategory.STATISTICS, tr("Handling message of type %s"), type);
|
||||
handler[type](data);
|
||||
} else if(current_config.verbose) {
|
||||
console.warn(LOG_PREFIX + tr("Received message with an unknown type (%s). Dropping message. Full message: %o"), type, data_object);
|
||||
log.warn(LogCategory.STATISTICS, tr("Received message with an unknown type (%s). Dropping message. Full message: %o"), type, data_object);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,7 +231,7 @@ namespace stats {
|
|||
interface NotifyInitialized {}
|
||||
function handle_notify_initialized(json: NotifyInitialized) {
|
||||
if(current_config.verbose)
|
||||
console.log(LOG_PREFIX + tr("Session successfully initialized."));
|
||||
log.info(LogCategory.STATISTICS, tr("Session successfully initialized."));
|
||||
|
||||
connection_state = ConnectionState.CONNECTED;
|
||||
}
|
||||
|
|
|
@ -393,8 +393,9 @@ class ChannelEntry {
|
|||
for(let index = 0; index + 1 < clients.length; index++)
|
||||
clients[index].tag.before(clients[index + 1].tag);
|
||||
|
||||
log.debug(LogCategory.CHANNEL, tr("Reordered channel clients: %d"), clients.length);
|
||||
for(let client of clients) {
|
||||
console.log("- %i %s", client.properties.client_talk_power, client.properties.client_nickname);
|
||||
log.debug(LogCategory.CHANNEL, "- %i %s", client.properties.client_talk_power, client.properties.client_nickname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -624,6 +625,7 @@ class ChannelEntry {
|
|||
}
|
||||
|
||||
handle_frame_resized() {
|
||||
if(this._channel_name_formatted === "align-repetitive")
|
||||
this.__updateChannelName();
|
||||
}
|
||||
|
||||
|
@ -631,16 +633,15 @@ class ChannelEntry {
|
|||
private __updateChannelName() {
|
||||
this._channel_name_formatted = undefined;
|
||||
|
||||
parseType:
|
||||
parse_type:
|
||||
if(this.parent_channel() == null && this.properties.channel_name.charAt(0) == '[') {
|
||||
let end = this.properties.channel_name.indexOf(']');
|
||||
if(end == -1) break parseType;
|
||||
if(end == -1) break parse_type;
|
||||
|
||||
let options = this.properties.channel_name.substr(1, end - 1);
|
||||
if(options.indexOf("spacer") == -1) break parseType;
|
||||
if(options.indexOf("spacer") == -1) break parse_type;
|
||||
options = options.substr(0, options.indexOf("spacer"));
|
||||
|
||||
console.log(tr("Channel options: '%o'"), options);
|
||||
if(options.length == 0)
|
||||
options = "l";
|
||||
else if(options.length > 1)
|
||||
|
@ -661,11 +662,10 @@ class ChannelEntry {
|
|||
break;
|
||||
default:
|
||||
this._channel_name_alignment = undefined;
|
||||
break parseType;
|
||||
break parse_type;
|
||||
}
|
||||
|
||||
this._channel_name_formatted = this.properties.channel_name.substr(end + 1) || "";
|
||||
console.log(tr("Got formated channel name: %o"), this._channel_name_formatted);
|
||||
}
|
||||
|
||||
this._tag_channel.find(".show-channel-normal-only").toggleClass("channel-normal", this._channel_name_formatted === undefined);
|
||||
|
@ -696,14 +696,13 @@ class ChannelEntry {
|
|||
index = 63;
|
||||
} while (tag_name.parent().width() >= tag_name.width() && ++index < 64);
|
||||
if(index == 64)
|
||||
console.warn(LogCategory.CHANNEL, tr("Repeating spacer took too much repeats!"));
|
||||
log.warn(LogCategory.CHANNEL, tr("Repeating spacer took too much repeats!"));
|
||||
if(lastSuccess.length > 0) {
|
||||
tag_name.text(lastSuccess);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(tr("Align: %s"), this._channel_name_alignment);
|
||||
}
|
||||
|
||||
recalculate_repetitive_name() {
|
||||
|
@ -722,7 +721,7 @@ class ChannelEntry {
|
|||
value: variable.value,
|
||||
type: typeof (this.properties[variable.key])
|
||||
});
|
||||
log.table("Clannel update properties", entries);
|
||||
log.table(LogType.DEBUG, LogCategory.PERMISSIONS, "Clannel update properties", entries);
|
||||
}
|
||||
|
||||
let info_update = false;
|
||||
|
|
|
@ -25,6 +25,8 @@ class ClientProperties {
|
|||
|
||||
client_channel_group_id: number = 0;
|
||||
client_lastconnected: number = 0;
|
||||
client_created: number = 0;
|
||||
client_totalconnections: number = 0;
|
||||
|
||||
client_flag_avatar: string = "";
|
||||
client_icon_id: number = 0;
|
||||
|
@ -44,9 +46,68 @@ class ClientProperties {
|
|||
client_teaforo_name: string = "";
|
||||
client_teaforo_flags: number = 0; /* 0x01 := Banned | 0x02 := Stuff | 0x04 := Premium */
|
||||
|
||||
|
||||
/* not updated in view! */
|
||||
client_month_bytes_uploaded: number = 0;
|
||||
client_month_bytes_downloaded: number = 0;
|
||||
client_total_bytes_uploaded: number = 0;
|
||||
client_total_bytes_downloaded: number = 0;
|
||||
|
||||
client_talk_power: number = 0;
|
||||
}
|
||||
|
||||
class ClientConnectionInfo {
|
||||
connection_bandwidth_received_last_minute_control: number = -1;
|
||||
connection_bandwidth_received_last_minute_keepalive: number = -1;
|
||||
connection_bandwidth_received_last_minute_speech: number = -1;
|
||||
connection_bandwidth_received_last_second_control: number = -1;
|
||||
connection_bandwidth_received_last_second_keepalive: number = -1;
|
||||
connection_bandwidth_received_last_second_speech: number = -1;
|
||||
|
||||
connection_bandwidth_sent_last_minute_control: number = -1;
|
||||
connection_bandwidth_sent_last_minute_keepalive: number = -1;
|
||||
connection_bandwidth_sent_last_minute_speech: number = -1;
|
||||
connection_bandwidth_sent_last_second_control: number = -1;
|
||||
connection_bandwidth_sent_last_second_keepalive: number = -1;
|
||||
connection_bandwidth_sent_last_second_speech: number = -1;
|
||||
|
||||
connection_bytes_received_control: number = -1;
|
||||
connection_bytes_received_keepalive: number = -1;
|
||||
connection_bytes_received_speech: number = -1;
|
||||
connection_bytes_sent_control: number = -1;
|
||||
connection_bytes_sent_keepalive: number = -1;
|
||||
connection_bytes_sent_speech: number = -1;
|
||||
|
||||
connection_packets_received_control: number = -1;
|
||||
connection_packets_received_keepalive: number = -1;
|
||||
connection_packets_received_speech: number = -1;
|
||||
|
||||
connection_packets_sent_control: number = -1;
|
||||
connection_packets_sent_keepalive: number = -1;
|
||||
connection_packets_sent_speech: number = -1;
|
||||
|
||||
connection_ping: number = -1;
|
||||
connection_ping_deviation: number = -1;
|
||||
|
||||
connection_server2client_packetloss_control: number = -1;
|
||||
connection_server2client_packetloss_keepalive: number = -1;
|
||||
connection_server2client_packetloss_speech: number = -1;
|
||||
connection_server2client_packetloss_total: number = -1;
|
||||
|
||||
connection_client2server_packetloss_speech: number = -1;
|
||||
connection_client2server_packetloss_keepalive: number = -1;
|
||||
connection_client2server_packetloss_control: number = -1;
|
||||
connection_client2server_packetloss_total: number = -1;
|
||||
|
||||
connection_filetransfer_bandwidth_sent: number = -1;
|
||||
connection_filetransfer_bandwidth_received: number = -1;
|
||||
|
||||
connection_connected_time: number = -1;
|
||||
connection_idle_time: number = -1;
|
||||
connection_client_ip: string | undefined;
|
||||
connection_client_port: number = -1;
|
||||
}
|
||||
|
||||
class ClientEntry {
|
||||
protected _clientId: number;
|
||||
protected _channel: ChannelEntry;
|
||||
|
@ -61,6 +122,14 @@ class ClientEntry {
|
|||
protected _audio_volume: number;
|
||||
protected _audio_muted: boolean;
|
||||
|
||||
private _info_variables_promise: Promise<void>;
|
||||
private _info_variables_promise_timestamp: number;
|
||||
|
||||
private _info_connection_promise: Promise<ClientConnectionInfo>;
|
||||
private _info_connection_promise_timestamp: number;
|
||||
private _info_connection_promise_resolve: any;
|
||||
private _info_connection_promise_reject: any;
|
||||
|
||||
channelTree: ChannelTree;
|
||||
|
||||
constructor(clientId: number, clientName, properties: ClientProperties = new ClientProperties()) {
|
||||
|
@ -176,10 +245,6 @@ class ClientEntry {
|
|||
}
|
||||
});
|
||||
|
||||
this.tag.on('click', event => {
|
||||
console.log("I've been clicked!");
|
||||
});
|
||||
|
||||
if(!(this instanceof LocalClientEntry) && !(this instanceof MusicClientEntry))
|
||||
this.tag.dblclick(event => {
|
||||
if($.isArray(this.channelTree.currently_selected)) { //Multiselect
|
||||
|
@ -524,8 +589,7 @@ class ClientEntry {
|
|||
this.channelTree.client.settings.changeServer("volume_client_" + this.clientUid(), volume);
|
||||
if(this._audio_handle)
|
||||
this._audio_handle.set_volume(volume);
|
||||
if(this.channelTree.client.select_info.currentSelected == this)
|
||||
this.channelTree.client.select_info.update();
|
||||
//TODO: Update in info
|
||||
});
|
||||
}
|
||||
}, {
|
||||
|
@ -710,7 +774,7 @@ class ClientEntry {
|
|||
value: variable.value,
|
||||
type: typeof (this.properties[variable.key])
|
||||
});
|
||||
log.table("Client update properties", entries);
|
||||
log.table(LogType.DEBUG, LogCategory.PERMISSIONS, "Client update properties", entries);
|
||||
}
|
||||
|
||||
for(const variable of variables) {
|
||||
|
@ -848,11 +912,17 @@ class ClientEntry {
|
|||
}
|
||||
}
|
||||
|
||||
updateClientVariables(){
|
||||
if(this.lastVariableUpdate == 0 || new Date().getTime() - 10 * 60 * 1000 > this.lastVariableUpdate){ //Cache these only 10 min
|
||||
this.lastVariableUpdate = new Date().getTime();
|
||||
this.channelTree.client.serverConnection.send_command("clientgetvariables", {clid: this.clientId()});
|
||||
}
|
||||
updateClientVariables(force_update?: boolean) : Promise<void> {
|
||||
if(Date.now() - 10 * 60 * 1000 < this._info_variables_promise_timestamp && this._info_variables_promise && (typeof(force_update) !== "boolean" || force_update))
|
||||
return this._info_variables_promise;
|
||||
|
||||
this._info_variables_promise_timestamp = Date.now();
|
||||
return (this._info_variables_promise = new Promise<void>((resolve, reject) => {
|
||||
this.channelTree.client.serverConnection.send_command("clientgetvariables", {clid: this.clientId()}).then(() => resolve()).catch(error => {
|
||||
this._info_connection_promise_timestamp = 0; /* not succeeded */
|
||||
reject(error);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
updateClientIcon() {
|
||||
|
@ -957,6 +1027,34 @@ class ClientEntry {
|
|||
client_id: this._clientId
|
||||
}
|
||||
}
|
||||
|
||||
/* max 1s ago, so we could update every second */
|
||||
request_connection_info() : Promise<ClientConnectionInfo> {
|
||||
if(Date.now() - 900 < this._info_connection_promise_timestamp && this._info_connection_promise)
|
||||
return this._info_connection_promise;
|
||||
|
||||
if(this._info_connection_promise_reject)
|
||||
this._info_connection_promise_resolve("timeout");
|
||||
|
||||
let _local_reject; /* to ensure we're using the right resolve! */
|
||||
this._info_connection_promise = new Promise<ClientConnectionInfo>((resolve, reject) => {
|
||||
this._info_connection_promise_resolve = resolve;
|
||||
this._info_connection_promise_reject = reject;
|
||||
_local_reject = reject;
|
||||
});
|
||||
|
||||
this._info_connection_promise_timestamp = Date.now();
|
||||
this.channelTree.client.serverConnection.send_command("getconnectioninfo", {clid: this._clientId}).catch(error => _local_reject(error));
|
||||
return this._info_connection_promise;
|
||||
}
|
||||
|
||||
set_connection_info(info: ClientConnectionInfo) {
|
||||
if(!this._info_connection_promise_resolve)
|
||||
return;
|
||||
this._info_connection_promise_resolve(info);
|
||||
this._info_connection_promise_resolve = undefined;
|
||||
this._info_connection_promise_reject = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class LocalClientEntry extends ClientEntry {
|
||||
|
@ -1243,8 +1341,6 @@ class MusicClientEntry extends ClientEntry {
|
|||
Modals.spawnChangeVolume(this._audio_handle.get_volume(), volume => {
|
||||
this.channelTree.client.settings.changeServer("volume_client_" + this.clientUid(), volume);
|
||||
this._audio_handle.set_volume(volume);
|
||||
if(this.channelTree.client.select_info.currentSelected == this)
|
||||
(<MusicInfoManager>this.channelTree.client.select_info.current_manager()).update_local_volume(volume);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -1265,8 +1361,7 @@ class MusicClientEntry extends ClientEntry {
|
|||
clid: this.clientId(),
|
||||
player_volume: value,
|
||||
}).then(() => {
|
||||
if(this.channelTree.client.select_info.currentSelected == this)
|
||||
(<MusicInfoManager>this.channelTree.client.select_info.current_manager()).update_remote_volume(value);
|
||||
//TODO: Update in info
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ class ClientMover {
|
|||
|
||||
this.selected_client = client;
|
||||
this.callback = callback;
|
||||
console.log(tr("Starting mouse move"));
|
||||
log.debug(LogCategory.GENERAL, tr("Starting mouse move"));
|
||||
|
||||
ClientMover.listener_root.on('mouseup', this._bound_finish = this.finish_listener.bind(this)).on('mousemove', this._bound_move = this.move_listener.bind(this));
|
||||
|
||||
|
@ -112,6 +112,7 @@ class ClientMover {
|
|||
|
||||
private finish_listener(event) {
|
||||
ClientMover.move_element.hide();
|
||||
log.debug(LogCategory.GENERAL, tr("Finishing mouse move"));
|
||||
|
||||
const channel_id = this.hovered_channel ? parseInt(this.hovered_channel.getAttribute("channel-id")) : 0;
|
||||
ClientMover.listener_root.unbind('mouseleave', this._bound_finish);
|
||||
|
|
|
@ -16,9 +16,18 @@ if(!$.fn.dividerfy) {
|
|||
const seperator_id = element.attr("seperator-id");
|
||||
const vertical = element.hasClass("vertical");
|
||||
|
||||
const apply_view = (property: string, previous: number, next: number) => {
|
||||
previous_element.css(property, "calc(" + previous + "% - " + (vertical ? element.width() : element.height()) + "px)");
|
||||
next_element.css(property, "calc(" +next + "% - " + (vertical ? element.width() : element.height()) + "px)");
|
||||
const apply_view = (property: "width" | "height", previous: number, next: number) => {
|
||||
const value_a = "calc(" + previous + "% - " + (vertical ? element.width() : element.height()) + "px)";
|
||||
const value_b = "calc(" + next + "% - " + (vertical ? element.width() : element.height()) + "px)";
|
||||
|
||||
/* dont cause a reflow here */
|
||||
if(property === "height") {
|
||||
(previous_element[0] as HTMLElement).style.height = value_a;
|
||||
(next_element[0] as HTMLElement).style.height = value_b;
|
||||
} else {
|
||||
(previous_element[0] as HTMLElement).style.width = value_a;
|
||||
(next_element[0] as HTMLElement).style.width = value_b;
|
||||
}
|
||||
};
|
||||
|
||||
const listener_move = (event: MouseEvent | TouchEvent) => {
|
||||
|
@ -105,12 +114,12 @@ if(!$.fn.dividerfy) {
|
|||
try {
|
||||
const config = JSON.parse(settings.global("seperator-settings-" + seperator_id));
|
||||
if(config) {
|
||||
console.log("Apply previous changed: %o", config);
|
||||
log.debug(LogCategory.GENERAL, tr("Applying previous changed sperator settings for %s: %o"), seperator_id, config);
|
||||
apply_view(config.property, config.previous, config.next);
|
||||
}
|
||||
} catch(e) {
|
||||
if(!(e instanceof SyntaxError))
|
||||
console.error(e);
|
||||
log.error(LogCategory.GENERAL, tr("Failed to parse seperator settings for sperator %s: %o"), seperator_id, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -80,14 +80,15 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
|
|||
private _global_click_listener: (event) => any;
|
||||
private _context_menu: JQuery;
|
||||
private _close_callbacks: (() => any)[] = [];
|
||||
private _visible = false;
|
||||
|
||||
despawn_context_menu() {
|
||||
let menu = this._context_menu || (this._context_menu = $(".context-menu"));
|
||||
|
||||
if(!menu.is(":visible"))
|
||||
if(!this._visible)
|
||||
return;
|
||||
|
||||
let menu = this._context_menu || (this._context_menu = $(".context-menu"));
|
||||
menu.animate({opacity: 0}, 100, () => menu.css("display", "none"));
|
||||
this._visible = false;
|
||||
for(const callback of this._close_callbacks) {
|
||||
if(typeof(callback) !== "function") {
|
||||
console.error(tr("Given close callback is not a function!. Callback: %o"), callback);
|
||||
|
@ -185,6 +186,8 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
|
|||
}
|
||||
|
||||
spawn_context_menu(x: number, y: number, ...entries: contextmenu.MenuEntry[]) {
|
||||
this._visible = true;
|
||||
|
||||
let menu_tag = this._context_menu || (this._context_menu = $(".context-menu"));
|
||||
menu_tag.finish().empty().css("opacity", "0");
|
||||
|
||||
|
|
|
@ -1,700 +0,0 @@
|
|||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../../../vendor/xbbcode/src/parser.ts" />
|
||||
|
||||
abstract class InfoManagerBase {
|
||||
private timers: NodeJS.Timer[] = [];
|
||||
private intervals: number[] = [];
|
||||
|
||||
protected resetTimers() {
|
||||
for(let timer of this.timers)
|
||||
clearTimeout(timer);
|
||||
this.timers = [];
|
||||
}
|
||||
|
||||
protected resetIntervals() {
|
||||
for(let interval of this.intervals)
|
||||
clearInterval(interval);
|
||||
this.intervals = [];
|
||||
}
|
||||
|
||||
protected registerTimer(timer: NodeJS.Timer) {
|
||||
this.timers.push(timer);
|
||||
}
|
||||
|
||||
protected registerInterval<T extends number | NodeJS.Timer>(interval: T) {
|
||||
this.intervals.push(interval as number);
|
||||
}
|
||||
|
||||
abstract available<V>(object: V) : boolean;
|
||||
}
|
||||
|
||||
abstract class InfoManager<T> extends InfoManagerBase {
|
||||
protected handle?: InfoBar<undefined>;
|
||||
|
||||
createFrame<_>(handle: InfoBar<_>, object: T, html_tag: JQuery<HTMLElement>) {
|
||||
this.handle = handle as InfoBar<undefined>;
|
||||
}
|
||||
|
||||
abstract updateFrame(object: T, html_tag: JQuery<HTMLElement>);
|
||||
|
||||
finalizeFrame(object: T, frame: JQuery<HTMLElement>) {
|
||||
this.resetIntervals();
|
||||
this.resetTimers();
|
||||
this.handle = undefined;
|
||||
}
|
||||
|
||||
protected triggerUpdate() {
|
||||
if(this.handle)
|
||||
this.handle.update();
|
||||
}
|
||||
}
|
||||
|
||||
class InfoBar<AvailableTypes = ServerEntry | ChannelEntry | ClientEntry | undefined> {
|
||||
readonly handle: ConnectionHandler;
|
||||
|
||||
private current_selected?: AvailableTypes;
|
||||
|
||||
private _tag: JQuery<HTMLElement>;
|
||||
private readonly _tag_info: JQuery<HTMLElement>;
|
||||
private readonly _tag_banner: JQuery<HTMLElement>;
|
||||
|
||||
private _current_manager: InfoManagerBase = undefined;
|
||||
private managers: InfoManagerBase[] = [];
|
||||
|
||||
constructor(client: ConnectionHandler) {
|
||||
this.handle = client;
|
||||
|
||||
this._tag = $("#tmpl_select_info").renderTag();
|
||||
this._tag_info = this._tag.find(".container-select-info");
|
||||
this._tag_banner = this._tag.find(".container-banner");
|
||||
|
||||
this.managers.push(new MusicInfoManager());
|
||||
this.managers.push(new ClientInfoManager());
|
||||
this.managers.push(new ChannelInfoManager());
|
||||
this.managers.push(new ServerInfoManager());
|
||||
|
||||
this._tag.find("button.close").on('click', () => this.close_popover());
|
||||
}
|
||||
|
||||
get_tag() : JQuery {
|
||||
return this._tag;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._tag && this._tag.remove();
|
||||
this._tag = undefined;
|
||||
|
||||
this.managers = undefined;
|
||||
this._current_manager = undefined;
|
||||
|
||||
this.current_selected = undefined;
|
||||
}
|
||||
|
||||
handle_resize() {
|
||||
/* test if the popover isn't a popover anymore */
|
||||
if(this._tag.parent().hasClass('shown')) {
|
||||
this._tag.parent().removeClass('shown');
|
||||
if(this.is_popover())
|
||||
this._tag.parent().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;
|
||||
this.current_selected = null;
|
||||
}
|
||||
this._tag_info.empty();
|
||||
|
||||
this.current_selected = entry;
|
||||
for(let manager of this.managers) {
|
||||
if(manager.available(this.current_selected)) {
|
||||
this._current_manager = manager;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(tr("Using info manager: %o"), this._current_manager);
|
||||
if(this._current_manager)
|
||||
(this._current_manager as InfoManager<AvailableTypes>).createFrame(this, this.current_selected, this._tag_info);
|
||||
}
|
||||
|
||||
get currentSelected() {
|
||||
return this.current_selected;
|
||||
}
|
||||
|
||||
update(){
|
||||
if(this._current_manager && this.current_selected)
|
||||
(this._current_manager as InfoManager<AvailableTypes>).updateFrame(this.current_selected, this._tag_info);
|
||||
}
|
||||
|
||||
current_manager() { return this._current_manager; }
|
||||
|
||||
is_popover() : boolean {
|
||||
return !this._tag.parent().is(':visible') || this._tag.parent().hasClass('shown');
|
||||
}
|
||||
|
||||
open_popover() {
|
||||
this._tag.parent().toggleClass('shown', true);
|
||||
}
|
||||
|
||||
close_popover() {
|
||||
this._tag.parent().toggleClass('shown', false);
|
||||
}
|
||||
|
||||
rendered_tag() {
|
||||
return this._tag_info;
|
||||
}
|
||||
}
|
||||
|
||||
interface Window {
|
||||
Image: typeof HTMLImageElement;
|
||||
HTMLImageElement: typeof HTMLImageElement;
|
||||
}
|
||||
|
||||
class ClientInfoManager extends InfoManager<ClientEntry> {
|
||||
available<V>(object: V): boolean {
|
||||
return typeof object == "object" && object instanceof ClientEntry;
|
||||
}
|
||||
|
||||
createFrame<_>(handle: InfoBar<_>, client: ClientEntry, html_tag: JQuery<HTMLElement>) {
|
||||
super.createFrame(handle, client, html_tag);
|
||||
|
||||
client.updateClientVariables();
|
||||
this.updateFrame(client, html_tag);
|
||||
}
|
||||
|
||||
updateFrame(client: ClientEntry, html_tag: JQuery<HTMLElement>) {
|
||||
this.resetIntervals();
|
||||
html_tag.empty();
|
||||
|
||||
let properties = this.buildProperties(client);
|
||||
|
||||
let rendered = $("#tmpl_selected_client").renderTag(properties);
|
||||
html_tag.append(rendered);
|
||||
|
||||
this.registerInterval(setInterval(() => {
|
||||
html_tag.find(".update_onlinetime").text(formatDate(client.calculateOnlineTime()));
|
||||
}, 1000));
|
||||
}
|
||||
|
||||
buildProperties(client: ClientEntry) : any {
|
||||
let properties: any = {};
|
||||
|
||||
properties["client_name"] = client.createChatTag()[0];
|
||||
properties["client_onlinetime"] = formatDate(client.calculateOnlineTime());
|
||||
properties["sound_volume"] = client.get_audio_handle() ? client.get_audio_handle().get_volume() * 100 : -1;
|
||||
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()) {
|
||||
let group = client.channelTree.client.groups.serverGroup(groupId);
|
||||
if(!group) continue;
|
||||
|
||||
let group_property = {};
|
||||
|
||||
group_property["group_id"] = groupId;
|
||||
group_property["group_name"] = group.name;
|
||||
properties["group_server"].push(group_property);
|
||||
properties["group_" + groupId + "_icon"] = client.channelTree.client.fileManager.icons.generateTag(group.properties.iconid);
|
||||
}
|
||||
|
||||
let group = client.channelTree.client.groups.channelGroup(client.assignedChannelGroup());
|
||||
if(group) {
|
||||
properties["group_channel"] = group.id;
|
||||
properties["group_" + group.id + "_name"] = group.name;
|
||||
properties["group_" + group.id + "_icon"] = client.channelTree.client.fileManager.icons.generateTag(group.properties.iconid);
|
||||
}
|
||||
|
||||
for(let key in client.properties)
|
||||
properties["property_" + key] = client.properties[key];
|
||||
|
||||
if(client.properties.client_teaforo_id > 0) {
|
||||
properties["teaspeak_forum"] = $.spawn("a")
|
||||
.attr("href", "//forum.teaspeak.de/index.php?members/" + client.properties.client_teaforo_id)
|
||||
.attr("target", "_blank")
|
||||
.text(client.properties.client_teaforo_id);
|
||||
}
|
||||
|
||||
if(client.properties.client_flag_avatar && client.properties.client_flag_avatar.length > 0) {
|
||||
properties["client_avatar"] = client.channelTree.client.fileManager.avatars.generate_client_tag(client);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
||||
class ServerInfoManager extends InfoManager<ServerEntry> {
|
||||
createFrame<_>(handle: InfoBar<_>, server: ServerEntry, html_tag: JQuery<HTMLElement>) {
|
||||
super.createFrame(handle, server, html_tag);
|
||||
|
||||
if(server.shouldUpdateProperties()) server.updateProperties();
|
||||
this.updateFrame(server, html_tag);
|
||||
}
|
||||
|
||||
updateFrame(server: ServerEntry, html_tag: JQuery<HTMLElement>) {
|
||||
this.resetIntervals();
|
||||
html_tag.empty();
|
||||
let properties: any = {};
|
||||
|
||||
properties["server_name"] = $.spawn("a").text(server.properties.virtualserver_name);
|
||||
properties["server_onlinetime"] = formatDate(server.calculateUptime());
|
||||
properties["server_address"] = server.remote_address.host + (server.remote_address.port == 9987 ? "" : ":" + server.remote_address.port);
|
||||
const peer_address = server.channelTree.client.serverConnection.remote_address();
|
||||
properties["server_peer_address"] = peer_address.host + (peer_address.port == 9987 ? "" : ":" + server.remote_address.port);
|
||||
properties["hidden_clients"] = Math.max(0, server.properties.virtualserver_clientsonline - server.channelTree.clients.length);
|
||||
|
||||
for(let key in server.properties)
|
||||
properties["property_" + key] = server.properties[key];
|
||||
|
||||
let rendered = $("#tmpl_selected_server").renderTag([properties]);
|
||||
|
||||
this.registerInterval(setInterval(() => {
|
||||
html_tag.find(".update_onlinetime").text(formatDate(server.calculateUptime()));
|
||||
}, 1000));
|
||||
|
||||
|
||||
{
|
||||
const disabled = !server.shouldUpdateProperties();
|
||||
let requestUpdate = rendered.find(".button-update");
|
||||
requestUpdate
|
||||
.prop("disabled", disabled)
|
||||
.toggleClass('btn-success', !disabled)
|
||||
.toggleClass('btn-danger', disabled);
|
||||
|
||||
requestUpdate.click(() => {
|
||||
server.updateProperties();
|
||||
this.triggerUpdate();
|
||||
});
|
||||
|
||||
this.registerTimer(setTimeout(function () {
|
||||
requestUpdate
|
||||
.prop("disabled", false)
|
||||
.toggleClass('btn-success', true)
|
||||
.toggleClass('btn-danger', false);
|
||||
}, server.nextInfoRequest - Date.now()));
|
||||
}
|
||||
|
||||
html_tag.append(rendered);
|
||||
}
|
||||
|
||||
available<V>(object: V): boolean {
|
||||
return typeof object == "object" && object instanceof ServerEntry;
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelInfoManager extends InfoManager<ChannelEntry> {
|
||||
createFrame<_>(handle: InfoBar<_>, channel: ChannelEntry, html_tag: JQuery<HTMLElement>) {
|
||||
super.createFrame(handle, channel, html_tag);
|
||||
this.updateFrame(channel, html_tag);
|
||||
}
|
||||
|
||||
updateFrame(channel: ChannelEntry, html_tag: JQuery<HTMLElement>) {
|
||||
this.resetIntervals();
|
||||
html_tag.empty();
|
||||
let properties: any = {};
|
||||
|
||||
properties["channel_name"] = channel.generate_tag(false);
|
||||
properties["channel_type"] = ChannelType.normalize(channel.channelType());
|
||||
properties["channel_clients"] = channel.channelTree.clientsByChannel(channel).length;
|
||||
properties["channel_subscribed"] = channel.flag_subscribed;
|
||||
properties["server_encryption"] = channel.channelTree.server.properties.virtualserver_codec_encryption_mode;
|
||||
|
||||
for(let key in channel.properties)
|
||||
properties["property_" + key] = channel.properties[key];
|
||||
|
||||
let tag_channel_description = $.spawn("div");
|
||||
properties["bbcode_channel_description"] = tag_channel_description;
|
||||
|
||||
channel.getChannelDescription().then(description => {
|
||||
const result = xbbcode.parse(description, {});
|
||||
/*
|
||||
if(result.error) {
|
||||
console.log("BBCode parse error: %o", result.errorQueue);
|
||||
}
|
||||
*/
|
||||
|
||||
tag_channel_description.empty()
|
||||
.append($.spawn("div").html(result.build_html()).contents())
|
||||
.css("overflow-y", "auto")
|
||||
.css("flex-grow", "1");
|
||||
});
|
||||
|
||||
let rendered = $("#tmpl_selected_channel").renderTag([properties]);
|
||||
html_tag.append(rendered);
|
||||
}
|
||||
|
||||
available<V>(object: V): boolean {
|
||||
return typeof object == "object" && object instanceof ChannelEntry;
|
||||
}
|
||||
}
|
||||
|
||||
function format_time(time: number) {
|
||||
let hours: any = 0, minutes: any = 0, seconds: any = 0;
|
||||
if(time >= 60 * 60) {
|
||||
hours = Math.floor(time / (60 * 60));
|
||||
time -= hours * 60 * 60;
|
||||
}
|
||||
if(time >= 60) {
|
||||
minutes = Math.floor(time / 60);
|
||||
time -= minutes * 60;
|
||||
}
|
||||
seconds = time;
|
||||
|
||||
if(hours > 9)
|
||||
hours = hours.toString();
|
||||
else if(hours > 0)
|
||||
hours = '0' + hours.toString();
|
||||
else hours = '';
|
||||
|
||||
if(minutes > 9)
|
||||
minutes = minutes.toString();
|
||||
else if(minutes > 0)
|
||||
minutes = '0' + minutes.toString();
|
||||
else
|
||||
minutes = '00';
|
||||
|
||||
if(seconds > 9)
|
||||
seconds = seconds.toString();
|
||||
else if(seconds > 0)
|
||||
seconds = '0' + seconds.toString();
|
||||
else
|
||||
seconds = '00';
|
||||
|
||||
return (hours ? hours + ":" : "") + minutes + ':' + seconds;
|
||||
}
|
||||
|
||||
enum MusicPlayerState {
|
||||
SLEEPING,
|
||||
LOADING,
|
||||
|
||||
PLAYING,
|
||||
PAUSED,
|
||||
STOPPED
|
||||
}
|
||||
|
||||
class MusicInfoManager extends ClientInfoManager {
|
||||
single_handler: connection.SingleCommandHandler;
|
||||
|
||||
createFrame<_>(handle: InfoBar<_>, channel: MusicClientEntry, html_tag: JQuery<HTMLElement>) {
|
||||
super.createFrame(handle, channel, html_tag);
|
||||
this.updateFrame(channel, html_tag);
|
||||
}
|
||||
|
||||
updateFrame(bot: MusicClientEntry, html_tag: JQuery<HTMLElement>) {
|
||||
if(this.single_handler) {
|
||||
this.handle.handle.serverConnection.command_handler_boss().remove_single_handler(this.single_handler);
|
||||
this.single_handler = undefined;
|
||||
}
|
||||
|
||||
this.resetIntervals();
|
||||
html_tag.empty();
|
||||
|
||||
let properties = super.buildProperties(bot);
|
||||
{ //Render info frame
|
||||
if(bot.properties.player_state < MusicPlayerState.PLAYING) {
|
||||
properties["music_player"] = $("#tmpl_music_frame_empty").renderTag().css("align-self", "center");
|
||||
} else {
|
||||
let frame = $.spawn("div").text(tr("loading...")) as JQuery<HTMLElement>;
|
||||
properties["music_player"] = frame;
|
||||
properties["song_url"] = $.spawn("a").text(tr("loading..."));
|
||||
|
||||
bot.requestPlayerInfo().then(info => {
|
||||
let timestamp = Date.now();
|
||||
|
||||
console.log(info);
|
||||
let _frame = $("#tmpl_music_frame").renderTag({
|
||||
song_name: info.player_title ? info.player_title :
|
||||
info.song_url ? info.song_url : tr("No title or url"),
|
||||
song_url: info.song_url,
|
||||
thumbnail: info.song_thumbnail && info.song_thumbnail.length > 0 ? info.song_thumbnail : undefined
|
||||
}).css("align-self", "center");
|
||||
properties["song_url"].text(info.song_url);
|
||||
|
||||
frame.replaceWith(_frame);
|
||||
frame = _frame;
|
||||
|
||||
/* Play/Pause logic */
|
||||
{
|
||||
let button_play = frame.find(".button_play");
|
||||
let button_pause = frame.find(".button_pause");
|
||||
let button_stop = frame.find('.button_stop');
|
||||
button_play.click(handler => {
|
||||
if(!button_play.hasClass("active")) {
|
||||
this.handle.handle.serverConnection.send_command("musicbotplayeraction", {
|
||||
bot_id: bot.properties.client_database_id,
|
||||
action: 1
|
||||
}).then(updated => this.triggerUpdate()).catch(error => {
|
||||
createErrorModal(tr("Failed to execute play"), MessageHelper.formatMessage(tr("Failed to execute play.<br>{}"), error)).open();
|
||||
this.triggerUpdate();
|
||||
});
|
||||
}
|
||||
button_pause.show();
|
||||
button_play.hide();
|
||||
});
|
||||
button_pause.click(handler => {
|
||||
if(!button_pause.hasClass("active")) {
|
||||
this.handle.handle.serverConnection.send_command("musicbotplayeraction", {
|
||||
bot_id: bot.properties.client_database_id,
|
||||
action: 2
|
||||
}).then(updated => this.triggerUpdate()).catch(error => {
|
||||
createErrorModal(tr("Failed to execute pause"), MessageHelper.formatMessage(tr("Failed to execute pause.<br>{}"), error)).open();
|
||||
this.triggerUpdate();
|
||||
});
|
||||
}
|
||||
button_play.show();
|
||||
button_pause.hide();
|
||||
});
|
||||
button_stop.click(handler => {
|
||||
this.handle.handle.serverConnection.send_command("musicbotplayeraction", {
|
||||
bot_id: bot.properties.client_database_id,
|
||||
action: 0
|
||||
}).then(updated => this.triggerUpdate()).catch(error => {
|
||||
createErrorModal(tr("Failed to execute stop"), MessageHelper.formatMessage(tr("Failed to execute stop.<br>{}"), error)).open();
|
||||
this.triggerUpdate();
|
||||
});
|
||||
});
|
||||
|
||||
if(bot.properties.player_state == 2) {
|
||||
button_play.hide();
|
||||
button_pause.show();
|
||||
} else if(bot.properties.player_state == 3) {
|
||||
button_pause.hide();
|
||||
button_play.show();
|
||||
} else if(bot.properties.player_state == 4) {
|
||||
button_pause.hide();
|
||||
button_play.show();
|
||||
}
|
||||
}
|
||||
|
||||
{ /* Button functions */
|
||||
_frame.find(".btn-forward").click(() => {
|
||||
this.handle.handle.serverConnection.send_command("musicbotplayeraction", {
|
||||
bot_id: bot.properties.client_database_id,
|
||||
action: 3
|
||||
}).then(updated => this.triggerUpdate()).catch(error => {
|
||||
createErrorModal(tr("Failed to execute forward"), tr("Failed to execute pause.<br>{}").format(error)).open();
|
||||
this.triggerUpdate();
|
||||
});
|
||||
});
|
||||
_frame.find(".btn-rewind").click(() => {
|
||||
this.handle.handle.serverConnection.send_command("musicbotplayeraction", {
|
||||
bot_id: bot.properties.client_database_id,
|
||||
action: 4
|
||||
}).then(updated => this.triggerUpdate()).catch(error => {
|
||||
createErrorModal(tr("Failed to execute rewind"),tr( "Failed to execute pause.<br>{}").format(error)).open();
|
||||
this.triggerUpdate();
|
||||
});
|
||||
});
|
||||
_frame.find(".btn-settings").click(() => {
|
||||
this.handle.handle.serverConnection.command_helper.request_playlist_list().then(lists => {
|
||||
for(const entry of lists) {
|
||||
if(entry.playlist_id == bot.properties.client_playlist_id) {
|
||||
Modals.spawnPlaylistEdit(bot.channelTree.client, entry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
createErrorModal(tr("Invalid permissions"), tr("You don't have to see the bots playlist.")).open();
|
||||
}).catch(error => {
|
||||
createErrorModal(tr("Failed to query playlist."), tr("Failed to query playlist info.")).open();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* Required flip card javascript */
|
||||
frame.find(".right").mouseenter(() => {
|
||||
frame.find(".controls-overlay").addClass("flipped");
|
||||
});
|
||||
frame.find(".right").mouseleave(() => {
|
||||
frame.find(".controls-overlay").removeClass("flipped");
|
||||
});
|
||||
|
||||
/* Slider */
|
||||
frame.find(".timeline .slider").on('mousedown', ev => {
|
||||
let timeline = frame.find(".timeline");
|
||||
let time = frame.find(".time");
|
||||
let slider = timeline.find(".slider");
|
||||
let slider_old = slider.css("margin-left");
|
||||
|
||||
let time_max = parseInt(timeline.attr("time-max"));
|
||||
slider.prop("editing", true);
|
||||
|
||||
let target_timestamp = 0;
|
||||
let move_handler = (event: MouseEvent) => {
|
||||
let max = timeline.width();
|
||||
let current = event.pageX - timeline.offset().left - slider.width() / 2;
|
||||
if(current < 0) current = 0;
|
||||
else if(current > max) current = max;
|
||||
|
||||
target_timestamp = current / max * time_max;
|
||||
time.text(format_time(Math.floor(target_timestamp / 1000)));
|
||||
slider.css("margin-left", current / max * 100 + "%");
|
||||
};
|
||||
|
||||
let finish_handler = event => {
|
||||
console.log("Event (%i | %s): %o", event.button, event.type, event);
|
||||
if(event.type == "mousedown" && event.button != 2) return;
|
||||
$(document).unbind("mousemove", move_handler as any);
|
||||
$(document).unbind("mouseup mouseleave mousedown", finish_handler as any);
|
||||
|
||||
if(event.type != "mousedown") {
|
||||
slider.prop("editing", false);
|
||||
slider.prop("edited", true);
|
||||
|
||||
let current_timestamp = info.player_replay_index + Date.now() - timestamp;
|
||||
this.handle.handle.serverConnection.send_command("musicbotplayeraction", {
|
||||
bot_id: bot.properties.client_database_id,
|
||||
action: current_timestamp > target_timestamp ? 6 : 5,
|
||||
units: current_timestamp < target_timestamp ? target_timestamp - current_timestamp : current_timestamp - target_timestamp
|
||||
}).then(() => this.triggerUpdate()).catch(error => {
|
||||
slider.prop("edited", false);
|
||||
});
|
||||
} else { //Restore old
|
||||
event.preventDefault();
|
||||
slider.css("margin-left", slider_old + "%");
|
||||
}
|
||||
};
|
||||
|
||||
$(document).on('mousemove', move_handler as any);
|
||||
$(document).on('mouseup mouseleave mousedown', finish_handler as any);
|
||||
|
||||
ev.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
{
|
||||
frame.find(".timeline").attr("time-max", info.player_max_index);
|
||||
let timeline = frame.find(".timeline");
|
||||
let time_bar = timeline.find(".played");
|
||||
let buffered_bar = timeline.find(".buffered");
|
||||
let slider = timeline.find(".slider");
|
||||
|
||||
let player_time = _frame.find(".player_time");
|
||||
|
||||
let update_handler = (played?: number, buffered?: number) => {
|
||||
let time_index = played || info.player_replay_index + (bot.properties.player_state == 2 ? Date.now() - timestamp : 0);
|
||||
let buffered_index = buffered || 0;
|
||||
|
||||
time_bar.css("width", time_index / info.player_max_index * 100 + "%");
|
||||
buffered_bar.css("width", buffered_index / info.player_max_index * 100 + "%");
|
||||
if(!slider.prop("editing") && !slider.prop("edited")) {
|
||||
player_time.text(format_time(Math.floor(time_index / 1000)));
|
||||
slider.css("margin-left", time_index / info.player_max_index * 100 + "%");
|
||||
}
|
||||
};
|
||||
|
||||
let interval = setInterval(update_handler, 1000);
|
||||
this.registerInterval(interval);
|
||||
update_handler();
|
||||
|
||||
/* register subscription */
|
||||
this.handle.handle.serverConnection.send_command("musicbotsetsubscription", {bot_id: bot.properties.client_database_id}).catch(error => {
|
||||
console.error("Failed to subscribe to displayed music bot! Using pseudo timeline");
|
||||
}).then(() => {
|
||||
clearInterval(interval);
|
||||
});
|
||||
|
||||
this.single_handler = {
|
||||
command: "notifymusicstatusupdate",
|
||||
function: command => {
|
||||
const json = command.arguments[0];
|
||||
update_handler(parseInt(json["player_replay_index"]), parseInt(json["player_buffered_index"]));
|
||||
|
||||
return false; /* do not unregister me! */
|
||||
}
|
||||
};
|
||||
|
||||
this.handle.handle.serverConnection.command_handler_boss().register_single_handler(this.single_handler);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const player = properties["music_player"];
|
||||
const player_transformer = $.spawn("div").append(player);
|
||||
player_transformer.css({
|
||||
'display': 'block',
|
||||
//'width': "100%",
|
||||
'height': '100%'
|
||||
});
|
||||
properties["music_player"] = player_transformer;
|
||||
}
|
||||
|
||||
let rendered = $("#tmpl_selected_music").renderTag([properties]);
|
||||
html_tag.append(rendered);
|
||||
|
||||
{
|
||||
const player = properties["music_player"] as JQuery;
|
||||
const player_width = 400; //player.width();
|
||||
const player_height = 400; //player.height();
|
||||
|
||||
const parent = player.parent();
|
||||
parent.css({
|
||||
'flex-grow': 1,
|
||||
'display': 'flex',
|
||||
'flex-direction': 'row',
|
||||
'justify-content': 'space-around',
|
||||
});
|
||||
|
||||
const padding = 14;
|
||||
const scale_x = Math.min((parent.width() - padding) / player_width, 1.5);
|
||||
const scale_y = Math.min((parent.height() - padding) / player_height, 1.5);
|
||||
let scale = Math.min(scale_x, scale_y);
|
||||
|
||||
let translate_x = 50, translate_y = 50;
|
||||
if(scale_x == scale_y && scale_x == scale) {
|
||||
//Equal scale
|
||||
} else if(scale_x == scale) {
|
||||
//We scale on the x-Axis
|
||||
//We have to adjust the y-Axis
|
||||
} else {
|
||||
//We scale on the y-Axis
|
||||
//We have to adjust the x-Axis
|
||||
|
||||
}
|
||||
//1 => 0 | 0
|
||||
//1.5 => 0 | 25
|
||||
//0.5 => 0 | -25
|
||||
//const translate_x = scale_x != scale ? 0 : undefined || 50 - (50 * ((parent.width() - padding) / player_width));
|
||||
//const translate_y = scale_y != scale || scale_y > 1 ? 0 : undefined || 50 - (50 * ((parent.height() - padding) / player_height));
|
||||
const transform = ("translate(0%, " + (scale * 50 - 50) + "%) scale(" + scale.toPrecision(2) + ")");
|
||||
|
||||
console.log(tr("Parents: %o | %o"), parent.width(), parent.height());
|
||||
console.log(tr("Player: %o | %o"), player_width, player_height);
|
||||
console.log(tr("Scale: %f => translate: %o | %o"), scale, translate_x, translate_y);
|
||||
player.css({
|
||||
transform: transform
|
||||
});
|
||||
console.log("Transform: " + transform);
|
||||
}
|
||||
|
||||
this.registerInterval(setInterval(() => {
|
||||
html_tag.find(".update_onlinetime").text(formatDate(bot.calculateOnlineTime()));
|
||||
}, 1000));
|
||||
}
|
||||
|
||||
update_local_volume(volume: number) {
|
||||
this.handle.rendered_tag().find(".property-volume-local").text(Math.floor(volume * 100) + "%");
|
||||
}
|
||||
|
||||
update_remote_volume(volume: number) {
|
||||
this.handle.rendered_tag().find(".property-volume-remote").text(Math.floor(volume * 100) + "%")
|
||||
}
|
||||
|
||||
available<V>(object: V): boolean {
|
||||
return typeof object == "object" && object instanceof MusicClientEntry;
|
||||
}
|
||||
|
||||
finalizeFrame(object: ClientEntry, frame: JQuery<HTMLElement>) {
|
||||
if(this.single_handler) {
|
||||
this.handle.handle.serverConnection.command_handler_boss().remove_single_handler(this.single_handler);
|
||||
this.single_handler = undefined;
|
||||
}
|
||||
|
||||
super.finalizeFrame(object, frame);
|
||||
}
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ enum ChatType {
|
|||
CLIENT
|
||||
}
|
||||
|
||||
declare const xbbcode: any;
|
||||
namespace MessageHelper {
|
||||
export function htmlEscape(message: string) : string[] {
|
||||
const div = document.createElement('div');
|
||||
|
@ -90,7 +91,7 @@ namespace MessageHelper {
|
|||
}
|
||||
|
||||
export function bbcode_chat(message: string) : JQuery[] {
|
||||
const result: xbbcode.Result = xbbcode.parse(message, {
|
||||
const result = xbbcode.parse(message, {
|
||||
/* TODO make this configurable and allow IMG */
|
||||
tag_whitelist: [
|
||||
"b", "big",
|
||||
|
@ -133,6 +134,123 @@ namespace MessageHelper {
|
|||
//return result.root_tag.content.map(e => e.build_html()).map((entry, idx, array) => $.spawn("a").css("display", (idx == 0 ? "inline" : "") + "block").html(entry == "" && idx != 0 ? " " : entry));
|
||||
}
|
||||
|
||||
export namespace network {
|
||||
export const KB = 1024;
|
||||
export const MB = 1024 * KB;
|
||||
export const GB = 1024 * MB;
|
||||
export const TB = 1024 * GB;
|
||||
|
||||
export function format_bytes(value: number, options?: {
|
||||
time?: string,
|
||||
unit?: string,
|
||||
exact?: boolean
|
||||
}) : string {
|
||||
options = Object.assign(options || {}, {
|
||||
time: undefined,
|
||||
unit: "Bytes",
|
||||
exact: true
|
||||
});
|
||||
|
||||
let points = value.toFixed(0).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
||||
|
||||
let v, unit;
|
||||
if(value > 2 * TB) {
|
||||
unit = "TB";
|
||||
v = value / TB;
|
||||
} else if(value > GB) {
|
||||
unit = "GB";
|
||||
v = value / GB;
|
||||
} else if(value > MB) {
|
||||
unit = "MB";
|
||||
v = value / MB;
|
||||
} else if(value > KB) {
|
||||
unit = "KB";
|
||||
v = value / KB;
|
||||
} else {
|
||||
unit = "";
|
||||
v = value;
|
||||
}
|
||||
if(unit && options.time)
|
||||
unit = unit + "/" + options.time;
|
||||
return (options.exact || !unit ? (points + " " + (options.unit || "")) : "") + (unit ? (" / " + v.toFixed(2) + " " + unit) : "");
|
||||
}
|
||||
}
|
||||
|
||||
export const K = 1000;
|
||||
export const M = 1000 * K;
|
||||
export const G = 1000 * M;
|
||||
export const T = 1000 * G;
|
||||
export function format_number(value: number, options?: {
|
||||
time?: string,
|
||||
unit?: string
|
||||
}) {
|
||||
options = Object.assign(options || {}, {});
|
||||
|
||||
let points = value.toFixed(0).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
||||
|
||||
let v, unit;
|
||||
if(value > 2 * T) {
|
||||
unit = "T";
|
||||
v = value / T;
|
||||
} else if(value > G) {
|
||||
unit = "G";
|
||||
v = value / G;
|
||||
} else if(value > M) {
|
||||
unit = "M";
|
||||
v = value / M;
|
||||
} else if(value > K) {
|
||||
unit = "K";
|
||||
v = value / K;
|
||||
} else {
|
||||
unit = "";
|
||||
v = value;
|
||||
}
|
||||
if(unit && options.time)
|
||||
unit = unit + "/" + options.time;
|
||||
return points + " " + (options.unit || "") + (unit ? (" / " + v.toFixed(2) + " " + unit) : "");
|
||||
}
|
||||
|
||||
export const TIME_SECOND = 1000;
|
||||
export const TIME_MINUTE = 60 * TIME_SECOND;
|
||||
export const TIME_HOUR = 60 * TIME_MINUTE;
|
||||
export const TIME_DAY = 24 * TIME_HOUR;
|
||||
export const TIME_WEEK = 7 * TIME_DAY;
|
||||
|
||||
export function format_time(time: number, default_value: string) {
|
||||
let result = "";
|
||||
if(time > TIME_WEEK) {
|
||||
const amount = Math.floor(time / TIME_WEEK);
|
||||
result += " " + amount + " " + (amount > 1 ? tr("Weeks") : tr("Week"));
|
||||
time -= amount * TIME_WEEK;
|
||||
}
|
||||
|
||||
if(time > TIME_DAY) {
|
||||
const amount = Math.floor(time / TIME_DAY);
|
||||
result += " " + amount + " " + (amount > 1 ? tr("Days") : tr("Day"));
|
||||
time -= amount * TIME_DAY;
|
||||
}
|
||||
|
||||
if(time > TIME_HOUR) {
|
||||
const amount = Math.floor(time / TIME_HOUR);
|
||||
result += " " + amount + " " + (amount > 1 ? tr("Hours") : tr("Hour"));
|
||||
time -= amount * TIME_HOUR;
|
||||
}
|
||||
|
||||
if(time > TIME_MINUTE) {
|
||||
const amount = Math.floor(time / TIME_MINUTE);
|
||||
result += " " + amount + " " + (amount > 1 ? tr("Minutes") : tr("Minute"));
|
||||
time -= amount * TIME_MINUTE;
|
||||
}
|
||||
|
||||
if(time > TIME_SECOND) {
|
||||
const amount = Math.floor(time / TIME_SECOND);
|
||||
result += " " + amount + " " + (amount > 1 ? tr("Seconds") : tr("Second"));
|
||||
time -= amount * TIME_SECOND;
|
||||
}
|
||||
|
||||
return result.length > 0 ? result.substring(1) : default_value;
|
||||
}
|
||||
|
||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||
name: "XBBCode code tag init",
|
||||
function: async () => {
|
||||
|
@ -141,7 +259,7 @@ namespace MessageHelper {
|
|||
tag: ["code", "icode", "i-code"],
|
||||
content_tags_whitelist: [],
|
||||
|
||||
build_html(layer: xbbcode.TagLayer) : string {
|
||||
build_html(layer) : string {
|
||||
const klass = layer.tag_normalized != 'code' ? "tag-hljs-inline-code" : "tag-hljs-code";
|
||||
const language = (layer.options || "").replace("\"", "'").toLowerCase();
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* the bar on the right with the chats (Channel & Client) */
|
||||
namespace chat {
|
||||
/* Fix some declare issues... */
|
||||
import instantiate = WebAssembly.instantiate;
|
||||
import Event = JQuery.Event;
|
||||
|
||||
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
||||
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
|
||||
|
@ -20,6 +19,8 @@ namespace chat {
|
|||
private _value_ping: JQuery;
|
||||
private _ping_updater: number;
|
||||
|
||||
private _button_conversation: HTMLElement;
|
||||
|
||||
constructor(handle: Frame) {
|
||||
this.handle = handle;
|
||||
this._build_html_tag();
|
||||
|
@ -44,6 +45,20 @@ namespace chat {
|
|||
this._html_tag.find(".button-switch-chat-channel").on('click', () => this.handle.show_channel_conversations());
|
||||
this._value_ping = this._html_tag.find(".value-ping");
|
||||
this._html_tag.find(".chat-counter").on('click', event => this.handle.show_private_conversations());
|
||||
this._button_conversation = this._html_tag.find(".button.open-conversation").on('click', event => {
|
||||
const selected_client = this.handle.client_info().current_client();
|
||||
if(!selected_client) return;
|
||||
|
||||
const conversation = selected_client ? this.handle.private_conversations().find_conversation({
|
||||
name: selected_client.properties.client_nickname,
|
||||
unique_id: selected_client.properties.client_unique_identifier,
|
||||
client_id: selected_client.clientId()
|
||||
}, { create: true, attach: true }) : undefined;
|
||||
if(!conversation) return;
|
||||
|
||||
this.handle.private_conversations().set_selected_conversation(conversation);
|
||||
this.handle.show_private_conversations();
|
||||
})[0];
|
||||
}
|
||||
|
||||
update_ping() {
|
||||
|
@ -115,7 +130,7 @@ namespace chat {
|
|||
const current_channel_id = channel_tree ? this.handle.channel_conversations().current_channel() : 0;
|
||||
const channel = channel_tree ? channel_tree.findChannel(current_channel_id) : undefined;
|
||||
|
||||
const tag_container = this._html_tag.find(".mode-channel_chat");
|
||||
const tag_container = this._html_tag.find(".mode-channel_chat.channel");
|
||||
const html_tag_title = tag_container.find(".title");
|
||||
const html_tag = tag_container.find(".value-text-channel");
|
||||
const html_limit_tag = tag_container.find(".value-text-limit");
|
||||
|
@ -179,12 +194,31 @@ namespace chat {
|
|||
}
|
||||
|
||||
set_mode(mode: InfoFrameMode) {
|
||||
if(this._mode === mode)
|
||||
return;
|
||||
if(this._mode !== mode) {
|
||||
this._mode = mode;
|
||||
this._html_tag.find(".mode-based").hide();
|
||||
this._html_tag.find(".mode-" + mode).show();
|
||||
}
|
||||
|
||||
if(mode === InfoFrameMode.CLIENT_INFO && this._button_conversation) {
|
||||
//Will be called every time a client is shown
|
||||
const selected_client = this.handle.client_info().current_client();
|
||||
const conversation = selected_client ? this.handle.private_conversations().find_conversation({
|
||||
name: selected_client.properties.client_nickname,
|
||||
unique_id: selected_client.properties.client_unique_identifier,
|
||||
client_id: selected_client.clientId()
|
||||
}, { create: false, attach: false }) : undefined;
|
||||
|
||||
const visibility = (selected_client && selected_client.clientId() !== this.handle.handle.clientId) ? "visible" : "hidden";
|
||||
if(this._button_conversation.style.visibility !== visibility)
|
||||
this._button_conversation.style.visibility = visibility;
|
||||
if(conversation) {
|
||||
this._button_conversation.innerText = tr("Open conversation");
|
||||
} else {
|
||||
this._button_conversation.innerText = tr("Start a conversation");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ChatBox {
|
||||
|
@ -195,6 +229,11 @@ namespace chat {
|
|||
private __callback_key_down;
|
||||
private __callback_paste;
|
||||
|
||||
private _typing_timeout: number; /* ID when the next callback_typing will be called */
|
||||
private _typing_last_event: number; /* timestamp of the last typing event */
|
||||
|
||||
typing_interval: number = 2000; /* update frequency */
|
||||
callback_typing: () => any;
|
||||
callback_text: (text: string) => any;
|
||||
|
||||
constructor() {
|
||||
|
@ -216,15 +255,18 @@ namespace chat {
|
|||
this._html_tag = undefined;
|
||||
this._html_input = undefined;
|
||||
|
||||
clearTimeout(this._typing_timeout);
|
||||
|
||||
this.__callback_text_changed = undefined;
|
||||
this.__callback_key_down = undefined;
|
||||
this.__callback_paste = undefined;
|
||||
|
||||
this.callback_text = undefined;
|
||||
this.callback_typing = undefined;
|
||||
}
|
||||
|
||||
private _initialize_listener() {
|
||||
this._html_input.on("cut paste drop key keyup", (event) => this.__callback_text_changed(event));
|
||||
this._html_input.on("cut paste drop keydown keyup", (event) => this.__callback_text_changed(event));
|
||||
this._html_input.on("change", this.__callback_text_changed);
|
||||
this._html_input.on("keydown", this.__callback_key_down);
|
||||
this._html_input.on("paste", this.__callback_paste);
|
||||
|
@ -246,7 +288,7 @@ namespace chat {
|
|||
});
|
||||
}
|
||||
|
||||
private _callback_text_changed(event) {
|
||||
private _callback_text_changed(event: Event) {
|
||||
if(event && event.isDefaultPrevented())
|
||||
return;
|
||||
|
||||
|
@ -254,6 +296,28 @@ namespace chat {
|
|||
const text = this._html_input[0];
|
||||
text.style.height = "1em";
|
||||
text.style.height = text.scrollHeight + 'px';
|
||||
|
||||
if(!event || (event.type !== "keydown" && event.type !== "keyup" && event.type !== "change"))
|
||||
return;
|
||||
|
||||
this._typing_last_event = Date.now();
|
||||
if(this._typing_timeout)
|
||||
return;
|
||||
|
||||
const _trigger_typing = (last_time: number) => {
|
||||
if(this._typing_last_event <= last_time) {
|
||||
this._typing_timeout = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if(this.callback_typing)
|
||||
this.callback_typing();
|
||||
} finally {
|
||||
this._typing_timeout = setTimeout(_trigger_typing, this.typing_interval, this._typing_last_event);
|
||||
}
|
||||
};
|
||||
_trigger_typing(0); /* We def want that*/
|
||||
}
|
||||
|
||||
private _text(element: HTMLElement) {
|
||||
|
@ -331,8 +395,14 @@ namespace chat {
|
|||
this.callback_text(helpers.preprocess_chat_message(text));
|
||||
}
|
||||
|
||||
if(this._typing_timeout)
|
||||
clearTimeout(this._typing_timeout);
|
||||
this._typing_timeout = 1; /* enforce no typing update while sending */
|
||||
this._html_input.text("");
|
||||
setTimeout(() => this.__callback_text_changed());
|
||||
setTimeout(() => {
|
||||
this.__callback_text_changed();
|
||||
this._typing_timeout = 0; /* enable text change listener again */
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -392,7 +462,7 @@ namespace chat {
|
|||
}
|
||||
|
||||
namespace md2bbc {
|
||||
type RemarkToken = {
|
||||
export type RemarkToken = {
|
||||
type: string;
|
||||
tight: boolean;
|
||||
lines: number[];
|
||||
|
@ -764,11 +834,11 @@ test
|
|||
}
|
||||
}
|
||||
|
||||
type PrivateConversationViewEntry = {
|
||||
export type PrivateConversationViewEntry = {
|
||||
html_tag: JQuery;
|
||||
}
|
||||
|
||||
type PrivateConversationMessageData = {
|
||||
export type PrivateConversationMessageData = {
|
||||
message_id: string;
|
||||
message: string;
|
||||
sender: "self" | "partner";
|
||||
|
@ -780,10 +850,10 @@ test
|
|||
timestamp: number;
|
||||
};
|
||||
|
||||
type PrivateConversationViewMessage = PrivateConversationMessageData & PrivateConversationViewEntry & {
|
||||
export type PrivateConversationViewMessage = PrivateConversationMessageData & PrivateConversationViewEntry & {
|
||||
time_update_id: number;
|
||||
};
|
||||
type PrivateConversationViewSpacer = PrivateConversationViewEntry;
|
||||
export type PrivateConversationViewSpacer = PrivateConversationViewEntry;
|
||||
|
||||
export enum PrivateConversationState {
|
||||
OPEN,
|
||||
|
@ -791,7 +861,7 @@ test
|
|||
DISCONNECTED,
|
||||
}
|
||||
|
||||
type DisplayedMessage = {
|
||||
export type DisplayedMessage = {
|
||||
timestamp: number;
|
||||
|
||||
message: PrivateConversationViewMessage | PrivateConversationViewEntry;
|
||||
|
@ -817,6 +887,9 @@ test
|
|||
private _state: PrivateConversationState;
|
||||
|
||||
private _last_message_updater_id: number;
|
||||
private _last_typing: number = 0;
|
||||
private _typing_timeout: number = 4000;
|
||||
private _typing_timeout_task: number;
|
||||
|
||||
_scroll_position: number | undefined; /* undefined to follow bottom | position for special stuff */
|
||||
_html_message_container: JQuery; /* only set when this chat is selected! */
|
||||
|
@ -888,6 +961,8 @@ test
|
|||
this._html_entry_tag = undefined;
|
||||
|
||||
this._message_history = undefined;
|
||||
if(this._typing_timeout_task)
|
||||
clearTimeout(this._typing_timeout_task);
|
||||
}
|
||||
|
||||
private _2d_flat<T>(array: T[][]) : T[] {
|
||||
|
@ -943,7 +1018,20 @@ test
|
|||
while(this._message_history.length > 100)
|
||||
this._message_history.pop();
|
||||
}
|
||||
|
||||
if(sender.type === "partner") {
|
||||
clearTimeout(this._typing_timeout_task);
|
||||
this._typing_timeout_task = 0;
|
||||
|
||||
if(this.typing_active()) {
|
||||
this._last_typing = 0;
|
||||
this.typing_expired();
|
||||
} else {
|
||||
this._update_message_timestamp();
|
||||
}
|
||||
} else {
|
||||
this._update_message_timestamp();
|
||||
}
|
||||
|
||||
if(typeof(save_history) !== "boolean" || save_history)
|
||||
this.save_history();
|
||||
|
@ -1039,6 +1127,15 @@ test
|
|||
private _update_message_timestamp() {
|
||||
if(this._last_message_updater_id)
|
||||
clearTimeout(this._last_message_updater_id);
|
||||
|
||||
if(!this._html_entry_tag)
|
||||
return; /* we got deleted, not need for updates */
|
||||
|
||||
if(this.typing_active()) {
|
||||
this._html_entry_tag.find(".last-message").text(tr("currently typing..."));
|
||||
return;
|
||||
}
|
||||
|
||||
const last_message = this._message_history[0];
|
||||
if(!last_message) {
|
||||
this._html_entry_tag.find(".last-message").text(tr("no history"));
|
||||
|
@ -1312,9 +1409,10 @@ test
|
|||
if(this._state == state)
|
||||
return;
|
||||
|
||||
if(state == PrivateConversationState.DISCONNECTED)
|
||||
if(state == PrivateConversationState.DISCONNECTED) {
|
||||
this._append_state_change("disconnect");
|
||||
else if(state == PrivateConversationState.OPEN && this._state != PrivateConversationState.CLOSED)
|
||||
this.client_id = 0;
|
||||
} else if(state == PrivateConversationState.OPEN && this._state != PrivateConversationState.CLOSED)
|
||||
this._append_state_change("reconnect");
|
||||
else if(state == PrivateConversationState.CLOSED)
|
||||
this._append_state_change("closed");
|
||||
|
@ -1355,6 +1453,31 @@ test
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
private typing_expired() {
|
||||
this._update_message_timestamp();
|
||||
if(this.handle.current_conversation() === this)
|
||||
this.handle.update_typing_state();
|
||||
}
|
||||
|
||||
trigger_typing() {
|
||||
let _new = Date.now() - this._last_typing > this._typing_timeout;
|
||||
this._last_typing = Date.now();
|
||||
|
||||
if(this._typing_timeout_task)
|
||||
clearTimeout(this._typing_timeout_task);
|
||||
|
||||
if(_new)
|
||||
this._update_message_timestamp();
|
||||
if(this.handle.current_conversation() === this)
|
||||
this.handle.update_typing_state();
|
||||
|
||||
this._typing_timeout_task = setTimeout(() => this.typing_expired(), this._typing_timeout);
|
||||
}
|
||||
|
||||
typing_active() {
|
||||
return Date.now() - this._last_typing < this._typing_timeout;
|
||||
}
|
||||
}
|
||||
|
||||
export class PrivateConverations {
|
||||
|
@ -1365,6 +1488,7 @@ test
|
|||
private _container_conversation: JQuery;
|
||||
private _container_conversation_messages: JQuery;
|
||||
private _container_conversation_list: JQuery;
|
||||
private _container_typing: JQuery;
|
||||
|
||||
private _html_no_chats: JQuery;
|
||||
private _conversations: PrivateConveration[] = [];
|
||||
|
@ -1378,12 +1502,29 @@ test
|
|||
this._build_html_tag();
|
||||
|
||||
this.update_chatbox_state();
|
||||
this.update_typing_state();
|
||||
this._chat_box.callback_text = message => {
|
||||
if(!this._current_conversation) {
|
||||
console.warn(tr("Dropping conversation message because of no active conversation."));
|
||||
return;
|
||||
}
|
||||
this._current_conversation.call_message(message);
|
||||
};
|
||||
|
||||
this._chat_box.callback_typing = () => {
|
||||
if(!this._current_conversation) {
|
||||
console.warn(tr("Dropping conversation typing action because of no active conversation."));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("TYPING!");
|
||||
const connection = this.handle.handle.serverConnection;
|
||||
if(!connection || !connection.connected())
|
||||
return;
|
||||
|
||||
connection.send_command("clientchatcomposing", {
|
||||
clid: this._current_conversation.client_id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1408,6 +1549,8 @@ test
|
|||
|
||||
}
|
||||
|
||||
current_conversation() : PrivateConveration | undefined { return this._current_conversation; }
|
||||
|
||||
conversations() : PrivateConveration[] { return this._conversations; }
|
||||
create_conversation(client_uid: string, client_name: string, client_id: number) : PrivateConveration {
|
||||
const conv = new PrivateConveration(this, client_uid, client_name, client_id);
|
||||
|
@ -1523,8 +1666,14 @@ test
|
|||
this._chat_box.set_enabled(!!this._current_conversation && this._current_conversation.chat_enabled());
|
||||
}
|
||||
|
||||
update_typing_state() {
|
||||
this._container_typing.toggleClass("hidden", !this._current_conversation || !this._current_conversation.typing_active());
|
||||
}
|
||||
|
||||
private _build_html_tag() {
|
||||
this._html_tag = $("#tmpl_frame_chat_private").renderTag().dividerfy();
|
||||
this._html_tag = $("#tmpl_frame_chat_private").renderTag({
|
||||
chatbox: this._chat_box.html_tag()
|
||||
}).dividerfy();
|
||||
this._container_conversation = this._html_tag.find(".conversation");
|
||||
this._container_conversation.on('click', event => { /* lets think if a user clicks within that field that he has read the messages */
|
||||
if(this._current_conversation)
|
||||
|
@ -1542,10 +1691,10 @@ test
|
|||
else
|
||||
this._current_conversation._scroll_position = this._container_conversation_messages[0].scrollTop;
|
||||
});
|
||||
this._container_conversation.find(".chatbox").append(this._chat_box.html_tag());
|
||||
|
||||
this._container_conversation_list = this._html_tag.find(".conversation-list");
|
||||
this._html_no_chats = this._container_conversation_list.find(".no-chats");
|
||||
this._container_typing = this._html_tag.find(".container-typing");
|
||||
}
|
||||
|
||||
try_input_focus() {
|
||||
|
@ -1637,7 +1786,17 @@ test
|
|||
} else {
|
||||
this._scroll_position = this._container_messages[0].scrollTop;
|
||||
}
|
||||
this._container_new_message.toggleClass("shown",!!this._first_unread_message && this._first_unread_message_pointer.html_element[0].offsetTop > exact_position);
|
||||
|
||||
const will_visible = !!this._first_unread_message && this._first_unread_message_pointer.html_element[0].offsetTop > exact_position;
|
||||
const is_visible = this._container_new_message[0].classList.contains("shown");
|
||||
if(!is_visible && will_visible)
|
||||
this._container_new_message[0].classList.add("shown");
|
||||
|
||||
if(is_visible && !will_visible)
|
||||
this._container_new_message[0].classList.remove("shown");
|
||||
|
||||
//This causes a Layout recalc (Forced reflow)
|
||||
//this._container_new_message.toggleClass("shown",!!this._first_unread_message && this._first_unread_message_pointer.html_element[0].offsetTop > exact_position);
|
||||
});
|
||||
|
||||
this._view_older_messages = this._generate_view_spacer(tr("Load older messages"), "old");
|
||||
|
@ -2147,6 +2306,12 @@ test
|
|||
|
||||
this.handle.set_content(this.previous_frame_content);
|
||||
});
|
||||
this._html_tag.find(".button-more").on('click', () => {
|
||||
if(!this._current_client)
|
||||
return;
|
||||
|
||||
Modals.openClientInfo(this._current_client);
|
||||
});
|
||||
this._html_tag.find('.container-avatar-edit').on('click', () => this.handle.handle.update_avatar());
|
||||
}
|
||||
|
||||
|
@ -2221,7 +2386,7 @@ test
|
|||
}
|
||||
|
||||
const volume = this._html_tag.find(".client-local-volume");
|
||||
volume.text((client && client.get_audio_handle() ? (client.get_audio_handle().get_volume() * 100) : -1) + "%");
|
||||
volume.text((client && client.get_audio_handle() ? (client.get_audio_handle().get_volume() * 100) : -1).toFixed(0) + "%");
|
||||
}
|
||||
|
||||
/* teaspeak forum */
|
||||
|
@ -2471,6 +2636,7 @@ test
|
|||
|
||||
show_client_info(client: ClientEntry) {
|
||||
this._client_info.set_current_client(client);
|
||||
this._info_frame.set_mode(InfoFrameMode.CLIENT_INFO); /* specially needs an update here to update the conversation button */
|
||||
|
||||
if(this._content_type === FrameContent.CLIENT_INFO)
|
||||
return;
|
||||
|
@ -2479,7 +2645,6 @@ test
|
|||
this._clear();
|
||||
this._content_type = FrameContent.CLIENT_INFO;
|
||||
this._container_chat.append(this._client_info.html_tag());
|
||||
this._info_frame.set_mode(InfoFrameMode.CLIENT_INFO);
|
||||
}
|
||||
|
||||
set_content(type: FrameContent) {
|
||||
|
|
|
@ -8,7 +8,6 @@ class ServerConnectionManager {
|
|||
private _container_log_server: JQuery;
|
||||
private _container_channel_tree: JQuery;
|
||||
private _container_hostbanner: JQuery;
|
||||
private _container_select_info: JQuery;
|
||||
private _container_chat: JQuery;
|
||||
|
||||
private _tag: JQuery;
|
||||
|
@ -34,7 +33,6 @@ class ServerConnectionManager {
|
|||
this._container_log_server = $("#server-log");
|
||||
this._container_channel_tree = $("#channelTree");
|
||||
this._container_hostbanner = $("#hostbanner");
|
||||
this._container_select_info = $("#select_info");
|
||||
this._container_chat = $("#chat");
|
||||
|
||||
this.set_active_connection_handler(undefined);
|
||||
|
@ -75,11 +73,8 @@ class ServerConnectionManager {
|
|||
if(handler && this.connection_handlers.indexOf(handler) == -1)
|
||||
throw "Handler hasn't been registered or is already obsolete!";
|
||||
|
||||
if(this.active_handler)
|
||||
this.active_handler.select_info.close_popover();
|
||||
this._tag_connection_entries.find(".active").removeClass("active");
|
||||
this._container_channel_tree.children().detach();
|
||||
this._container_select_info.children().detach();
|
||||
this._container_chat.children().detach();
|
||||
this._container_log_server.children().detach();
|
||||
this._container_hostbanner.children().detach();
|
||||
|
@ -90,7 +85,6 @@ class ServerConnectionManager {
|
|||
|
||||
this._container_hostbanner.append(handler.hostbanner.html_tag);
|
||||
this._container_channel_tree.append(handler.channelTree.tag_tree());
|
||||
this._container_select_info.append(handler.select_info.get_tag());
|
||||
this._container_chat.append(handler.side_bar.html_tag());
|
||||
this._container_log_server.append(handler.log.html_tag());
|
||||
|
||||
|
|
|
@ -68,20 +68,14 @@ class Hostbanner {
|
|||
this.html_tag.attr('title', server ? server.properties.virtualserver_hostbanner_url : undefined);
|
||||
}
|
||||
|
||||
private async generate_tag?() : Promise<JQuery | undefined> {
|
||||
if(!this.client.connected)
|
||||
return undefined;
|
||||
public static async generate_tag(banner_url: string | undefined, gfx_interval: number, mode: number) : Promise<JQuery | undefined> {
|
||||
if(!banner_url) return undefined;
|
||||
|
||||
const server = this.client.channelTree.server;
|
||||
if(!server) return undefined;
|
||||
if(!server.properties.virtualserver_hostbanner_gfx_url) return undefined;
|
||||
|
||||
let banner_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);
|
||||
if(gfx_interval > 0) {
|
||||
const update_interval = Math.max(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);
|
||||
const url = new URL(banner_url);
|
||||
if(url.search.length == 0)
|
||||
banner_url += "?_ts=" + update_timestamp;
|
||||
else
|
||||
|
@ -90,8 +84,6 @@ class Hostbanner {
|
|||
console.warn(tr("Failed to parse banner URL: %o. Using default '&' append."), error);
|
||||
banner_url += "&_ts=" + update_timestamp;
|
||||
}
|
||||
|
||||
this.updater = setTimeout(() => this.update(), update_interval * 1000);
|
||||
}
|
||||
|
||||
/* first now load the image */
|
||||
|
@ -102,11 +94,27 @@ class Hostbanner {
|
|||
image_element.src = banner_url;
|
||||
image_element.style.display = 'none';
|
||||
document.body.append(image_element);
|
||||
console.log("Loading image!");
|
||||
console.log("Loading hostbanner image!");
|
||||
});
|
||||
|
||||
image_element.parentNode.removeChild(image_element);
|
||||
image_element.style.display = 'unset';
|
||||
return $.spawn("div").addClass("hostbanner-image-container hostbanner-mode-" + server.properties.virtualserver_hostbanner_mode).append($(image_element));
|
||||
return $.spawn("div").addClass("hostbanner-image-container hostbanner-mode-" + mode).append($(image_element));
|
||||
}
|
||||
|
||||
private async generate_tag?() : Promise<JQuery | undefined> {
|
||||
if(!this.client.connected)
|
||||
return undefined;
|
||||
|
||||
const server = this.client.channelTree.server;
|
||||
if(!server) return undefined;
|
||||
if(!server.properties.virtualserver_hostbanner_gfx_url) return undefined;
|
||||
|
||||
const timeout = server.properties.virtualserver_hostbanner_gfx_interval;
|
||||
const tag = Hostbanner.generate_tag(server.properties.virtualserver_hostbanner_gfx_url, server.properties.virtualserver_hostbanner_gfx_interval, server.properties.virtualserver_hostbanner_mode);
|
||||
if(timeout > 0)
|
||||
this.updater = setTimeout(() => this.update(), timeout * 1000);
|
||||
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -251,8 +251,8 @@ namespace log {
|
|||
"channel_delete": event.ChannelDelete;
|
||||
}
|
||||
|
||||
type MessageBuilderOptions = {};
|
||||
type MessageBuilder<T extends keyof server.TypeInfo> = (data: TypeInfo[T], options: MessageBuilderOptions) => JQuery[] | undefined;
|
||||
export type MessageBuilderOptions = {};
|
||||
export type MessageBuilder<T extends keyof server.TypeInfo> = (data: TypeInfo[T], options: MessageBuilderOptions) => JQuery[] | undefined;
|
||||
|
||||
export const MessageBuilders: {[key: string]: MessageBuilder<any>} = {
|
||||
"error_custom": (data: event.ErrorCustom, options) => {
|
||||
|
|
|
@ -182,7 +182,7 @@ namespace htmltags {
|
|||
const origin_url = xbbcode.register.find_parser('url');
|
||||
xbbcode.register.register_parser({
|
||||
tag: 'url',
|
||||
build_html_tag_open(layer: xbbcode.TagLayer): string {
|
||||
build_html_tag_open(layer): string {
|
||||
if(layer.options) {
|
||||
if(layer.options.match(url_channel_regex)) {
|
||||
const groups = url_channel_regex.exec(layer.options);
|
||||
|
@ -205,7 +205,7 @@ namespace htmltags {
|
|||
}
|
||||
return origin_url.build_html_tag_open(layer);
|
||||
},
|
||||
build_html_tag_close(layer: xbbcode.TagLayer): string {
|
||||
build_html_tag_close(layer): string {
|
||||
if(layer.options) {
|
||||
if(layer.options.match(url_client_regex))
|
||||
return "</div>";
|
||||
|
|
|
@ -0,0 +1,523 @@
|
|||
namespace Modals {
|
||||
type InfoUpdateCallback = (info: ClientConnectionInfo) => any;
|
||||
export function openClientInfo(client: ClientEntry) {
|
||||
let modal: Modal;
|
||||
let update_callbacks: InfoUpdateCallback[] = [];
|
||||
|
||||
modal = createModal({
|
||||
header: tr("Profile Information: ") + client.clientNickName(),
|
||||
body: () => {
|
||||
const template = $("#tmpl_client_info").renderTag();
|
||||
|
||||
/* the tab functionality */
|
||||
{
|
||||
const container_tabs = template.find(".container-categories");
|
||||
container_tabs.find(".categories .entry").on('click', event => {
|
||||
const entry = $(event.target);
|
||||
|
||||
container_tabs.find(".bodies > .body").addClass("hidden");
|
||||
container_tabs.find(".categories > .selected").removeClass("selected");
|
||||
|
||||
entry.addClass("selected");
|
||||
container_tabs.find(".bodies > .body." + entry.attr("container")).removeClass("hidden");
|
||||
});
|
||||
|
||||
container_tabs.find(".entry").first().trigger('click');
|
||||
}
|
||||
|
||||
apply_static_info(client, template, modal, update_callbacks);
|
||||
apply_client_status(client, template, modal, update_callbacks);
|
||||
apply_basic_info(client, template.find(".container-basic"), modal, update_callbacks);
|
||||
apply_groups(client, template.find(".container-groups"), modal, update_callbacks);
|
||||
apply_packets(client, template.find(".container-packets"), modal, update_callbacks);
|
||||
|
||||
tooltip(template);
|
||||
return template.children();
|
||||
},
|
||||
footer: null
|
||||
});
|
||||
|
||||
const updater = setInterval(() => {
|
||||
client.request_connection_info().then(info => update_callbacks.forEach(e => e(info)));
|
||||
}, 1000);
|
||||
|
||||
modal.htmlTag.find(".modal-body").addClass("modal-client-info");
|
||||
modal.open();
|
||||
modal.close_listener.push(() => clearInterval(updater));
|
||||
}
|
||||
|
||||
const TIME_SECOND = 1000;
|
||||
const TIME_MINUTE = 60 * TIME_SECOND;
|
||||
const TIME_HOUR = 60 * TIME_MINUTE;
|
||||
const TIME_DAY = 24 * TIME_HOUR;
|
||||
const TIME_WEEK = 7 * TIME_DAY;
|
||||
|
||||
function format_time(time: number, default_value: string) {
|
||||
let result = "";
|
||||
if(time > TIME_WEEK) {
|
||||
const amount = Math.floor(time / TIME_WEEK);
|
||||
result += " " + amount + " " + (amount > 1 ? tr("Weeks") : tr("Week"));
|
||||
time -= amount * TIME_WEEK;
|
||||
}
|
||||
|
||||
if(time > TIME_DAY) {
|
||||
const amount = Math.floor(time / TIME_DAY);
|
||||
result += " " + amount + " " + (amount > 1 ? tr("Days") : tr("Day"));
|
||||
time -= amount * TIME_DAY;
|
||||
}
|
||||
|
||||
if(time > TIME_HOUR) {
|
||||
const amount = Math.floor(time / TIME_HOUR);
|
||||
result += " " + amount + " " + (amount > 1 ? tr("Hours") : tr("Hour"));
|
||||
time -= amount * TIME_HOUR;
|
||||
}
|
||||
|
||||
if(time > TIME_MINUTE) {
|
||||
const amount = Math.floor(time / TIME_MINUTE);
|
||||
result += " " + amount + " " + (amount > 1 ? tr("Minutes") : tr("Minute"));
|
||||
time -= amount * TIME_MINUTE;
|
||||
}
|
||||
|
||||
if(time > TIME_SECOND) {
|
||||
const amount = Math.floor(time / TIME_SECOND);
|
||||
result += " " + amount + " " + (amount > 1 ? tr("Seconds") : tr("Second"));
|
||||
time -= amount * TIME_SECOND;
|
||||
}
|
||||
|
||||
return result.length > 0 ? result.substring(1) : default_value;
|
||||
}
|
||||
|
||||
function apply_static_info(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) {
|
||||
tag.find(".container-avatar").append(
|
||||
client.channelTree.client.fileManager.avatars.generate_chat_tag({database_id: client.properties.client_database_id, id: client.clientId()}, client.properties.client_unique_identifier)
|
||||
);
|
||||
|
||||
tag.find(".container-name").append(
|
||||
client.createChatTag()
|
||||
);
|
||||
|
||||
tag.find(".client-description").text(
|
||||
client.properties.client_description
|
||||
);
|
||||
}
|
||||
|
||||
function apply_client_status(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) {
|
||||
tag.find(".status-output-disabled").toggle(!client.properties.client_output_hardware);
|
||||
tag.find(".status-input-disabled").toggle(!client.properties.client_input_hardware);
|
||||
|
||||
tag.find(".status-output-muted").toggle(client.properties.client_output_muted);
|
||||
tag.find(".status-input-muted").toggle(client.properties.client_input_muted);
|
||||
|
||||
|
||||
tag.find(".status-away").toggle(client.properties.client_away);
|
||||
if(client.properties.client_away_message) {
|
||||
tag.find(".container-away-message").show().find("a").text(client.properties.client_away_message);
|
||||
} else {
|
||||
tag.find(".container-away-message").hide();
|
||||
}
|
||||
}
|
||||
|
||||
function apply_basic_info(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) {
|
||||
/* Unique ID */
|
||||
{
|
||||
const container = tag.find(".property-unique-id");
|
||||
|
||||
container.find(".value a").text(client.clientUid());
|
||||
container.find(".value-dbid").text(client.properties.client_database_id);
|
||||
|
||||
container.find(".button-copy").on('click', event => {
|
||||
copy_to_clipboard(client.clientUid());
|
||||
createInfoModal(tr("Unique ID copied"), tr("The unique id has been copied to your clipboard!")).open();
|
||||
});
|
||||
}
|
||||
|
||||
/* TeaForo */
|
||||
{
|
||||
const container = tag.find(".property-teaforo .value").empty();
|
||||
|
||||
if(client.properties.client_teaforo_id) {
|
||||
container.children().remove();
|
||||
|
||||
let text = client.properties.client_teaforo_name;
|
||||
if((client.properties.client_teaforo_flags & 0x01) > 0)
|
||||
text += " (" + tr("Banned") + ")";
|
||||
if((client.properties.client_teaforo_flags & 0x02) > 0)
|
||||
text += " (" + tr("Stuff") + ")";
|
||||
if((client.properties.client_teaforo_flags & 0x04) > 0)
|
||||
text += " (" + tr("Premium") + ")";
|
||||
|
||||
$.spawn("a")
|
||||
.attr("href", "https://forum.teaspeak.de/index.php?members/" + client.properties.client_teaforo_id)
|
||||
.attr("target", "_blank")
|
||||
.text(text)
|
||||
.appendTo(container);
|
||||
} else {
|
||||
container.append($.spawn("a").text(tr("Not connected")));
|
||||
}
|
||||
}
|
||||
|
||||
/* Version */
|
||||
{
|
||||
const container = tag.find(".property-version");
|
||||
|
||||
let version_full = client.properties.client_version;
|
||||
let version = version_full.substring(0, version_full.indexOf(" "));
|
||||
|
||||
container.find(".value").empty().append(
|
||||
$.spawn("a").attr("title", version_full).text(version),
|
||||
$.spawn("a").addClass("a-on").text("on"),
|
||||
$.spawn("a").text(client.properties.client_platform)
|
||||
);
|
||||
|
||||
const container_timestamp = container.find(".container-tooltip");
|
||||
|
||||
let timestamp = -1;
|
||||
version_full.replace(/\[build: ?([0-9]+)]/gmi, (group, ts) => {
|
||||
timestamp = parseInt(ts);
|
||||
return "";
|
||||
});
|
||||
if(timestamp > 0) {
|
||||
container_timestamp.find(".value-timestamp").text(moment(timestamp * 1000).format('MMMM Do YYYY, h:mm:ss a'));
|
||||
container_timestamp.show();
|
||||
} else {
|
||||
container_timestamp.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/* Country */
|
||||
{
|
||||
const container = tag.find(".property-country");
|
||||
container.find(".value").empty().append(
|
||||
$.spawn("div").addClass("country flag-" + client.properties.client_country.toLowerCase()),
|
||||
$.spawn("a").text(i18n.country_name(client.properties.client_country, tr("Unknown")))
|
||||
);
|
||||
}
|
||||
|
||||
/* IP Address */
|
||||
{
|
||||
const container = tag.find(".property-ip");
|
||||
const value = container.find(".value a");
|
||||
value.text(tr("loading..."));
|
||||
|
||||
container.find(".button-copy").on('click', event => {
|
||||
copy_to_clipboard(value.text());
|
||||
createInfoModal(tr("Client IP copied"), tr("The client IP has been copied to your clipboard!")).open();
|
||||
});
|
||||
|
||||
callbacks.push(info => {
|
||||
value.text(info.connection_client_ip ? (info.connection_client_ip + ":" + info.connection_client_port) : tr("Hidden"));
|
||||
});
|
||||
}
|
||||
|
||||
/* first connected */
|
||||
{
|
||||
const container = tag.find(".property-first-connected");
|
||||
|
||||
container.find(".value a").text(tr("loading..."));
|
||||
client.updateClientVariables().then(() => {
|
||||
container.find(".value a").text(moment(client.properties.client_created * 1000).format('MMMM Do YYYY, h:mm:ss a'));
|
||||
}).catch(error => {
|
||||
container.find(".value a").text(tr("error"));
|
||||
});
|
||||
}
|
||||
|
||||
/* connect count */
|
||||
{
|
||||
const container = tag.find(".property-connect-count");
|
||||
|
||||
container.find(".value a").text(tr("loading..."));
|
||||
client.updateClientVariables().then(() => {
|
||||
container.find(".value a").text(client.properties.client_totalconnections);
|
||||
}).catch(error => {
|
||||
container.find(".value a").text(tr("error"));
|
||||
});
|
||||
}
|
||||
|
||||
/* Online since */
|
||||
{
|
||||
const container = tag.find(".property-online-since");
|
||||
|
||||
const node = container.find(".value a")[0];
|
||||
if(node) {
|
||||
const update = () => {
|
||||
node.innerText = format_time(client.calculateOnlineTime() * 1000, tr("0 Seconds"));
|
||||
};
|
||||
|
||||
callbacks.push(update); /* keep it in sync with all other updates. Else it looks wired */
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
/* Idle time */
|
||||
{
|
||||
const container = tag.find(".property-idle-time");
|
||||
const node = container.find(".value a")[0];
|
||||
if(node) {
|
||||
callbacks.push(info => {
|
||||
node.innerText = format_time(info.connection_idle_time, tr("Currently active"));
|
||||
});
|
||||
node.innerText = tr("loading...");
|
||||
}
|
||||
}
|
||||
|
||||
/* ping */
|
||||
{
|
||||
const container = tag.find(".property-ping");
|
||||
const node = container.find(".value a")[0];
|
||||
|
||||
if(node) {
|
||||
callbacks.push(info => {
|
||||
if(info.connection_ping >= 0)
|
||||
node.innerText = info.connection_ping.toFixed(0) + "ms ± " + info.connection_ping_deviation.toFixed(2) + "ms";
|
||||
else if(info.connection_ping == -1 && info.connection_ping_deviation == -1)
|
||||
node.innerText = tr("Not calculated");
|
||||
else
|
||||
node.innerText = tr("loading...");
|
||||
});
|
||||
node.innerText = tr("loading...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function apply_groups(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) {
|
||||
/* server groups */
|
||||
{
|
||||
const container_entries = tag.find(".entries");
|
||||
const container_empty = tag.find(".container-default-groups");
|
||||
|
||||
const update_groups = () => {
|
||||
container_entries.empty();
|
||||
container_empty.show();
|
||||
|
||||
for(const group_id of client.assignedServerGroupIds()) {
|
||||
if(group_id == client.channelTree.server.properties.virtualserver_default_server_group)
|
||||
continue;
|
||||
|
||||
const group = client.channelTree.client.groups.serverGroup(group_id);
|
||||
if(!group) continue; //This shall never happen!
|
||||
|
||||
container_empty.hide();
|
||||
container_entries.append($.spawn("div").addClass("entry").append(
|
||||
client.channelTree.client.fileManager.icons.generateTag(group.properties.iconid),
|
||||
$.spawn("a").addClass("name").text(group.name + " (" + group.id + ")"),
|
||||
$.spawn("div").addClass("button-delete").append(
|
||||
$.spawn("div").addClass("icon_em client-delete").attr("title", tr("Delete group")).on('click', event => {
|
||||
client.channelTree.client.serverConnection.send_command("servergroupdelclient", {
|
||||
sgid: group.id,
|
||||
cldbid: client.properties.client_database_id
|
||||
}).then(result => update_groups());
|
||||
})
|
||||
).toggleClass("visible",
|
||||
client.channelTree.client.permissions.neededPermission(PermissionType.I_SERVER_GROUP_MEMBER_REMOVE_POWER).granted(group.requiredMemberRemovePower) ||
|
||||
client.clientId() == client.channelTree.client.getClientId() && client.channelTree.client.permissions.neededPermission(PermissionType.I_SERVER_GROUP_SELF_REMOVE_POWER).granted(group.requiredMemberRemovePower)
|
||||
)
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
tag.find(".button-group-add").on('click', event => {
|
||||
Modals.createServerGroupAssignmentModal(client, (group, flag) => {
|
||||
if(flag) {
|
||||
return client.channelTree.client.serverConnection.send_command("servergroupaddclient", {
|
||||
sgid: group.id,
|
||||
cldbid: client.properties.client_database_id
|
||||
}).then(result => { update_groups(); return true; });
|
||||
} else
|
||||
return client.channelTree.client.serverConnection.send_command("servergroupdelclient", {
|
||||
sgid: group.id,
|
||||
cldbid: client.properties.client_database_id
|
||||
}).then(result => { update_groups(); return true; });
|
||||
});
|
||||
});
|
||||
|
||||
update_groups();
|
||||
}
|
||||
}
|
||||
|
||||
function apply_packets(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) {
|
||||
|
||||
/* Packet Loss */
|
||||
{
|
||||
const container = tag.find(".statistic-packet-loss");
|
||||
const node_downstream = container.find(".downstream .value")[0];
|
||||
const node_upstream = container.find(".upstream .value")[0];
|
||||
|
||||
if(node_downstream) {
|
||||
callbacks.push(info => {
|
||||
node_downstream.innerText = info.connection_server2client_packetloss_control < 0 ? tr("Not calculated") : (info.connection_server2client_packetloss_control || 0).toFixed();
|
||||
});
|
||||
node_downstream.innerText = tr("loading...");
|
||||
}
|
||||
|
||||
if(node_upstream) {
|
||||
callbacks.push(info => {
|
||||
node_upstream.innerText = info.connection_client2server_packetloss_total < 0 ? tr("Not calculated") : (info.connection_client2server_packetloss_total || 0).toFixed();
|
||||
});
|
||||
node_upstream.innerText = tr("loading...");
|
||||
}
|
||||
}
|
||||
|
||||
/* Packets transmitted */
|
||||
{
|
||||
const container = tag.find(".statistic-transmitted-packets");
|
||||
const node_downstream = container.find(".downstream .value")[0];
|
||||
const node_upstream = container.find(".upstream .value")[0];
|
||||
|
||||
if(node_downstream) {
|
||||
callbacks.push(info => {
|
||||
let packets = 0;
|
||||
packets += info.connection_packets_received_speech > 0 ? info.connection_packets_received_speech : 0;
|
||||
packets += info.connection_packets_received_control > 0 ? info.connection_packets_received_control : 0;
|
||||
packets += info.connection_packets_received_keepalive > 0 ? info.connection_packets_received_keepalive : 0;
|
||||
if(packets == 0 && info.connection_packets_received_keepalive == -1)
|
||||
node_downstream.innerText = tr("Not calculated");
|
||||
else
|
||||
node_downstream.innerText = MessageHelper.format_number(packets, {unit: "Packets"});
|
||||
});
|
||||
node_downstream.innerText = tr("loading...");
|
||||
}
|
||||
|
||||
if(node_upstream) {
|
||||
callbacks.push(info => {
|
||||
let packets = 0;
|
||||
packets += info.connection_packets_sent_speech > 0 ? info.connection_packets_sent_speech : 0;
|
||||
packets += info.connection_packets_sent_control > 0 ? info.connection_packets_sent_control : 0;
|
||||
packets += info.connection_packets_sent_keepalive > 0 ? info.connection_packets_sent_keepalive : 0;
|
||||
if(packets == 0 && info.connection_packets_sent_keepalive == -1)
|
||||
node_upstream.innerText = tr("Not calculated");
|
||||
else
|
||||
node_upstream.innerText = MessageHelper.format_number(packets, {unit: "Packets"});
|
||||
});
|
||||
node_upstream.innerText = tr("loading...");
|
||||
}
|
||||
}
|
||||
|
||||
/* Bytes transmitted */
|
||||
{
|
||||
const container = tag.find(".statistic-transmitted-bytes");
|
||||
const node_downstream = container.find(".downstream .value")[0];
|
||||
const node_upstream = container.find(".upstream .value")[0];
|
||||
|
||||
if(node_downstream) {
|
||||
callbacks.push(info => {
|
||||
let bytes = 0;
|
||||
bytes += info.connection_bytes_received_speech > 0 ? info.connection_bytes_received_speech : 0;
|
||||
bytes += info.connection_bytes_received_control > 0 ? info.connection_bytes_received_control : 0;
|
||||
bytes += info.connection_bytes_received_keepalive > 0 ? info.connection_bytes_received_keepalive : 0;
|
||||
if(bytes == 0 && info.connection_bytes_received_keepalive == -1)
|
||||
node_downstream.innerText = tr("Not calculated");
|
||||
else
|
||||
node_downstream.innerText = MessageHelper.network.format_bytes(bytes);
|
||||
});
|
||||
node_downstream.innerText = tr("loading...");
|
||||
}
|
||||
|
||||
if(node_upstream) {
|
||||
callbacks.push(info => {
|
||||
let bytes = 0;
|
||||
bytes += info.connection_bytes_sent_speech > 0 ? info.connection_bytes_sent_speech : 0;
|
||||
bytes += info.connection_bytes_sent_control > 0 ? info.connection_bytes_sent_control : 0;
|
||||
bytes += info.connection_bytes_sent_keepalive > 0 ? info.connection_bytes_sent_keepalive : 0;
|
||||
if(bytes == 0 && info.connection_bytes_sent_keepalive == -1)
|
||||
node_upstream.innerText = tr("Not calculated");
|
||||
else
|
||||
node_upstream.innerText = MessageHelper.network.format_bytes(bytes);
|
||||
});
|
||||
node_upstream.innerText = tr("loading...");
|
||||
}
|
||||
}
|
||||
|
||||
/* Bandwidth second */
|
||||
{
|
||||
const container = tag.find(".statistic-bandwidth-second");
|
||||
const node_downstream = container.find(".downstream .value")[0];
|
||||
const node_upstream = container.find(".upstream .value")[0];
|
||||
|
||||
if(node_downstream) {
|
||||
callbacks.push(info => {
|
||||
let bytes = 0;
|
||||
bytes += info.connection_bandwidth_received_last_second_speech > 0 ? info.connection_bandwidth_received_last_second_speech : 0;
|
||||
bytes += info.connection_bandwidth_received_last_second_control > 0 ? info.connection_bandwidth_received_last_second_control : 0;
|
||||
bytes += info.connection_bandwidth_received_last_second_keepalive > 0 ? info.connection_bandwidth_received_last_second_keepalive : 0;
|
||||
if(bytes == 0 && info.connection_bandwidth_received_last_second_keepalive == -1)
|
||||
node_downstream.innerText = tr("Not calculated");
|
||||
else
|
||||
node_downstream.innerText = MessageHelper.network.format_bytes(bytes, {time: "s"});
|
||||
});
|
||||
node_downstream.innerText = tr("loading...");
|
||||
}
|
||||
|
||||
if(node_upstream) {
|
||||
callbacks.push(info => {
|
||||
let bytes = 0;
|
||||
bytes += info.connection_bandwidth_sent_last_second_speech > 0 ? info.connection_bandwidth_sent_last_second_speech : 0;
|
||||
bytes += info.connection_bandwidth_sent_last_second_control > 0 ? info.connection_bandwidth_sent_last_second_control : 0;
|
||||
bytes += info.connection_bandwidth_sent_last_second_keepalive > 0 ? info.connection_bandwidth_sent_last_second_keepalive : 0;
|
||||
if(bytes == 0 && info.connection_bandwidth_sent_last_second_keepalive == -1)
|
||||
node_upstream.innerText = tr("Not calculated");
|
||||
else
|
||||
node_upstream.innerText = MessageHelper.network.format_bytes(bytes, {time: "s"});
|
||||
});
|
||||
node_upstream.innerText = tr("loading...");
|
||||
}
|
||||
}
|
||||
|
||||
/* Bandwidth minute */
|
||||
{
|
||||
const container = tag.find(".statistic-bandwidth-minute");
|
||||
const node_downstream = container.find(".downstream .value")[0];
|
||||
const node_upstream = container.find(".upstream .value")[0];
|
||||
|
||||
if(node_downstream) {
|
||||
callbacks.push(info => {
|
||||
let bytes = 0;
|
||||
bytes += info.connection_bandwidth_received_last_minute_speech > 0 ? info.connection_bandwidth_received_last_minute_speech : 0;
|
||||
bytes += info.connection_bandwidth_received_last_minute_control > 0 ? info.connection_bandwidth_received_last_minute_control : 0;
|
||||
bytes += info.connection_bandwidth_received_last_minute_keepalive > 0 ? info.connection_bandwidth_received_last_minute_keepalive : 0;
|
||||
if(bytes == 0 && info.connection_bandwidth_received_last_minute_keepalive == -1)
|
||||
node_downstream.innerText = tr("Not calculated");
|
||||
else
|
||||
node_downstream.innerText = MessageHelper.network.format_bytes(bytes, {time: "s"});
|
||||
});
|
||||
node_downstream.innerText = tr("loading...");
|
||||
}
|
||||
|
||||
if(node_upstream) {
|
||||
callbacks.push(info => {
|
||||
let bytes = 0;
|
||||
bytes += info.connection_bandwidth_sent_last_minute_speech > 0 ? info.connection_bandwidth_sent_last_minute_speech : 0;
|
||||
bytes += info.connection_bandwidth_sent_last_minute_control > 0 ? info.connection_bandwidth_sent_last_minute_control : 0;
|
||||
bytes += info.connection_bandwidth_sent_last_minute_keepalive > 0 ? info.connection_bandwidth_sent_last_minute_keepalive : 0;
|
||||
if(bytes == 0 && info.connection_bandwidth_sent_last_minute_keepalive == -1)
|
||||
node_upstream.innerText = tr("Not calculated");
|
||||
else
|
||||
node_upstream.innerText = MessageHelper.network.format_bytes(bytes, {time: "s"});
|
||||
});
|
||||
node_upstream.innerText = tr("loading...");
|
||||
}
|
||||
}
|
||||
|
||||
/* quota */
|
||||
{
|
||||
const container = tag.find(".statistic-quota");
|
||||
const node_downstream = container.find(".downstream .value")[0];
|
||||
const node_upstream = container.find(".upstream .value")[0];
|
||||
|
||||
if(node_downstream) {
|
||||
client.updateClientVariables().then(info => {
|
||||
//TODO: Test for own client info and if so then show the max quota (needed permission)
|
||||
node_downstream.innerText = MessageHelper.network.format_bytes(client.properties.client_month_bytes_downloaded, {exact: false});
|
||||
});
|
||||
node_downstream.innerText = tr("loading...");
|
||||
}
|
||||
|
||||
if(node_upstream) {
|
||||
client.updateClientVariables().then(info => {
|
||||
//TODO: Test for own client info and if so then show the max quota (needed permission)
|
||||
node_upstream.innerText = MessageHelper.network.format_bytes(client.properties.client_month_bytes_uploaded, {exact: false});
|
||||
});
|
||||
node_upstream.innerText = tr("loading...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,14 @@
|
|||
namespace Modals {
|
||||
export function spawnTeamSpeakIdentityImprove(identity: profiles.identities.TeaSpeakIdentity): Modal {
|
||||
export function spawnTeamSpeakIdentityImprove(identity: profiles.identities.TeaSpeakIdentity, name: string): Modal {
|
||||
let modal: Modal;
|
||||
let elapsed_timer: NodeJS.Timer;
|
||||
|
||||
modal = createModal({
|
||||
header: tr("Improve identity"),
|
||||
body: () => {
|
||||
let template = $("#tmpl_settings-teamspeak_improve").renderTag();
|
||||
let template = $("#tmpl_settings-teamspeak_improve").renderTag({
|
||||
identity_name: name
|
||||
});
|
||||
template = $.spawn("div").append(template);
|
||||
|
||||
let active;
|
||||
|
@ -103,11 +105,14 @@ namespace Modals {
|
|||
}).catch(error => {
|
||||
input_current_level.val("error: " + error);
|
||||
});
|
||||
return template;
|
||||
tooltip(template);
|
||||
return template.children();
|
||||
},
|
||||
footer: undefined,
|
||||
width: 750
|
||||
});
|
||||
|
||||
modal.htmlTag.find(".modal-body").addClass("modal-identity-improve modal-green");
|
||||
modal.close_listener.push(() => modal.htmlTag.find(".button-close").trigger('click'));
|
||||
modal.open();
|
||||
return modal;
|
||||
|
@ -115,78 +120,104 @@ namespace Modals {
|
|||
|
||||
export function spawnTeamSpeakIdentityImport(callback: (identity: profiles.identities.TeaSpeakIdentity) => any): Modal {
|
||||
let modal: Modal;
|
||||
let loaded_identity: profiles.identities.TeaSpeakIdentity;
|
||||
let selected_type: string;
|
||||
let identities: {[key: string]: profiles.identities.TeaSpeakIdentity} = {};
|
||||
|
||||
modal = createModal({
|
||||
header: tr("Import identity"),
|
||||
body: () => {
|
||||
let template = $("#tmpl_settings-teamspeak_import").renderTag();
|
||||
template = $.spawn("div").append(template);
|
||||
|
||||
template.find(".button-load-file").on('click', event => template.find(".input-file").trigger('click'));
|
||||
|
||||
const button_import = template.find(".button-import");
|
||||
const set_error = message => {
|
||||
template.find(".success").hide();
|
||||
const button_file_select = template.find(".button-load-file");
|
||||
|
||||
const container_status = template.find(".container-status");
|
||||
const input_text = template.find(".input-identity-text");
|
||||
const input_file = template.find(".file-selector");
|
||||
|
||||
const set_status = (message: string | undefined, type: "error" | "loading" | "success") => {
|
||||
container_status.toggleClass("hidden", !message);
|
||||
if(message) {
|
||||
template.find(".error").text(message).show();
|
||||
button_import.prop("disabled", true);
|
||||
} else
|
||||
template.find(".error").hide();
|
||||
container_status.toggleClass("error", type === "error");
|
||||
container_status.toggleClass("loading", type === "loading");
|
||||
container_status.find("a").text(message);
|
||||
}
|
||||
};
|
||||
|
||||
button_file_select.on('click', event => input_file.trigger('click'));
|
||||
|
||||
template.find("input[name='type']").on('change', event => {
|
||||
const type = (event.target as HTMLInputElement).value;
|
||||
|
||||
button_file_select.prop("disabled", type !== "file");
|
||||
input_text.prop("disabled", type !== "text");
|
||||
|
||||
selected_type = type;
|
||||
button_import.prop("disabled", !identities[type]);
|
||||
});
|
||||
template.find("input[name='type'][value='file']").prop("checked", true).trigger("change");
|
||||
|
||||
const import_identity = (data: string, ini: boolean) => {
|
||||
set_status(tr("Parsing identity"), "loading");
|
||||
profiles.identities.TeaSpeakIdentity.import_ts(data, ini).then(identity => {
|
||||
loaded_identity = identity;
|
||||
set_error("");
|
||||
identities[selected_type] = identity;
|
||||
set_status("Identity parsed successfully.", "success");
|
||||
button_import.prop("disabled", false);
|
||||
template.find(".success").show();
|
||||
}).catch(error => {
|
||||
set_error("Failed to load identity: " + error);
|
||||
set_status(tr("Failed to parse identity: ") + error, "error");
|
||||
});
|
||||
};
|
||||
|
||||
{ /* file select button */
|
||||
template.find(".input-file").on('change', event => {
|
||||
/* file select button */
|
||||
input_file.on('change', event => {
|
||||
const element = event.target as HTMLInputElement;
|
||||
const file_reader = new FileReader();
|
||||
|
||||
set_status(tr("Loading file"), "loading");
|
||||
file_reader.onload = function () {
|
||||
import_identity(file_reader.result as string, true);
|
||||
};
|
||||
|
||||
file_reader.onerror = ev => {
|
||||
console.error(tr("Failed to read give identity file: %o"), ev);
|
||||
set_error(tr("Failed to read file!"));
|
||||
set_status(tr("Failed to read the identity file."), "error");
|
||||
return;
|
||||
};
|
||||
|
||||
if (element.files && element.files.length > 0)
|
||||
file_reader.readAsText(element.files[0]);
|
||||
});
|
||||
|
||||
input_text.on('change keyup', event => {
|
||||
const text = input_text.val() as string;
|
||||
if(!text) {
|
||||
set_status("", "success");
|
||||
return;
|
||||
}
|
||||
|
||||
{ /* text input */
|
||||
template.find(".button-load-text").on('click', event => {
|
||||
createInputModal("Import identity from text", "Please paste your idenity bellow<br>", text => text.length > 0 && text.indexOf('V') != -1, result => {
|
||||
if (result)
|
||||
import_identity(result as string, false);
|
||||
}).open();
|
||||
});
|
||||
if(text.indexOf('V') == -1) {
|
||||
set_status(tr("Invalid identity string"), "error");
|
||||
return;
|
||||
}
|
||||
|
||||
import_identity(text, false);
|
||||
});
|
||||
|
||||
button_import.on('click', event => {
|
||||
modal.close();
|
||||
callback(loaded_identity);
|
||||
callback(identities[selected_type]);
|
||||
});
|
||||
|
||||
set_error("");
|
||||
set_status("", "success");
|
||||
button_import.prop("disabled", true);
|
||||
return template;
|
||||
return template.children();
|
||||
},
|
||||
footer: undefined,
|
||||
width: 750
|
||||
});
|
||||
|
||||
modal.htmlTag.find(".modal-body").addClass("modal-identity-import modal-green");
|
||||
modal.open();
|
||||
return modal;
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ namespace Modals {
|
|||
}
|
||||
}
|
||||
|
||||
type PokeInvoker = {
|
||||
export type PokeInvoker = {
|
||||
name: string,
|
||||
id: number,
|
||||
unique_id: string
|
||||
|
|
|
@ -327,42 +327,14 @@ namespace Modals {
|
|||
}
|
||||
}
|
||||
|
||||
const format_unit = (value: number) => {
|
||||
const KB = 1024;
|
||||
const MB = 1024 * KB;
|
||||
const GB = 1024 * MB;
|
||||
const TB = 1024 * GB;
|
||||
|
||||
let points = value.toFixed(0).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
||||
|
||||
let v, unit;
|
||||
if(value > 2 * TB) {
|
||||
unit = "TB";
|
||||
v = value / TB;
|
||||
} else if(value > GB) {
|
||||
unit = "GB";
|
||||
v = value / GB;
|
||||
} else if(value > MB) {
|
||||
unit = "MB";
|
||||
v = value / MB;
|
||||
} else if(value > KB) {
|
||||
unit = "KB";
|
||||
v = value / KB;
|
||||
} else {
|
||||
unit = "";
|
||||
v = value;
|
||||
}
|
||||
return points + " Bytes" + (unit ? (" / " + v.toFixed(2) + " " + unit) : "");
|
||||
};
|
||||
|
||||
/* quota info */
|
||||
{
|
||||
server.updateProperties().then(() => {
|
||||
tag.find(".value.virtualserver_month_bytes_downloaded").text(format_unit(server.properties.virtualserver_month_bytes_downloaded));
|
||||
tag.find(".value.virtualserver_month_bytes_uploaded").text(format_unit(server.properties.virtualserver_month_bytes_uploaded));
|
||||
tag.find(".value.virtualserver_month_bytes_downloaded").text(MessageHelper.network.format_bytes(server.properties.virtualserver_month_bytes_downloaded));
|
||||
tag.find(".value.virtualserver_month_bytes_uploaded").text(MessageHelper.network.format_bytes(server.properties.virtualserver_month_bytes_uploaded));
|
||||
|
||||
tag.find(".value.virtualserver_total_bytes_downloaded").text(format_unit(server.properties.virtualserver_total_bytes_downloaded));
|
||||
tag.find(".value.virtualserver_total_bytes_uploaded").text(format_unit(server.properties.virtualserver_total_bytes_uploaded));
|
||||
tag.find(".value.virtualserver_total_bytes_downloaded").text(MessageHelper.network.format_bytes(server.properties.virtualserver_total_bytes_downloaded));
|
||||
tag.find(".value.virtualserver_total_bytes_uploaded").text(MessageHelper.network.format_bytes(server.properties.virtualserver_total_bytes_uploaded));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -381,14 +353,14 @@ namespace Modals {
|
|||
|
||||
server.request_connection_info().then(info => {
|
||||
if(info.connection_filetransfer_bytes_sent_month && month_bytes_downloaded)
|
||||
month_bytes_downloaded.innerText = format_unit(info.connection_filetransfer_bytes_sent_month);
|
||||
month_bytes_downloaded.innerText = MessageHelper.network.format_bytes(info.connection_filetransfer_bytes_sent_month);
|
||||
if(info.connection_filetransfer_bytes_received_month && month_bytes_uploaded)
|
||||
month_bytes_uploaded.innerText = format_unit(info.connection_filetransfer_bytes_received_month);
|
||||
month_bytes_uploaded.innerText = MessageHelper.network.format_bytes(info.connection_filetransfer_bytes_received_month);
|
||||
|
||||
if(info.connection_filetransfer_bytes_sent_total && total_bytes_downloaded)
|
||||
total_bytes_downloaded.innerText = format_unit(info.connection_filetransfer_bytes_sent_total);
|
||||
total_bytes_downloaded.innerText = MessageHelper.network.format_bytes(info.connection_filetransfer_bytes_sent_total);
|
||||
if(info.connection_filetransfer_bytes_received_total && total_bytes_uploaded)
|
||||
total_bytes_uploaded.innerText = format_unit(info.connection_filetransfer_bytes_received_total);
|
||||
total_bytes_uploaded.innerText = MessageHelper.network.format_bytes(info.connection_filetransfer_bytes_received_total);
|
||||
});
|
||||
}, 1000);
|
||||
modal.close_listener.push(() => clearInterval(id));
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
namespace Modals {
|
||||
type InfoUpdateCallback = (info: ServerConnectionInfo | boolean) => any;
|
||||
export function openServerInfo(server: ServerEntry) {
|
||||
let modal: Modal;
|
||||
let update_callbacks: InfoUpdateCallback[] = [];
|
||||
|
||||
modal = createModal({
|
||||
header: tr("Server Information: ") + server.properties.virtualserver_name,
|
||||
body: () => {
|
||||
const template = $("#tmpl_server_info").renderTag();
|
||||
|
||||
apply_hostbanner(server, template.find(".container-top"));
|
||||
apply_category_1(server, template, update_callbacks);
|
||||
apply_category_2(server, template, update_callbacks);
|
||||
apply_category_3(server, template, update_callbacks);
|
||||
|
||||
tooltip(template);
|
||||
return template.children();
|
||||
},
|
||||
footer: null,
|
||||
min_width: "25em"
|
||||
});
|
||||
|
||||
const updater = setInterval(() => {
|
||||
server.request_connection_info().then(info => update_callbacks.forEach(e => e(info))).catch(error => update_callbacks.forEach(e => e(false)));
|
||||
}, 1000);
|
||||
|
||||
|
||||
modal.htmlTag.find(".button-close").on('click', event => modal.close());
|
||||
modal.htmlTag.find(".button-show-bandwidth").on('click', event => {
|
||||
//TODO!
|
||||
});
|
||||
|
||||
modal.htmlTag.find(".modal-body").addClass("modal-server-info");
|
||||
modal.open();
|
||||
modal.close_listener.push(() => clearInterval(updater));
|
||||
}
|
||||
|
||||
function apply_hostbanner(server: ServerEntry, tag: JQuery) {
|
||||
let container: JQuery;
|
||||
tag.empty().append(
|
||||
container = $.spawn("div").addClass("container-hostbanner")
|
||||
).addClass("hidden");
|
||||
|
||||
const htag = Hostbanner.generate_tag(server.properties.virtualserver_hostbanner_gfx_url, server.properties.virtualserver_hostbanner_gfx_interval, server.properties.virtualserver_hostbanner_mode);
|
||||
htag.then(t => {
|
||||
if(!t) return;
|
||||
|
||||
tag.removeClass("hidden");
|
||||
container.append(t);
|
||||
});
|
||||
}
|
||||
|
||||
function apply_category_1(server: ServerEntry, tag: JQuery, update_callbacks: InfoUpdateCallback[]) {
|
||||
/* server name */
|
||||
{
|
||||
const container = tag.find(".server-name");
|
||||
container.text(server.properties.virtualserver_name);
|
||||
}
|
||||
|
||||
/* server region */
|
||||
{
|
||||
const container = tag.find(".server-region").empty();
|
||||
container.append(
|
||||
$.spawn("div").addClass("country flag-" + server.properties.virtualserver_country_code.toLowerCase()),
|
||||
$.spawn("a").text(i18n.country_name(server.properties.virtualserver_country_code, tr("Global")))
|
||||
);
|
||||
}
|
||||
|
||||
/* slots */
|
||||
{
|
||||
const container = tag.find(".server-slots");
|
||||
|
||||
let text = server.properties.virtualserver_clientsonline + "/" + server.properties.virtualserver_maxclients;
|
||||
if(server.properties.virtualserver_queryclientsonline)
|
||||
text += " +" + (server.properties.virtualserver_queryclientsonline > 1 ?
|
||||
server.properties.virtualserver_queryclientsonline + " " + tr("Queries") :
|
||||
server.properties.virtualserver_queryclientsonline + " " + tr("Query"));
|
||||
if(server.properties.virtualserver_reserved_slots)
|
||||
text += " (" + server.properties.virtualserver_reserved_slots + " " + tr("Reserved") + ")";
|
||||
|
||||
container.text(text);
|
||||
}
|
||||
|
||||
/* first run */
|
||||
{
|
||||
const container = tag.find(".server-first-run");
|
||||
|
||||
container.text(
|
||||
server.properties.virtualserver_created > 0 ?
|
||||
moment(server.properties.virtualserver_created * 1000).format('MMMM Do YYYY, h:mm:ss a') :
|
||||
tr("Unknown")
|
||||
);
|
||||
}
|
||||
|
||||
/* uptime */
|
||||
{
|
||||
const container = tag.find(".server-uptime");
|
||||
const update = () => container.text(MessageHelper.format_time(server.calculateUptime() * 1000, tr("just started")));
|
||||
update_callbacks.push(update);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
function apply_category_2(server: ServerEntry, tag: JQuery, update_callbacks: InfoUpdateCallback[]) {
|
||||
/* ip */
|
||||
{
|
||||
const container = tag.find(".server-ip");
|
||||
container.text(server.remote_address.host + (server.remote_address.port == 9987 ? "" : (":" + server.remote_address.port)))
|
||||
}
|
||||
|
||||
/* version */
|
||||
{
|
||||
const container = tag.find(".server-version");
|
||||
|
||||
let timestamp = -1;
|
||||
const version = (server.properties.virtualserver_version || "unknwon").replace(/ ?\[build: ?([0-9]+)]/gmi, (group, ts) => {
|
||||
timestamp = parseInt(ts);
|
||||
return "";
|
||||
});
|
||||
|
||||
container.find("a").text(version);
|
||||
container.find(".container-tooltip").toggle(timestamp > 0).find(".tooltip a").text(
|
||||
moment(timestamp * 1000).format('[Build timestamp:] YYYY-MM-DD HH:mm Z')
|
||||
);
|
||||
}
|
||||
|
||||
/* platform */
|
||||
{
|
||||
const container = tag.find(".server-platform");
|
||||
container.text(server.properties.virtualserver_platform);
|
||||
}
|
||||
|
||||
/* ping */
|
||||
{
|
||||
const container = tag.find(".server-ping");
|
||||
container.text(tr("calculating..."));
|
||||
update_callbacks.push(data => {
|
||||
if(typeof(data) === "boolean")
|
||||
container.text(tr("No Permissions"));
|
||||
else
|
||||
container.text(data.connection_ping.toFixed(0) + " " + "ms");
|
||||
});
|
||||
}
|
||||
|
||||
/* packet loss */
|
||||
{
|
||||
const container = tag.find(".server-packet-loss");
|
||||
container.text(tr("calculating..."));
|
||||
update_callbacks.push(data => {
|
||||
if(typeof(data) === "boolean")
|
||||
container.text(tr("No Permissions"));
|
||||
else
|
||||
container.text(data.connection_packetloss_total.toFixed(2) + "%");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function apply_category_3(server: ServerEntry, tag: JQuery, update_callbacks: InfoUpdateCallback[]) {
|
||||
/* unique id */
|
||||
{
|
||||
const container = tag.find(".server-unique-id");
|
||||
container.text(server.properties.virtualserver_unique_identifier || tr("Unknown"));
|
||||
}
|
||||
|
||||
/* voice encryption */
|
||||
{
|
||||
const container = tag.find(".server-voice-encryption");
|
||||
if(server.properties.virtualserver_codec_encryption_mode == 0)
|
||||
container.text(tr("Globally off"));
|
||||
else if(server.properties.virtualserver_codec_encryption_mode == 1)
|
||||
container.text(tr("Individually configured per channel"));
|
||||
else
|
||||
container.text(tr("Globally on"));
|
||||
}
|
||||
|
||||
/* channel count */
|
||||
{
|
||||
const container = tag.find(".server-channel-count");
|
||||
container.text(server.properties.virtualserver_channelsonline);
|
||||
}
|
||||
|
||||
/* minimal security level */
|
||||
{
|
||||
const container = tag.find(".server-min-security-level");
|
||||
container.text(server.properties.virtualserver_needed_identity_security_level);
|
||||
}
|
||||
|
||||
/* complains */
|
||||
{
|
||||
const container = tag.find(".server-complains");
|
||||
container.text(server.properties.virtualserver_complain_autoban_count);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1005,7 +1005,7 @@ namespace Modals {
|
|||
const profile = selected_profile.identity.selected_identity(profiles.identities.IdentitifyType.TEAMSPEAK) as profiles.identities.TeaSpeakIdentity;
|
||||
if (!profile) return;
|
||||
|
||||
Modals.spawnTeamSpeakIdentityImprove(profile).close_listener.push(() => {
|
||||
Modals.spawnTeamSpeakIdentityImprove(profile, selected_profile.identity.profile_name).close_listener.push(() => {
|
||||
profiles.mark_need_save();
|
||||
for(const listener of profile_identity_changed)
|
||||
listener();
|
||||
|
|
|
@ -240,7 +240,6 @@ namespace Modals {
|
|||
{
|
||||
const pe_client = tab_right.find(".permission-editor");
|
||||
tab_right.on('show', event => {
|
||||
console.error("client channel tab show");
|
||||
editor.set_toggle_button(undefined, undefined);
|
||||
pe_client.append(editor.html_tag());
|
||||
if(connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CLIENT_PERMISSION_LIST).granted(1)) {
|
||||
|
@ -407,8 +406,6 @@ namespace Modals {
|
|||
{
|
||||
const pe_client = tab_right.find("permission-editor.client");
|
||||
tab_right.on('show', event => {
|
||||
console.error("Channel tab show");
|
||||
|
||||
editor.set_toggle_button(undefined, undefined);
|
||||
pe_client.append(editor.html_tag());
|
||||
if(connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CLIENT_PERMISSION_LIST).granted(1)) {
|
||||
|
@ -557,7 +554,6 @@ namespace Modals {
|
|||
{
|
||||
const pe_channel = tab_right.find(".permission-editor");
|
||||
tab_right.on('show', event => {
|
||||
console.error("Channel tab show");
|
||||
editor.set_toggle_button(undefined, undefined);
|
||||
pe_channel.append(editor.html_tag());
|
||||
if(connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CHANNEL_PERMISSION_LIST).granted(1))
|
||||
|
@ -672,7 +668,6 @@ namespace Modals {
|
|||
{
|
||||
const pe_server = tab_right.find(".permission-editor");
|
||||
tab_right.on('show', event => {
|
||||
console.error("Channel group tab show");
|
||||
editor.set_toggle_button(undefined, undefined);
|
||||
pe_server.append(editor.html_tag());
|
||||
if(connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CHANNELGROUP_PERMISSION_LIST).granted(1))
|
||||
|
@ -1162,7 +1157,6 @@ namespace Modals {
|
|||
{
|
||||
const pe_server = tab_right.find(".permission-editor");
|
||||
tab_right.on('show', event => {
|
||||
console.error("Server tab show");
|
||||
pe_server.append(editor.html_tag());
|
||||
if(connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_SERVERGROUP_PERMISSION_LIST).granted(1))
|
||||
editor.set_mode(PermissionEditorMode.VISIBLE);
|
||||
|
|
|
@ -16,6 +16,7 @@ class ServerProperties {
|
|||
virtualserver_queryclientsonline: number = 0;
|
||||
virtualserver_channelsonline: number = 0;
|
||||
virtualserver_uptime: number = 0;
|
||||
virtualserver_created: number = 0;
|
||||
virtualserver_maxclients: number = 0;
|
||||
virtualserver_reserved_slots: number = 0;
|
||||
|
||||
|
@ -198,12 +199,17 @@ class ServerEntry {
|
|||
name: tr("Show server info"),
|
||||
callback: () => {
|
||||
trigger_close = false;
|
||||
|
||||
//TODO
|
||||
alert("inplement me");
|
||||
Modals.openServerInfo(this);
|
||||
},
|
||||
icon_class: "client-about",
|
||||
visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)
|
||||
icon_class: "client-about"
|
||||
}, {
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-invite_buddy",
|
||||
name: tr("Invite buddy"),
|
||||
callback: () => Modals.spawnInviteEditor(this.channelTree.client)
|
||||
}, {
|
||||
type: contextmenu.MenuEntryType.HR,
|
||||
name: ''
|
||||
}, {
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-channel_switch",
|
||||
|
@ -213,10 +219,6 @@ class ServerEntry {
|
|||
this.channelTree.client.side_bar.show_channel_conversations();
|
||||
},
|
||||
visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)
|
||||
}, {
|
||||
type: contextmenu.MenuEntryType.HR,
|
||||
visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT),
|
||||
name: ''
|
||||
}, {
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-virtualserver_edit",
|
||||
|
@ -235,6 +237,10 @@ class ServerEntry {
|
|||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
}, {
|
||||
type: contextmenu.MenuEntryType.HR,
|
||||
visible: true,
|
||||
name: ''
|
||||
}, {
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-iconviewer",
|
||||
|
@ -245,13 +251,8 @@ class ServerEntry {
|
|||
icon_class: 'client-iconsview',
|
||||
name: tr("View avatars"),
|
||||
callback: () => Modals.spawnAvatarList(this.channelTree.client)
|
||||
}, {
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-invite_buddy",
|
||||
name: tr("Invite buddy"),
|
||||
callback: () => Modals.spawnInviteEditor(this.channelTree.client)
|
||||
},
|
||||
contextmenu.Entry.CLOSE(() => (trigger_close ? on_close : () => {})())
|
||||
contextmenu.Entry.CLOSE(() => (trigger_close ? on_close : (() => {}))())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -266,7 +267,7 @@ class ServerEntry {
|
|||
value: variable.value,
|
||||
type: typeof (this.properties[variable.key])
|
||||
});
|
||||
log.table("Server update properties", entries);
|
||||
log.table(LogType.DEBUG, LogCategory.PERMISSIONS, "Server update properties", entries);
|
||||
}
|
||||
|
||||
let update_bannner = false, update_button = false;
|
||||
|
@ -366,7 +367,7 @@ class ServerEntry {
|
|||
});
|
||||
|
||||
this._info_connection_promise_timestamp = Date.now();
|
||||
this.channelTree.client.serverConnection.send_command("serverrequestconnectioninfo").catch(error => _local_reject(error));
|
||||
this.channelTree.client.serverConnection.send_command("serverrequestconnectioninfo", {}, {process_result: false}).catch(error => _local_reject(error));
|
||||
return this._info_connection_promise;
|
||||
}
|
||||
|
||||
|
|
|
@ -494,7 +494,6 @@ class ChannelTree {
|
|||
this.client.side_bar.show_channel_conversations();
|
||||
}
|
||||
}
|
||||
this.client.select_info.setCurrentSelected($.isArray(this.currently_selected) ? undefined : entry);
|
||||
}
|
||||
|
||||
private callback_multiselect_channel(event) {
|
||||
|
@ -748,7 +747,7 @@ class ChannelTree {
|
|||
}
|
||||
|
||||
handle_key_press(event: KeyboardEvent) {
|
||||
console.log("Keydown: %o | %o | %o", this._focused, this.currently_selected, Array.isArray(this.currently_selected));
|
||||
//console.log("Keydown: %o | %o | %o", this._focused, this.currently_selected, Array.isArray(this.currently_selected));
|
||||
if(!this._focused || !this.currently_selected || Array.isArray(this.currently_selected)) return;
|
||||
|
||||
if(event.keyCode == KeyCode.KEY_UP) {
|
||||
|
|
|
@ -97,13 +97,13 @@ class RecorderProfile {
|
|||
private initialize_input() {
|
||||
this.input = audio.recorder.create_input();
|
||||
this.input.callback_begin = () => {
|
||||
console.log("Voice start");
|
||||
log.debug(LogCategory.VOICE, "Voice start");
|
||||
if(this.callback_start)
|
||||
this.callback_start();
|
||||
};
|
||||
|
||||
this.input.callback_end = () => {
|
||||
console.log("Voice end");
|
||||
log.debug(LogCategory.VOICE, "Voice end");
|
||||
if(this.callback_stop)
|
||||
this.callback_stop();
|
||||
};
|
||||
|
@ -153,11 +153,11 @@ class RecorderProfile {
|
|||
const devices = all_devices.filter(e => e.default_input || e.unique_id === this.config.device_id);
|
||||
const device = devices.find(e => e.unique_id === this.config.device_id) || devices[0];
|
||||
|
||||
console.log(tr("Loaded record profile device %s | %o (%o)"), this.config.device_id, device, all_devices);
|
||||
log.info(LogCategory.VOICE, tr("Loaded record profile device %s | %o (%o)"), this.config.device_id, device, all_devices);
|
||||
try {
|
||||
await this.input.set_device(device);
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to set input device (%o)"), error);
|
||||
log.error(LogCategory.VOICE, tr("Failed to set input device (%o)"), error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ class RecorderProfile {
|
|||
try {
|
||||
await this.input.set_consumer(undefined);
|
||||
} catch(error) {
|
||||
console.warn(tr("Failed to unmount input consumer for profile (%o)"), error);
|
||||
log.warn(LogCategory.VOICE, tr("Failed to unmount input consumer for profile (%o)"), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,605 @@
|
|||
/// <reference path="loader.ts" />
|
||||
|
||||
interface Window {
|
||||
$: JQuery;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
export enum Type {
|
||||
UNKNOWN,
|
||||
CLIENT_RELEASE,
|
||||
CLIENT_DEBUG,
|
||||
WEB_DEBUG,
|
||||
WEB_RELEASE
|
||||
}
|
||||
export let type: Type = Type.UNKNOWN;
|
||||
|
||||
export function is_web() {
|
||||
return type == Type.WEB_RELEASE || type == Type.WEB_DEBUG;
|
||||
}
|
||||
|
||||
let _ui_version;
|
||||
export function ui_version() {
|
||||
if(typeof(_ui_version) !== "string") {
|
||||
const version_node = document.getElementById("app_version");
|
||||
if(!version_node) return undefined;
|
||||
|
||||
const version = version_node.hasAttribute("value") ? version_node.getAttribute("value") : undefined;
|
||||
if(!version) return undefined;
|
||||
|
||||
return (_ui_version = version);
|
||||
}
|
||||
return _ui_version;
|
||||
}
|
||||
}
|
||||
|
||||
/* all javascript loaders */
|
||||
const loader_javascript = {
|
||||
detect_type: async () => {
|
||||
if(window.require) {
|
||||
const request = new Request("js/proto.js");
|
||||
let file_path = request.url;
|
||||
if(!file_path.startsWith("file://"))
|
||||
throw "Invalid file path (" + file_path + ")";
|
||||
file_path = file_path.substring(process.platform === "win32" ? 8 : 7);
|
||||
|
||||
const fs = require('fs');
|
||||
if(fs.existsSync(file_path)) {
|
||||
app.type = app.Type.CLIENT_DEBUG;
|
||||
} else {
|
||||
app.type = app.Type.CLIENT_RELEASE;
|
||||
}
|
||||
} else {
|
||||
/* test if js/proto.js is available. If so we're in debug mode */
|
||||
const request = new XMLHttpRequest();
|
||||
request.open('GET', 'js/proto.js', true);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
request.onreadystatechange = () => {
|
||||
if (request.readyState === 4){
|
||||
if (request.status === 404) {
|
||||
app.type = app.Type.WEB_RELEASE;
|
||||
} else {
|
||||
app.type = app.Type.WEB_DEBUG;
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
request.onerror = () => {
|
||||
reject("Failed to detect app type");
|
||||
};
|
||||
request.send();
|
||||
});
|
||||
}
|
||||
},
|
||||
load_scripts: async () => {
|
||||
/*
|
||||
if(window.require !== undefined) {
|
||||
console.log("Loading node specific things");
|
||||
const remote = require('electron').remote;
|
||||
module.paths.push(remote.app.getAppPath() + "/node_modules");
|
||||
module.paths.push(remote.app.getAppPath() + "/app");
|
||||
module.paths.push(remote.getGlobal("browser-root") + "js/");
|
||||
window.$ = require("assets/jquery.min.js");
|
||||
require("native/loader_adapter.js");
|
||||
}
|
||||
*/
|
||||
|
||||
if(!window.require) {
|
||||
await loader.load_script(["vendor/jquery/jquery.min.js"]);
|
||||
} else {
|
||||
/*
|
||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||
name: "forum sync",
|
||||
priority: 10,
|
||||
function: async () => {
|
||||
forum.sync_main();
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
await loader.load_script(["vendor/DOMPurify/purify.min.js"]);
|
||||
|
||||
/* bootstrap material design and libs */
|
||||
//await loader.load_script(["vendor/popper/popper.js"]);
|
||||
|
||||
//depends on popper
|
||||
//await loader.load_script(["vendor/bootstrap-material/bootstrap-material-design.js"]);
|
||||
|
||||
/*
|
||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||
name: "materialize body",
|
||||
priority: 10,
|
||||
function: async () => { $(document).ready(function() { $('body').bootstrapMaterialDesign(); }); }
|
||||
});
|
||||
*/
|
||||
|
||||
await loader.load_script("vendor/jsrender/jsrender.min.js");
|
||||
await loader.load_scripts([
|
||||
["vendor/xbbcode/src/parser.js"],
|
||||
["vendor/moment/moment.js"],
|
||||
["vendor/twemoji/twemoji.min.js", ""], /* empty string means not required */
|
||||
["vendor/highlight/highlight.pack.js", ""], /* empty string means not required */
|
||||
["vendor/remarkable/remarkable.min.js", ""], /* empty string means not required */
|
||||
["adapter/adapter-latest.js", "https://webrtc.github.io/adapter/adapter-latest.js"]
|
||||
]);
|
||||
await loader.load_scripts([
|
||||
["vendor/emoji-picker/src/jquery.lsxemojipicker.js"]
|
||||
]);
|
||||
|
||||
if(app.type == app.Type.WEB_RELEASE || app.type == app.Type.CLIENT_RELEASE) {
|
||||
loader.register_task(loader.Stage.JAVASCRIPT, {
|
||||
name: "scripts release",
|
||||
priority: 20,
|
||||
function: loader_javascript.load_release
|
||||
});
|
||||
} else {
|
||||
loader.register_task(loader.Stage.JAVASCRIPT, {
|
||||
name: "scripts debug",
|
||||
priority: 20,
|
||||
function: loader_javascript.load_scripts_debug
|
||||
});
|
||||
}
|
||||
},
|
||||
load_scripts_debug: async () => {
|
||||
/* test if we're loading as TeaClient or WebClient */
|
||||
if(!window.require) {
|
||||
loader.register_task(loader.Stage.JAVASCRIPT, {
|
||||
name: "javascript web",
|
||||
priority: 10,
|
||||
function: loader_javascript.load_scripts_debug_web
|
||||
});
|
||||
} else {
|
||||
loader.register_task(loader.Stage.JAVASCRIPT, {
|
||||
name: "javascript client",
|
||||
priority: 10,
|
||||
function: loader_javascript.load_scripts_debug_client
|
||||
});
|
||||
}
|
||||
|
||||
/* load some extends classes */
|
||||
await loader.load_scripts([
|
||||
["js/connection/ConnectionBase.js"]
|
||||
]);
|
||||
|
||||
/* load the main app */
|
||||
await loader.load_scripts([
|
||||
//Load general API's
|
||||
"js/proto.js",
|
||||
"js/i18n/localize.js",
|
||||
"js/i18n/country.js",
|
||||
"js/log.js",
|
||||
|
||||
"js/sound/Sounds.js",
|
||||
|
||||
"js/utils/helpers.js",
|
||||
|
||||
"js/crypto/sha.js",
|
||||
"js/crypto/hex.js",
|
||||
"js/crypto/asn1.js",
|
||||
"js/crypto/crc32.js",
|
||||
|
||||
//load the profiles
|
||||
"js/profiles/ConnectionProfile.js",
|
||||
"js/profiles/Identity.js",
|
||||
"js/profiles/identities/teaspeak-forum.js",
|
||||
|
||||
//Basic UI elements
|
||||
"js/ui/elements/context_divider.js",
|
||||
"js/ui/elements/context_menu.js",
|
||||
"js/ui/elements/modal.js",
|
||||
"js/ui/elements/tab.js",
|
||||
"js/ui/elements/slider.js",
|
||||
"js/ui/elements/tooltip.js",
|
||||
|
||||
//Load UI
|
||||
"js/ui/modal/ModalAbout.js",
|
||||
"js/ui/modal/ModalAvatar.js",
|
||||
"js/ui/modal/ModalAvatarList.js",
|
||||
"js/ui/modal/ModalClientInfo.js",
|
||||
"js/ui/modal/ModalQuery.js",
|
||||
"js/ui/modal/ModalQueryManage.js",
|
||||
"js/ui/modal/ModalPlaylistList.js",
|
||||
"js/ui/modal/ModalPlaylistEdit.js",
|
||||
"js/ui/modal/ModalBookmarks.js",
|
||||
"js/ui/modal/ModalConnect.js",
|
||||
"js/ui/modal/ModalSettings.js",
|
||||
"js/ui/modal/ModalCreateChannel.js",
|
||||
"js/ui/modal/ModalServerEdit.js",
|
||||
"js/ui/modal/ModalServerInfo.js",
|
||||
"js/ui/modal/ModalChangeVolume.js",
|
||||
"js/ui/modal/ModalBanClient.js",
|
||||
"js/ui/modal/ModalIconSelect.js",
|
||||
"js/ui/modal/ModalInvite.js",
|
||||
"js/ui/modal/ModalIdentity.js",
|
||||
"js/ui/modal/ModalBanCreate.js",
|
||||
"js/ui/modal/ModalBanList.js",
|
||||
"js/ui/modal/ModalYesNo.js",
|
||||
"js/ui/modal/ModalPoke.js",
|
||||
"js/ui/modal/ModalKeySelect.js",
|
||||
"js/ui/modal/ModalGroupAssignment.js",
|
||||
"js/ui/modal/permission/ModalPermissionEdit.js",
|
||||
{url: "js/ui/modal/permission/CanvasPermissionEditor.js", depends: ["js/ui/modal/permission/ModalPermissionEdit.js"]},
|
||||
{url: "js/ui/modal/permission/HTMLPermissionEditor.js", depends: ["js/ui/modal/permission/ModalPermissionEdit.js"]},
|
||||
|
||||
"js/ui/channel.js",
|
||||
"js/ui/client.js",
|
||||
"js/ui/server.js",
|
||||
"js/ui/view.js",
|
||||
"js/ui/client_move.js",
|
||||
"js/ui/htmltags.js",
|
||||
|
||||
"js/ui/frames/ControlBar.js",
|
||||
"js/ui/frames/chat.js",
|
||||
"js/ui/frames/chat_frame.js",
|
||||
"js/ui/frames/connection_handlers.js",
|
||||
"js/ui/frames/server_log.js",
|
||||
"js/ui/frames/hostbanner.js",
|
||||
"js/ui/frames/MenuBar.js",
|
||||
|
||||
//Load permissions
|
||||
"js/permission/PermissionManager.js",
|
||||
"js/permission/GroupManager.js",
|
||||
|
||||
//Load audio
|
||||
"js/voice/RecorderBase.js",
|
||||
"js/voice/RecorderProfile.js",
|
||||
|
||||
//Load general stuff
|
||||
"js/settings.js",
|
||||
"js/bookmarks.js",
|
||||
"js/FileManager.js",
|
||||
"js/ConnectionHandler.js",
|
||||
"js/BrowserIPC.js",
|
||||
"js/dns.js",
|
||||
|
||||
//Connection
|
||||
"js/connection/CommandHandler.js",
|
||||
"js/connection/CommandHelper.js",
|
||||
"js/connection/HandshakeHandler.js",
|
||||
"js/connection/ServerConnectionDeclaration.js",
|
||||
|
||||
"js/stats.js",
|
||||
"js/PPTListener.js",
|
||||
|
||||
"js/profiles/identities/NameIdentity.js", //Depends on Identity
|
||||
"js/profiles/identities/TeaForumIdentity.js", //Depends on Identity
|
||||
"js/profiles/identities/TeamSpeakIdentity.js", //Depends on Identity
|
||||
]);
|
||||
|
||||
await loader.load_script("js/main.js");
|
||||
},
|
||||
load_scripts_debug_web: async () => {
|
||||
await loader.load_scripts([
|
||||
["js/audio/AudioPlayer.js"],
|
||||
["js/audio/WebCodec.js"],
|
||||
["js/WebPPTListener.js"],
|
||||
|
||||
"js/voice/AudioResampler.js",
|
||||
"js/voice/JavascriptRecorder.js",
|
||||
"js/voice/VoiceHandler.js",
|
||||
"js/voice/VoiceClient.js",
|
||||
|
||||
//Connection
|
||||
"js/connection/ServerConnection.js",
|
||||
|
||||
//Load codec
|
||||
"js/codec/Codec.js",
|
||||
"js/codec/BasicCodec.js",
|
||||
{url: "js/codec/CodecWrapperWorker.js", depends: ["js/codec/BasicCodec.js"]},
|
||||
]);
|
||||
},
|
||||
load_scripts_debug_client: async () => {
|
||||
await loader.load_scripts([
|
||||
]);
|
||||
},
|
||||
|
||||
load_release: async () => {
|
||||
console.log("Load for release!");
|
||||
|
||||
await loader.load_scripts([
|
||||
//Load general API's
|
||||
["js/client.min.js", "js/client.js"]
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const loader_webassembly = {
|
||||
test_webassembly: async () => {
|
||||
/* We dont required WebAssembly anymore for fundamental functions, only for auto decoding
|
||||
if(typeof (WebAssembly) === "undefined" || typeof (WebAssembly.compile) === "undefined") {
|
||||
console.log(navigator.browserSpecs);
|
||||
if (navigator.browserSpecs.name == 'Safari') {
|
||||
if (parseInt(navigator.browserSpecs.version) < 11) {
|
||||
displayCriticalError("You require Safari 11 or higher to use the web client!<br>Safari " + navigator.browserSpecs.version + " does not support WebAssambly!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Do something for all other browsers.
|
||||
}
|
||||
displayCriticalError("You require WebAssembly for TeaSpeak-Web!");
|
||||
throw "Missing web assembly";
|
||||
}
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
const loader_style = {
|
||||
load_style: async () => {
|
||||
await loader.load_styles([
|
||||
"vendor/xbbcode/src/xbbcode.css"
|
||||
]);
|
||||
await loader.load_styles([
|
||||
"vendor/emoji-picker/src/jquery.lsxemojipicker.css"
|
||||
]);
|
||||
await loader.load_styles([
|
||||
["vendor/highlight/styles/darcula.css", ""], /* empty string means not required */
|
||||
]);
|
||||
|
||||
if(app.type == app.Type.WEB_DEBUG || app.type == app.Type.CLIENT_DEBUG) {
|
||||
await loader_style.load_style_debug();
|
||||
} else {
|
||||
await loader_style.load_style_release();
|
||||
}
|
||||
},
|
||||
|
||||
load_style_debug: async () => {
|
||||
await loader.load_styles([
|
||||
"css/static/main.css",
|
||||
"css/static/main-layout.css",
|
||||
"css/static/helptag.css",
|
||||
"css/static/scroll.css",
|
||||
"css/static/channel-tree.css",
|
||||
"css/static/ts/tab.css",
|
||||
"css/static/ts/chat.css",
|
||||
"css/static/ts/icons.css",
|
||||
"css/static/ts/icons_em.css",
|
||||
"css/static/ts/country.css",
|
||||
"css/static/general.css",
|
||||
"css/static/modal.css",
|
||||
"css/static/modals.css",
|
||||
"css/static/modal-about.css",
|
||||
"css/static/modal-avatar.css",
|
||||
"css/static/modal-icons.css",
|
||||
"css/static/modal-bookmarks.css",
|
||||
"css/static/modal-connect.css",
|
||||
"css/static/modal-channel.css",
|
||||
"css/static/modal-query.css",
|
||||
"css/static/modal-invite.css",
|
||||
"css/static/modal-playlist.css",
|
||||
"css/static/modal-banlist.css",
|
||||
"css/static/modal-bancreate.css",
|
||||
"css/static/modal-clientinfo.css",
|
||||
"css/static/modal-serverinfo.css",
|
||||
"css/static/modal-identity.css",
|
||||
"css/static/modal-settings.css",
|
||||
"css/static/modal-poke.css",
|
||||
"css/static/modal-server.css",
|
||||
"css/static/modal-keyselect.css",
|
||||
"css/static/modal-permissions.css",
|
||||
"css/static/modal-group-assignment.css",
|
||||
"css/static/music/info_plate.css",
|
||||
"css/static/frame/SelectInfo.css",
|
||||
"css/static/control_bar.css",
|
||||
"css/static/context_menu.css",
|
||||
"css/static/frame-chat.css",
|
||||
"css/static/connection_handlers.css",
|
||||
"css/static/server-log.css",
|
||||
"css/static/htmltags.css",
|
||||
"css/static/hostbanner.css",
|
||||
"css/static/menu-bar.css"
|
||||
]);
|
||||
},
|
||||
|
||||
load_style_release: async () => {
|
||||
await loader.load_styles([
|
||||
"css/static/base.css",
|
||||
"css/static/main.css",
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
async function load_templates() {
|
||||
try {
|
||||
const response = await $.ajax("templates.html" + loader.get_cache_version());
|
||||
|
||||
let node = document.createElement("html");
|
||||
node.innerHTML = response;
|
||||
let tags: HTMLCollection;
|
||||
if(node.getElementsByTagName("body").length > 0)
|
||||
tags = node.getElementsByTagName("body")[0].children;
|
||||
else
|
||||
tags = node.children;
|
||||
|
||||
let root = document.getElementById("templates");
|
||||
if(!root) {
|
||||
loader.critical_error("Failed to find template tag!");
|
||||
return;
|
||||
}
|
||||
while(tags.length > 0){
|
||||
let tag = tags.item(0);
|
||||
root.appendChild(tag);
|
||||
|
||||
}
|
||||
} catch(error) {
|
||||
loader.critical_error("Failed to find template tag!");
|
||||
throw "template error";
|
||||
}
|
||||
}
|
||||
|
||||
//FUN: loader_ignore_age=0&loader_default_duration=1500&loader_default_age=5000
|
||||
let _fadeout_warned = false;
|
||||
function fadeoutLoader(duration = undefined, minAge = undefined, ignoreAge = undefined) {
|
||||
if(typeof($) === "undefined") {
|
||||
if(!_fadeout_warned)
|
||||
console.warn("Could not fadeout loader screen. Missing jquery functions.");
|
||||
_fadeout_warned = true;
|
||||
return;
|
||||
}
|
||||
|
||||
let settingsDefined = typeof(StaticSettings) !== "undefined";
|
||||
if(!duration) {
|
||||
if(settingsDefined)
|
||||
duration = StaticSettings.instance.static("loader_default_duration", 750);
|
||||
else duration = 750;
|
||||
}
|
||||
if(!minAge) {
|
||||
if(settingsDefined)
|
||||
minAge = StaticSettings.instance.static("loader_default_age", 1750);
|
||||
else minAge = 750;
|
||||
}
|
||||
if(!ignoreAge) {
|
||||
if(settingsDefined)
|
||||
ignoreAge = StaticSettings.instance.static("loader_ignore_age", false);
|
||||
else ignoreAge = false;
|
||||
}
|
||||
|
||||
/*
|
||||
let age = Date.now() - app.appLoaded;
|
||||
if(age < minAge && !ignoreAge) {
|
||||
setTimeout(() => fadeoutLoader(duration, 0, true), minAge - age);
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
$(".loader .bookshelf_wrapper").animate({top: 0, opacity: 0}, duration);
|
||||
$(".loader .half").animate({width: 0}, duration, () => {
|
||||
$(".loader").detach();
|
||||
});
|
||||
}
|
||||
|
||||
/* register tasks */
|
||||
loader.register_task(loader.Stage.INITIALIZING, {
|
||||
name: "safari fix",
|
||||
function: async () => {
|
||||
/* safari remove "fix" */
|
||||
if(Element.prototype.remove === undefined)
|
||||
Object.defineProperty(Element.prototype, "remove", {
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
value: function(){
|
||||
this.parentElement.removeChild(this);
|
||||
}
|
||||
});
|
||||
},
|
||||
priority: 50
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.INITIALIZING, {
|
||||
name: "Browser detection",
|
||||
function: async () => {
|
||||
navigator.browserSpecs = (function(){
|
||||
let ua = navigator.userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
|
||||
if(/trident/i.test(M[1])){
|
||||
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
|
||||
return {name:'IE',version:(tem[1] || '')};
|
||||
}
|
||||
if(M[1]=== 'Chrome'){
|
||||
tem = ua.match(/\b(OPR|Edge)\/(\d+)/);
|
||||
if(tem != null) return {name:tem[1].replace('OPR', 'Opera'),version:tem[2]};
|
||||
}
|
||||
M = M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?'];
|
||||
if((tem = ua.match(/version\/(\d+)/i))!= null)
|
||||
M.splice(1, 1, tem[1]);
|
||||
return {name:M[0], version:M[1]};
|
||||
})();
|
||||
|
||||
console.log("Resolved browser specs: %o", navigator.browserSpecs); //Object { name: "Firefox", version: "42" }
|
||||
},
|
||||
priority: 30
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.INITIALIZING, {
|
||||
name: "secure tester",
|
||||
function: async () => {
|
||||
/* we need https or localhost to use some things like the storage API */
|
||||
if(typeof isSecureContext === "undefined")
|
||||
(<any>window)["isSecureContext"] = location.protocol !== 'https:' && location.hostname !== 'localhost';
|
||||
|
||||
if(!isSecureContext) {
|
||||
loader.critical_error("TeaWeb cant run on unsecured sides.", "App requires to be loaded via HTTPS!");
|
||||
throw "App requires a secure context!"
|
||||
}
|
||||
},
|
||||
priority: 20
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.INITIALIZING, {
|
||||
name: "webassembly tester",
|
||||
function: loader_webassembly.test_webassembly,
|
||||
priority: 20
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.INITIALIZING, {
|
||||
name: "app type test",
|
||||
function: loader_javascript.detect_type,
|
||||
priority: 20
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.JAVASCRIPT, {
|
||||
name: "javascript",
|
||||
function: loader_javascript.load_scripts,
|
||||
priority: 10
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.STYLE, {
|
||||
name: "style",
|
||||
function: loader_style.load_style,
|
||||
priority: 10
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.TEMPLATES, {
|
||||
name: "templates",
|
||||
function: load_templates,
|
||||
priority: 10
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.LOADED, {
|
||||
name: "loaded handler",
|
||||
function: async () => {
|
||||
fadeoutLoader();
|
||||
},
|
||||
priority: 10
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.LOADED, {
|
||||
name: "error task",
|
||||
function: async () => {
|
||||
if(Settings.instance.static(Settings.KEY_LOAD_DUMMY_ERROR, false)) {
|
||||
loader.critical_error("The tea is cold!", "Argh, this is evil! Cold tea dosn't taste good.");
|
||||
throw "The tea is cold!";
|
||||
}
|
||||
},
|
||||
priority: 20
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||
name: "lsx emoji picker setup",
|
||||
function: async () => await (window as any).setup_lsx_emoji_picker({twemoji: typeof(window.twemoji) !== "undefined"}),
|
||||
priority: 10
|
||||
});
|
||||
|
||||
window["Module"] = (window["Module"] || {}) as any;
|
||||
/* TeaClient */
|
||||
if(window.require) {
|
||||
const path = require("path");
|
||||
const remote = require('electron').remote;
|
||||
module.paths.push(path.join(remote.app.getAppPath(), "/modules"));
|
||||
module.paths.push(path.join(path.dirname(remote.getGlobal("browser-root")), "js"));
|
||||
|
||||
const connector = require("renderer");
|
||||
console.log(connector);
|
||||
|
||||
loader.register_task(loader.Stage.INITIALIZING, {
|
||||
name: "teaclient initialize",
|
||||
function: connector.initialize,
|
||||
priority: 40
|
||||
});
|
||||
}
|
||||
|
||||
if(!loader.running()) {
|
||||
/* we know that we want to load the app */
|
||||
loader.execute_managed();
|
||||
}
|
|
@ -0,0 +1,643 @@
|
|||
interface Window {
|
||||
tr(message: string) : string;
|
||||
}
|
||||
|
||||
namespace loader {
|
||||
export namespace config {
|
||||
export const loader_groups = false;
|
||||
export const verbose = false;
|
||||
export const error = true;
|
||||
}
|
||||
|
||||
export type Task = {
|
||||
name: string,
|
||||
priority: number, /* tasks with the same priority will be executed in sync */
|
||||
function: () => Promise<void>
|
||||
};
|
||||
|
||||
export enum Stage {
|
||||
/*
|
||||
loading loader required files (incl this)
|
||||
*/
|
||||
INITIALIZING,
|
||||
/*
|
||||
setting up the loading process
|
||||
*/
|
||||
SETUP,
|
||||
/*
|
||||
loading all style sheet files
|
||||
*/
|
||||
STYLE,
|
||||
/*
|
||||
loading all javascript files
|
||||
*/
|
||||
JAVASCRIPT,
|
||||
/*
|
||||
loading all template files
|
||||
*/
|
||||
TEMPLATES,
|
||||
/*
|
||||
initializing static/global stuff
|
||||
*/
|
||||
JAVASCRIPT_INITIALIZING,
|
||||
/*
|
||||
finalizing load process
|
||||
*/
|
||||
FINALIZING,
|
||||
/*
|
||||
invoking main task
|
||||
*/
|
||||
LOADED,
|
||||
|
||||
DONE
|
||||
}
|
||||
|
||||
let cache_tag: string | undefined;
|
||||
let current_stage: Stage = undefined;
|
||||
const tasks: {[key:number]:Task[]} = {};
|
||||
|
||||
/* test if all files shall be load from cache or fetch again */
|
||||
function loader_cache_tag() {
|
||||
const app_version = (() => {
|
||||
const version_node = document.getElementById("app_version");
|
||||
if(!version_node) return undefined;
|
||||
|
||||
const version = version_node.hasAttribute("value") ? version_node.getAttribute("value") : undefined;
|
||||
if(!version) return undefined;
|
||||
|
||||
if(!version || version == "unknown" || version.replace(/0+/, "").length == 0)
|
||||
return undefined;
|
||||
|
||||
return version;
|
||||
})();
|
||||
if(config.verbose) console.log("Found current app version: %o", app_version);
|
||||
|
||||
if(!app_version) {
|
||||
/* TODO add warning */
|
||||
cache_tag = "?_ts=" + Date.now();
|
||||
return;
|
||||
}
|
||||
const cached_version = localStorage.getItem("cached_version");
|
||||
if(!cached_version || cached_version != app_version) {
|
||||
loader.register_task(loader.Stage.LOADED, {
|
||||
priority: 0,
|
||||
name: "cached version updater",
|
||||
function: async () => {
|
||||
localStorage.setItem("cached_version", app_version);
|
||||
}
|
||||
});
|
||||
}
|
||||
cache_tag = "?_version=" + app_version;
|
||||
}
|
||||
|
||||
export function get_cache_version() { return cache_tag; }
|
||||
|
||||
export function finished() {
|
||||
return current_stage == Stage.DONE;
|
||||
}
|
||||
export function running() { return typeof(current_stage) !== "undefined"; }
|
||||
|
||||
export function register_task(stage: Stage, task: Task) {
|
||||
if(current_stage > stage) {
|
||||
if(config.error)
|
||||
console.warn("Register loading task, but it had already been finished. Executing task anyways!");
|
||||
task.function().catch(error => {
|
||||
if(config.error) {
|
||||
console.error("Failed to execute delayed loader task!");
|
||||
console.log(" - %s: %o", task.name, error);
|
||||
}
|
||||
|
||||
loader.critical_error(error);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const task_array = tasks[stage] || [];
|
||||
task_array.push(task);
|
||||
tasks[stage] = task_array.sort((a, b) => a.priority - b.priority);
|
||||
}
|
||||
|
||||
export async function execute() {
|
||||
document.getElementById("loader-overlay").classList.add("started");
|
||||
loader_cache_tag();
|
||||
|
||||
const load_begin = Date.now();
|
||||
|
||||
let begin: number = 0;
|
||||
let end: number = Date.now();
|
||||
while(current_stage <= Stage.LOADED || typeof(current_stage) === "undefined") {
|
||||
|
||||
let current_tasks: Task[] = [];
|
||||
while((tasks[current_stage] || []).length > 0) {
|
||||
if(current_tasks.length == 0 || current_tasks[0].priority == tasks[current_stage][0].priority) {
|
||||
current_tasks.push(tasks[current_stage].pop());
|
||||
} else break;
|
||||
}
|
||||
|
||||
const errors: {
|
||||
error: any,
|
||||
task: Task
|
||||
}[] = [];
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
for(const task of current_tasks) {
|
||||
try {
|
||||
if(config.verbose) console.debug("Executing loader %s (%d)", task.name, task.priority);
|
||||
promises.push(task.function().catch(error => {
|
||||
errors.push({
|
||||
task: task,
|
||||
error: error
|
||||
});
|
||||
return Promise.resolve();
|
||||
}));
|
||||
} catch(error) {
|
||||
errors.push({
|
||||
task: task,
|
||||
error: error
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(promises.length > 0) {
|
||||
await Promise.all([...promises]);
|
||||
}
|
||||
|
||||
if(errors.length > 0) {
|
||||
if(config.loader_groups) console.groupEnd();
|
||||
console.error("Failed to execute loader. The following tasks failed (%d):", errors.length);
|
||||
for(const error of errors)
|
||||
console.error(" - %s: %o", error.task.name, error.error);
|
||||
|
||||
throw "failed to process step " + Stage[current_stage];
|
||||
}
|
||||
|
||||
if(current_tasks.length == 0) {
|
||||
if(typeof(current_stage) === "undefined") {
|
||||
current_stage = -1;
|
||||
if(config.verbose) console.debug("[loader] Booting app");
|
||||
} else if(current_stage < Stage.INITIALIZING) {
|
||||
if(config.loader_groups) console.groupEnd();
|
||||
if(config.verbose) console.debug("[loader] Entering next state (%s). Last state took %dms", Stage[current_stage + 1], (end = Date.now()) - begin);
|
||||
} else {
|
||||
if(config.loader_groups) console.groupEnd();
|
||||
if(config.verbose) console.debug("[loader] Finish invoke took %dms", (end = Date.now()) - begin);
|
||||
}
|
||||
|
||||
begin = end;
|
||||
current_stage += 1;
|
||||
|
||||
if(current_stage != Stage.DONE && config.loader_groups)
|
||||
console.groupCollapsed("Executing loading stage %s", Stage[current_stage]);
|
||||
}
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
{
|
||||
_script_promises = {};
|
||||
}
|
||||
if(config.verbose) console.debug("[loader] finished loader. (Total time: %dms)", Date.now() - load_begin);
|
||||
}
|
||||
export function execute_managed() {
|
||||
loader.execute().then(() => {
|
||||
if(config.verbose) {
|
||||
let message;
|
||||
if(typeof(window.tr) !== "undefined")
|
||||
message = tr("App loaded successfully!");
|
||||
else
|
||||
message = "App loaded successfully!";
|
||||
|
||||
if(typeof(log) !== "undefined") {
|
||||
/* We're having our log module */
|
||||
log.info(LogCategory.GENERAL, message);
|
||||
} else {
|
||||
console.log(message);
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
if(config.error) {
|
||||
console.error("App loading failed: %o", error);
|
||||
}
|
||||
loader.critical_error("Failed to execute loader", "Lookup the console for more detail");
|
||||
});
|
||||
}
|
||||
|
||||
export type DependSource = {
|
||||
url: string;
|
||||
depends: string[];
|
||||
}
|
||||
export type SourcePath = string | DependSource | string[];
|
||||
|
||||
function script_name(path: SourcePath) {
|
||||
if(Array.isArray(path)) {
|
||||
let buffer = "";
|
||||
let _or = " or ";
|
||||
for(let entry of path)
|
||||
buffer += _or + script_name(entry);
|
||||
return buffer.slice(_or.length);
|
||||
} else if(typeof(path) === "string")
|
||||
return "<code>" + path + "</code>";
|
||||
else
|
||||
return "<code>" + path.url + "</code>";
|
||||
}
|
||||
|
||||
class SyntaxError {
|
||||
source: any;
|
||||
|
||||
constructor(source: any) {
|
||||
this.source = source;
|
||||
}
|
||||
}
|
||||
|
||||
let _script_promises: {[key: string]: Promise<void>} = {};
|
||||
export async function load_script(path: SourcePath) : Promise<void> {
|
||||
if(Array.isArray(path)) { //We have some fallback
|
||||
return load_script(path[0]).catch(error => {
|
||||
if(error instanceof SyntaxError)
|
||||
return Promise.reject(error.source);
|
||||
|
||||
if(path.length > 1)
|
||||
return load_script(path.slice(1));
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
} else {
|
||||
const source = typeof(path) === "string" ? {url: path, depends: []} : path;
|
||||
if(source.url.length == 0) return Promise.resolve();
|
||||
|
||||
return _script_promises[source.url] = (async () => {
|
||||
/* await depends */
|
||||
for(const depend of source.depends) {
|
||||
if(!_script_promises[depend])
|
||||
throw "Missing dependency " + depend;
|
||||
await _script_promises[depend];
|
||||
}
|
||||
|
||||
const tag: HTMLScriptElement = document.createElement("script");
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
let error = false;
|
||||
const error_handler = (event: ErrorEvent) => {
|
||||
if(event.filename == tag.src && event.message.indexOf("Illegal constructor") == -1) { //Our tag throw an uncaught error
|
||||
if(config.verbose) console.log("msg: %o, url: %o, line: %o, col: %o, error: %o", event.message, event.filename, event.lineno, event.colno, event.error);
|
||||
window.removeEventListener('error', error_handler as any);
|
||||
|
||||
reject(new SyntaxError(event.error));
|
||||
event.preventDefault();
|
||||
error = true;
|
||||
}
|
||||
};
|
||||
window.addEventListener('error', error_handler as any);
|
||||
|
||||
const cleanup = () => {
|
||||
tag.onerror = undefined;
|
||||
tag.onload = undefined;
|
||||
|
||||
clearTimeout(timeout_handle);
|
||||
window.removeEventListener('error', error_handler as any);
|
||||
};
|
||||
const timeout_handle = setTimeout(() => {
|
||||
cleanup();
|
||||
reject("timeout");
|
||||
}, 5000);
|
||||
tag.type = "application/javascript";
|
||||
tag.async = true;
|
||||
tag.defer = true;
|
||||
tag.onerror = error => {
|
||||
cleanup();
|
||||
tag.remove();
|
||||
reject(error);
|
||||
};
|
||||
tag.onload = () => {
|
||||
cleanup();
|
||||
|
||||
if(config.verbose) console.debug("Script %o loaded", path);
|
||||
setTimeout(resolve, 100);
|
||||
};
|
||||
|
||||
document.getElementById("scripts").appendChild(tag);
|
||||
|
||||
tag.src = source.url + (cache_tag || "");
|
||||
});
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
export async function load_scripts(paths: SourcePath[]) : Promise<void> {
|
||||
const promises: Promise<void>[] = [];
|
||||
const errors: {
|
||||
script: SourcePath,
|
||||
error: any
|
||||
}[] = [];
|
||||
|
||||
for(const script of paths)
|
||||
promises.push(load_script(script).catch(error => {
|
||||
errors.push({
|
||||
script: script,
|
||||
error: error
|
||||
});
|
||||
return Promise.resolve();
|
||||
}));
|
||||
|
||||
await Promise.all([...promises]);
|
||||
|
||||
if(errors.length > 0) {
|
||||
if(config.error) {
|
||||
console.error("Failed to load the following scripts:");
|
||||
for(const script of errors)
|
||||
console.log(" - %o: %o", script.script, script.error);
|
||||
}
|
||||
|
||||
loader.critical_error("Failed to load script " + script_name(errors[0].script) + " <br>" + "View the browser console for more information!");
|
||||
throw "failed to load script " + script_name(errors[0].script);
|
||||
}
|
||||
}
|
||||
|
||||
export async function load_style(path: SourcePath) : Promise<void> {
|
||||
if(Array.isArray(path)) { //We have some fallback
|
||||
return load_style(path[0]).catch(error => {
|
||||
if(error instanceof SyntaxError)
|
||||
return Promise.reject(error.source);
|
||||
|
||||
if(path.length > 1)
|
||||
return load_script(path.slice(1));
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
} else {
|
||||
if(!path) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const tag: HTMLLinkElement = document.createElement("link");
|
||||
|
||||
let error = false;
|
||||
const error_handler = (event: ErrorEvent) => {
|
||||
if(config.verbose) console.log("msg: %o, url: %o, line: %o, col: %o, error: %o", event.message, event.filename, event.lineno, event.colno, event.error);
|
||||
if(event.filename == tag.href) { //FIXME!
|
||||
window.removeEventListener('error', error_handler as any);
|
||||
|
||||
reject(new SyntaxError(event.error));
|
||||
event.preventDefault();
|
||||
error = true;
|
||||
}
|
||||
};
|
||||
window.addEventListener('error', error_handler as any);
|
||||
|
||||
tag.type = "text/css";
|
||||
tag.rel = "stylesheet";
|
||||
|
||||
const cleanup = () => {
|
||||
tag.onerror = undefined;
|
||||
tag.onload = undefined;
|
||||
|
||||
clearTimeout(timeout_handle);
|
||||
window.removeEventListener('error', error_handler as any);
|
||||
};
|
||||
|
||||
const timeout_handle = setTimeout(() => {
|
||||
cleanup();
|
||||
reject("timeout");
|
||||
}, 5000);
|
||||
|
||||
tag.onerror = error => {
|
||||
cleanup();
|
||||
tag.remove();
|
||||
if(config.error)
|
||||
console.error("File load error for file %s: %o", path, error);
|
||||
reject("failed to load file " + path);
|
||||
};
|
||||
tag.onload = () => {
|
||||
cleanup();
|
||||
{
|
||||
const css: CSSStyleSheet = tag.sheet as CSSStyleSheet;
|
||||
const rules = css.cssRules;
|
||||
const rules_remove: number[] = [];
|
||||
const rules_add: string[] = [];
|
||||
|
||||
for(let index = 0; index < rules.length; index++) {
|
||||
const rule = rules.item(index);
|
||||
let rule_text = rule.cssText;
|
||||
|
||||
if(rule.cssText.indexOf("%%base_path%%") != -1) {
|
||||
rules_remove.push(index);
|
||||
rules_add.push(rule_text.replace("%%base_path%%", document.location.origin + document.location.pathname));
|
||||
}
|
||||
}
|
||||
|
||||
for(const index of rules_remove.sort((a, b) => b > a ? 1 : 0)) {
|
||||
if(css.removeRule)
|
||||
css.removeRule(index);
|
||||
else
|
||||
css.deleteRule(index);
|
||||
}
|
||||
for(const rule of rules_add)
|
||||
css.insertRule(rule, rules_remove[0]);
|
||||
}
|
||||
|
||||
if(config.verbose) console.debug("Style sheet %o loaded", path);
|
||||
setTimeout(resolve, 100);
|
||||
};
|
||||
|
||||
document.getElementById("style").appendChild(tag);
|
||||
tag.href = path + (cache_tag || "");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function load_styles(paths: SourcePath[]) : Promise<void> {
|
||||
const promises: Promise<void>[] = [];
|
||||
const errors: {
|
||||
sheet: SourcePath,
|
||||
error: any
|
||||
}[] = [];
|
||||
|
||||
for(const sheet of paths)
|
||||
promises.push(load_style(sheet).catch(error => {
|
||||
errors.push({
|
||||
sheet: sheet,
|
||||
error: error
|
||||
});
|
||||
return Promise.resolve();
|
||||
}));
|
||||
|
||||
await Promise.all([...promises]);
|
||||
|
||||
if(errors.length > 0) {
|
||||
if(loader.config.error) {
|
||||
console.error("Failed to load the following style sheet:");
|
||||
for(const sheet of errors)
|
||||
console.log(" - %o: %o", sheet.sheet, sheet.error);
|
||||
}
|
||||
|
||||
loader.critical_error("Failed to load style sheet " + script_name(errors[0].sheet) + " <br>" + "View the browser console for more information!");
|
||||
throw "failed to load style sheet " + script_name(errors[0].sheet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export type ErrorHandler = (message: string, detail: string) => void;
|
||||
|
||||
let _callback_critical_error: ErrorHandler;
|
||||
let _callback_critical_called: boolean = false;
|
||||
|
||||
export function critical_error(message: string, detail?: string) {
|
||||
if(_callback_critical_called) {
|
||||
console.warn("[CRITICAL] %s", message);
|
||||
if(typeof(detail) === "string")
|
||||
console.warn("[CRITICAL] %s", detail);
|
||||
return;
|
||||
}
|
||||
|
||||
if(_callback_critical_error) {
|
||||
_callback_critical_error(message, detail);
|
||||
return;
|
||||
}
|
||||
|
||||
/* default handling */
|
||||
let tag = document.getElementById("critical-load");
|
||||
|
||||
{
|
||||
const error_tags = tag.getElementsByClassName("error");
|
||||
error_tags[0].innerHTML = message;
|
||||
}
|
||||
|
||||
if(typeof(detail) === "string") {
|
||||
let node_detail = tag.getElementsByClassName("detail")[0];
|
||||
node_detail.innerHTML = detail;
|
||||
}
|
||||
|
||||
tag.style.display = "block";
|
||||
}
|
||||
|
||||
export function critical_error_handler(handler?: ErrorHandler, override?: boolean) : ErrorHandler {
|
||||
if((typeof(handler) === "object" && handler !== _callback_critical_error) || override)
|
||||
_callback_critical_error = handler;
|
||||
return _callback_critical_error;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
const hello_world = () => {
|
||||
const clog = console.log;
|
||||
const print_security = () => {
|
||||
{
|
||||
const css = [
|
||||
"display: block",
|
||||
"text-align: center",
|
||||
"font-size: 42px",
|
||||
"font-weight: bold",
|
||||
"-webkit-text-stroke: 2px black",
|
||||
"color: red"
|
||||
].join(";");
|
||||
clog("%c ", "font-size: 100px;");
|
||||
clog("%cSecurity warning:", css);
|
||||
}
|
||||
{
|
||||
const css = [
|
||||
"display: block",
|
||||
"text-align: center",
|
||||
"font-size: 18px",
|
||||
"font-weight: bold"
|
||||
].join(";");
|
||||
|
||||
clog("%cPasting anything in here could give attackers access to your data.", css);
|
||||
clog("%cUnless you understand exactly what you are doing, close this window and stay safe.", css);
|
||||
clog("%c ", "font-size: 100px;");
|
||||
}
|
||||
};
|
||||
|
||||
/* print the hello world */
|
||||
{
|
||||
const css = [
|
||||
"display: block",
|
||||
"text-align: center",
|
||||
"font-size: 72px",
|
||||
"font-weight: bold",
|
||||
"-webkit-text-stroke: 2px black",
|
||||
"color: #18BC9C"
|
||||
].join(";");
|
||||
clog("%cHey, hold on!", css);
|
||||
}
|
||||
{
|
||||
const css = [
|
||||
"display: block",
|
||||
"text-align: center",
|
||||
"font-size: 26px",
|
||||
"font-weight: bold"
|
||||
].join(";");
|
||||
|
||||
const css_2 = [
|
||||
"display: block",
|
||||
"text-align: center",
|
||||
"font-size: 26px",
|
||||
"font-weight: bold",
|
||||
"color: blue"
|
||||
].join(";");
|
||||
|
||||
const display_detect = /./;
|
||||
display_detect.toString = function() { print_security(); return ""; };
|
||||
|
||||
clog("%cLovely to see you using and debugging the TeaSpeak Web client.", css);
|
||||
clog("%cIf you have some good ideas or already done some incredible changes,", css);
|
||||
clog("%cyou'll be may interested to share them here: %chttps://github.com/TeaSpeak/TeaWeb", css, css_2);
|
||||
clog("%c ", display_detect);
|
||||
}
|
||||
};
|
||||
|
||||
try { /* lets try to print it as VM code :)*/
|
||||
let hello_world_code = hello_world.toString();
|
||||
hello_world_code = hello_world_code.substr(hello_world_code.indexOf('() => {') + 8);
|
||||
hello_world_code = hello_world_code.substring(0, hello_world_code.lastIndexOf("}"));
|
||||
|
||||
//Look aheads are not possible with firefox
|
||||
//hello_world_code = hello_world_code.replace(/(?<!const|let)(?<=^([^"'/]|"[^"]*"|'[^']*'|`[^`]*`|\/[^/]*\/)*) /gm, ""); /* replace all spaces */
|
||||
hello_world_code = hello_world_code.replace(/[\n\r]/g, ""); /* replace as new lines */
|
||||
|
||||
eval(hello_world_code);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
hello_world();
|
||||
}
|
||||
}
|
||||
|
||||
/* set a timeout here, so if this script is merged with the actual loader (like in rel mode) the actual could load this manually here */
|
||||
setTimeout(() => {
|
||||
if(loader.running()) {
|
||||
if(loader.config.verbose)
|
||||
console.debug("Do not execute debug loading");
|
||||
return;
|
||||
}
|
||||
|
||||
loader.register_task(loader.Stage.INITIALIZING, {
|
||||
priority: 100,
|
||||
function: async () => {
|
||||
let loader_type;
|
||||
location.search.replace(/(?:^\?|&)([a-zA-Z_]+)=([.a-zA-Z0-9]+)(?=$|&)/g, (_, key: string, value: string) => {
|
||||
if(key.toLowerCase() == "loader_target")
|
||||
loader_type = value;
|
||||
return "";
|
||||
});
|
||||
loader_type = loader_type || "app";
|
||||
if(loader_type === "app") {
|
||||
try {
|
||||
await loader.load_scripts(["loader/app.js"]);
|
||||
} catch (error) {
|
||||
console.error("Failed to load main app script: %o", error);
|
||||
loader.critical_error("Failed to load main app script", error);
|
||||
throw "app loader failed";
|
||||
}
|
||||
} else {
|
||||
loader.critical_error("Missing loader target: " + loader_type);
|
||||
throw "Missing loader target: " + loader_type;
|
||||
}
|
||||
},
|
||||
name: "loading target"
|
||||
});
|
||||
|
||||
loader.execute_managed();
|
||||
}, 0);
|
||||
|
||||
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"source_files": [
|
||||
"../js/load.ts"
|
||||
],
|
||||
"target_file": "../declarations/exports_loader.d.ts"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"source_files": [
|
||||
"../loader/loader.ts",
|
||||
"../loader/app.ts"
|
||||
],
|
||||
"target_file": "../declarations/exports_loader_app.d.ts"
|
||||
}
|
|
@ -3,8 +3,7 @@
|
|||
"../js/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"../js/workers/**/*.ts",
|
||||
"../js/load.ts"
|
||||
"../js/workers/**/*.ts"
|
||||
],
|
||||
"target_file": "../declarations/exports_packed.d.ts"
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"include": [
|
||||
"../types",
|
||||
"../declarations/imports_*.d.ts",
|
||||
"../declarations/exports_loader_app.d.ts",
|
||||
"../backend",
|
||||
"../js/**/*.ts"
|
||||
]
|
||||
|
|
|
@ -17,13 +17,12 @@
|
|||
]
|
||||
},
|
||||
"exclude": [
|
||||
"../js/workers",
|
||||
"../js/load.ts"
|
||||
"../js/workers"
|
||||
],
|
||||
"include": [
|
||||
"../types",
|
||||
"../declarations/imports_*.d.ts",
|
||||
"../declarations/exports_loader.d.ts",
|
||||
"../declarations/exports_loader_app.d.ts",
|
||||
"../js/**/*.ts",
|
||||
"../backend"
|
||||
]
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"sourceMap": true
|
||||
"module": "none",
|
||||
"sourceMap": true,
|
||||
"outFile": "../generated/loader_app.js"
|
||||
},
|
||||
"include": [
|
||||
"../types",
|
||||
"../declarations/imports_*.d.ts",
|
||||
"../declarations/exports_packed.d.ts",
|
||||
"../js/load.ts",
|
||||
"../loader/loader.ts",
|
||||
"../loader/app.ts",
|
||||
"../backend"
|
||||
]
|
||||
}
|
76
todo.txt
76
todo.txt
|
@ -15,22 +15,63 @@
|
|||
- Icon uploiad
|
||||
- Avatar list
|
||||
- Server group assignments checkbox
|
||||
- Invite buddy
|
||||
- Identity improve
|
||||
- Identity import
|
||||
|
||||
|
||||
- Ban Liste
|
||||
- Übersicht
|
||||
- Suchfunktion
|
||||
- Text
|
||||
- "Hightlisht own bans"
|
||||
- "Show only own bans"
|
||||
Pro Ban:
|
||||
- Globaler ban oder server ban
|
||||
- Name (Nicht immer gegeben)
|
||||
- IP (Nicht immer gegeben)
|
||||
- UID (Nicht immer gegeben)
|
||||
- HWID (Nicht immer gegeben)
|
||||
- Reason
|
||||
- Creator
|
||||
- Created/Expires
|
||||
- "More info" Button (Dort sieht man auch dann die "Enforcements")
|
||||
|
||||
- Application Options
|
||||
- Crash
|
||||
- Focus crash window on crash
|
||||
- Add a notification (Like the browser notifications)
|
||||
- Reload button
|
||||
- Hinzufügen
|
||||
- Bearbeiten
|
||||
- Löschen
|
||||
|
||||
- Ban "More Info" Dialog
|
||||
Alle daten wie bei der liste, und eine liste mit "trigger" events (enforcements)
|
||||
Pro entry:
|
||||
- Unqiue ID
|
||||
- Hardware ID
|
||||
- Client Name
|
||||
- Connection IP
|
||||
- Timestamp
|
||||
|
||||
- Modal Bookmarks
|
||||
- Bookmark list (+ Subdirectories)
|
||||
- Bookmark Settings
|
||||
- Bookmark/Directory name
|
||||
- Only for bookmarks:
|
||||
- Connect profile
|
||||
- Server address
|
||||
- Server port
|
||||
- Server password
|
||||
- Only for directories
|
||||
- Parent directory
|
||||
- Modal Bookmark create
|
||||
- Type: Bookmark | Directory
|
||||
- Parent directory
|
||||
- Name
|
||||
|
||||
- Client info popup
|
||||
- Basic Info
|
||||
- Name/Unique ID (Database ID vil auch? Oder als hover irgendwo)
|
||||
- TeaForo connected? Ist premium ja nein?
|
||||
|
||||
- Country
|
||||
- Avatar
|
||||
- IP (Wenn permission)
|
||||
|
@ -44,15 +85,30 @@
|
|||
- Description
|
||||
- Server groups
|
||||
- Channel group
|
||||
- Connect count (Wie oft der client schon connectet ist)
|
||||
- Online seit | Idle time
|
||||
- Connect count (Wie oft der client schon connected ist)
|
||||
- Online seid | Idle time
|
||||
- Ping
|
||||
- Client version
|
||||
- First connected
|
||||
|
||||
Nur TeaClient; Nicht für WebClient clients
|
||||
- Bandwidth
|
||||
- Packets send/received | Packet Loss
|
||||
- Up/Download Quota
|
||||
|
||||
- Server
|
||||
- Server region/country
|
||||
- Bandwidths & Transferred data
|
||||
|
||||
- Channel
|
||||
- Audio Codec (Opus (4))
|
||||
- Channel type (Wenn TEMP dann delete delay (nur wenn empty))
|
||||
- Channel Topic
|
||||
- Current clients "2/Unlimited"
|
||||
- Description
|
||||
- Password protected
|
||||
- "Texting mode" | Private | "No saving" | "Logged (+ history length)"
|
||||
|
||||
|
||||
|
||||
- Server "short" info?
|
||||
|
@ -71,6 +127,12 @@
|
|||
- File Transfer bytes transferred, month & global (Up + Download)
|
||||
|
||||
TODO:
|
||||
Server Info (Bandwidth usage)
|
||||
Fix these icons: https://img.did.science/Screenshot_20-11-06.png
|
||||
Fix auto TS compile for vendor emoji-picker
|
||||
Make identity settings a bit smaller (Even scroll on my Laptop)
|
||||
|
||||
|
||||
- Application Options
|
||||
- Crash
|
||||
- Focus crash window on crash
|
||||
- Add a notification (Like the browser notifications)
|
|
@ -1,13 +1,6 @@
|
|||
|
||||
/* File: C:\Users\WolverinDEV\TeaSpeak\TeaWeb\tools\dtsgen\test\test_03.ts */
|
||||
declare enum YY {
|
||||
H = "C",
|
||||
B = "Y"
|
||||
}
|
||||
declare interface X {
|
||||
type: any;
|
||||
c: YY.B;
|
||||
}
|
||||
declare class X {
|
||||
static x();
|
||||
/* File: /home/wolverindev/TeaSpeak/Web-Client/tools/dtsgen/test/test_07.ts */
|
||||
declare namespace C { }
|
||||
declare namespace C {
|
||||
export function test(arg: string);
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit d75eb02ef8038595da5cc82d5953b170df9c49eb
|
||||
Subproject commit 983c9d59542a003df38e80fa1680ab9cfda4c530
|
|
@ -4,30 +4,34 @@ BASEDIR=$(dirname "$0")
|
|||
cd "$BASEDIR"
|
||||
source ../scripts/resolve_commands.sh
|
||||
|
||||
if [ ! -e declarations/imports_shared.d.ts ]; then
|
||||
if [[ ! -e declarations/imports_shared.d.ts ]]; then
|
||||
echo "generate the declarations first!"
|
||||
echo "Execute: /scripts/build_declarations.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -e ../shared/generated/shared.js ]; then
|
||||
if [[ ! -e ../shared/generated/shared.js ]]; then
|
||||
echo "generate the shared packed file first!"
|
||||
echo "Execute: /shared/generate_packed.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
execute_tsc -p tsconfig/tsconfig_packed.json
|
||||
if [ $? -ne 0 ]; then
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Failed to build file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Mergin files"
|
||||
echo "Merging files"
|
||||
|
||||
if [ -e generated/client.js ]; then
|
||||
if [[ -e generated/client.js ]]; then
|
||||
rm generated/client.js
|
||||
fi
|
||||
cat ../shared/generated/shared.js > generated/client.js
|
||||
cat generated/web.js >> generated/client.js
|
||||
|
||||
npm run minify-web-rel-file
|
||||
if [[ -e generated/client.min.js ]]; then
|
||||
rm generated/client.min.js
|
||||
fi
|
||||
|
||||
npm run minify-web-rel-file `pwd`/generated/client.min.js `pwd`/generated/client.js
|
|
@ -120,7 +120,7 @@ namespace ppt {
|
|||
new_hooks.push(hook);
|
||||
if(!old_hooks.remove(hook) && hook.callback_press) {
|
||||
hook.callback_press();
|
||||
console.debug("Trigger key press for %o!", hook);
|
||||
log.trace(LogCategory.GENERAL, tr("Trigger key press for %o!"), hook);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ namespace ppt {
|
|||
for(const hook of old_hooks)
|
||||
if(hook.callback_release) {
|
||||
hook.callback_release();
|
||||
console.debug("Trigger key release for %o!", hook);
|
||||
log.trace(LogCategory.GENERAL, tr("Trigger key release for %o!"), hook);
|
||||
}
|
||||
key_hooks_active = new_hooks;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace audio.player {
|
|||
}
|
||||
|
||||
function fire_initialized() {
|
||||
console.log("Fire initialized: %o", _initialized_listener);
|
||||
log.info(LogCategory.AUDIO, tr("File initialized for %d listeners"), _initialized_listener.length);
|
||||
while(_initialized_listener.length > 0)
|
||||
_initialized_listener.pop_front()();
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ namespace audio.player {
|
|||
(_globalContextPromise = _globalContext.resume()).then(() => {
|
||||
fire_initialized();
|
||||
}).catch(error => {
|
||||
displayCriticalError("Failed to initialize global audio context! (" + error + ")");
|
||||
loader.critical_error("Failed to initialize global audio context! (" + error + ")");
|
||||
});
|
||||
}
|
||||
_globalContext.resume(); //We already have our listener
|
||||
|
|
|
@ -49,7 +49,7 @@ abstract class BasicCodec implements Codec {
|
|||
|
||||
|
||||
encodeSamples(cache: CodecClientCache, pcm: AudioBuffer) {
|
||||
this._encodeResampler.resample(pcm).catch(error => console.error(tr("Could not resample PCM data for codec. Error: %o"), error))
|
||||
this._encodeResampler.resample(pcm).catch(error => log.error(LogCategory.VOICE, tr("Could not resample PCM data for codec. Error: %o"), error))
|
||||
.then(buffer => this.encodeSamples0(cache, buffer as any)).catch(error => console.error(tr("Could not encode PCM data for codec. Error: %o"), error))
|
||||
|
||||
}
|
||||
|
@ -74,12 +74,12 @@ abstract class BasicCodec implements Codec {
|
|||
if(result instanceof Uint8Array) {
|
||||
let time = Date.now() - encodeBegin;
|
||||
if(time > 20)
|
||||
console.error(tr("Required time: %d"), time);
|
||||
log.warn(LogCategory.VOICE, tr("Voice buffer stalled in WorkerPipe longer then expected: %d"), time);
|
||||
//if(time > 20)
|
||||
// chat.serverChat().appendMessage("Required decode time: " + time);
|
||||
this.on_encoded_data(result);
|
||||
}
|
||||
else console.error("[Codec][" + this.name() + "] Could not encode buffer. Result: " + result); //TODO tr
|
||||
else log.error(LogCategory.VOICE, "[Codec][" + this.name() + "] Could not encode buffer. Result: " + result); //TODO tr
|
||||
});
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -152,16 +152,14 @@ class CodecWrapperWorker extends BasicCodec {
|
|||
}
|
||||
|
||||
private onWorkerMessage(message: any) {
|
||||
if(Date.now() - message["timestamp"] > 5)
|
||||
console.warn(tr("Worker message stock time: %d"), Date.now() - message["timestamp"]);
|
||||
if(!message["token"]) {
|
||||
console.error(tr("Invalid worker token!"));
|
||||
log.error(LogCategory.VOICE, tr("Invalid worker token!"));
|
||||
return;
|
||||
}
|
||||
|
||||
if(message["token"] == this._workerCallbackToken) {
|
||||
if(message["type"] == "loaded") {
|
||||
console.log(tr("[Codec] Got worker init response: Success: %o Message: %o"), message["success"], message["message"]);
|
||||
log.info(LogCategory.VOICE, tr("[Codec] Got worker init response: Success: %o Message: %o"), message["success"], message["message"]);
|
||||
if(message["success"]) {
|
||||
if(this._workerCallbackResolve)
|
||||
this._workerCallbackResolve();
|
||||
|
@ -176,10 +174,14 @@ class CodecWrapperWorker extends BasicCodec {
|
|||
//FIXME?
|
||||
return;
|
||||
}
|
||||
console.log(tr("Costume callback! (%o)"), message);
|
||||
log.debug(LogCategory.VOICE, tr("Costume callback! (%o)"), message);
|
||||
return;
|
||||
}
|
||||
|
||||
/* lets warn on general packets. Control packets are allowed to "stuck" a bit longer */
|
||||
if(Date.now() - message["timestamp"] > 5)
|
||||
log.warn(LogCategory.VOICE, tr("Worker message stock time: %d"), Date.now() - message["timestamp"]);
|
||||
|
||||
for(let entry of this._workerListener) {
|
||||
if(entry.token == message["token"]) {
|
||||
entry.resolve(message);
|
||||
|
@ -188,8 +190,7 @@ class CodecWrapperWorker extends BasicCodec {
|
|||
}
|
||||
}
|
||||
|
||||
//TODO tr
|
||||
console.error("Could not find worker token entry! (" + message["token"] + ")");
|
||||
log.error(LogCategory.VOICE, tr("Could not find worker token entry! (%o)"), message["token"]);
|
||||
}
|
||||
|
||||
private spawnWorker() : Promise<Boolean> {
|
||||
|
|
|
@ -58,7 +58,7 @@ namespace connection {
|
|||
|
||||
destroy() {
|
||||
this.disconnect("handle destroyed").catch(error => {
|
||||
console.warn(tr("Failed to disconnect on server connection destroy: %o"), error);
|
||||
log.warn(LogCategory.NETWORKING, tr("Failed to disconnect on server connection destroy: %o"), error);
|
||||
}).then(() => {
|
||||
clearInterval(this._ping.thread_id);
|
||||
clearTimeout(this._connect_timeout_timer);
|
||||
|
@ -67,7 +67,7 @@ namespace connection {
|
|||
try {
|
||||
listener.reject("handler destroyed");
|
||||
} catch(error) {
|
||||
console.warn(tr("Failed to reject command promise: %o"), error);
|
||||
log.warn(LogCategory.NETWORKING, tr("Failed to reject command promise: %o"), error);
|
||||
}
|
||||
}
|
||||
this._retListener = undefined;
|
||||
|
@ -86,7 +86,7 @@ namespace connection {
|
|||
}
|
||||
|
||||
on_connect: () => void = () => {
|
||||
console.log(tr("Socket connected"));
|
||||
log.info(LogCategory.NETWORKING, tr("Socket connected"));
|
||||
this.client.log.log(log.server.Type.CONNECTION_LOGIN, {});
|
||||
this._handshakeHandler.initialize();
|
||||
this._handshakeHandler.startHandshake();
|
||||
|
@ -105,7 +105,7 @@ namespace connection {
|
|||
try {
|
||||
await this.disconnect()
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to close old connection properly. Error: %o"), error);
|
||||
log.error(LogCategory.NETWORKING, tr("Failed to close old connection properly. Error: %o"), error);
|
||||
throw "failed to cleanup old connection";
|
||||
}
|
||||
}
|
||||
|
@ -177,7 +177,7 @@ namespace connection {
|
|||
local_socket.onerror = e => {
|
||||
if(this._socket != local_socket) return; /* this socket isn't from interest anymore */
|
||||
|
||||
console.log(tr("Received web socket error: (%o)"), e);
|
||||
log.warn(LogCategory.NETWORKING, tr("Received web socket error: (%o)"), e);
|
||||
};
|
||||
|
||||
local_socket.onmessage = msg => {
|
||||
|
@ -241,12 +241,12 @@ namespace connection {
|
|||
try {
|
||||
json = JSON.parse(data);
|
||||
} catch(e) {
|
||||
console.error(tr("Could not parse message json!"));
|
||||
log.warn(LogCategory.NETWORKING, tr("Could not parse message json!"));
|
||||
alert(e); // error in the above string (in this case, yes)!
|
||||
return;
|
||||
}
|
||||
if(json["type"] === undefined) {
|
||||
console.log(tr("Missing data type!"));
|
||||
log.warn(LogCategory.NETWORKING, tr("Missing data type in message!"));
|
||||
return;
|
||||
}
|
||||
if(json["type"] === "command") {
|
||||
|
@ -271,7 +271,7 @@ namespace connection {
|
|||
if(this._voice_connection)
|
||||
this._voice_connection.handleControlPacket(json);
|
||||
else
|
||||
console.log(tr("Dropping WebRTC command packet, because we haven't a bridge."))
|
||||
log.warn(LogCategory.NETWORKING, tr("Dropping WebRTC command packet, because we haven't a bridge."))
|
||||
} else if(json["type"] === "ping") {
|
||||
this.sendData(JSON.stringify({
|
||||
type: 'pong',
|
||||
|
@ -288,7 +288,7 @@ namespace connection {
|
|||
log.debug(LogCategory.NETWORKING, tr("Received new pong. Updating ping to: JS: %o Native: %o"), this._ping.value.toFixed(3), this._ping.value_native.toFixed(3));
|
||||
}
|
||||
} else {
|
||||
console.log(tr("Unknown command type %o"), json["type"]);
|
||||
log.warn(LogCategory.NETWORKING, tr("Unknown command type %o"), json["type"]);
|
||||
}
|
||||
} else {
|
||||
log.warn(LogCategory.NETWORKING, tr("Received unknown message of type %s. Dropping message"), typeof(data));
|
||||
|
@ -317,7 +317,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."));
|
||||
log.warn(LogCategory.NETWORKING, tr("Tried to send a command without a valid connection."));
|
||||
return Promise.reject(tr("not connected"));
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ class AudioResampler {
|
|||
|
||||
resample(buffer: AudioBuffer) : Promise<AudioBuffer> {
|
||||
if(!buffer) {
|
||||
console.warn(tr("Received empty buffer as input! Returning empty output!"));
|
||||
log.warn(LogCategory.AUDIO, tr("Received empty buffer as input! Returning empty output!"));
|
||||
return Promise.resolve(buffer);
|
||||
}
|
||||
//console.log("Encode from %i to %i", buffer.sampleRate, this.targetSampleRate);
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace audio {
|
|||
if(_queried_devices.length > 0 && _queried_devices.filter(e => e.default_input).length == 0)
|
||||
_queried_devices[0].default_input = true;
|
||||
} catch(error) {
|
||||
console.warn(tr("Failed to query microphone devices (%o)"), error);
|
||||
log.error(LogCategory.AUDIO, tr("Failed to query microphone devices (%o)"), error);
|
||||
_queried_devices = [];
|
||||
}
|
||||
}
|
||||
|
@ -357,7 +357,7 @@ namespace audio {
|
|||
if(callback.callback_audio)
|
||||
callback.callback_audio(event.inputBuffer);
|
||||
if(callback.callback_buffer) {
|
||||
console.warn(tr("AudioInput has callback buffer, but this isn't supported yet!"));
|
||||
log.warn(LogCategory.AUDIO, tr("AudioInput has callback buffer, but this isn't supported yet!"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -371,7 +371,7 @@ namespace audio {
|
|||
if(this._state != InputState.PAUSED)
|
||||
return;
|
||||
} catch(error) {
|
||||
console.debug(tr("JavascriptInput:start() Start promise await resulted in an error: %o"), error);
|
||||
log.debug(LogCategory.AUDIO, tr("JavascriptInput:start() Start promise await resulted in an error: %o"), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -400,7 +400,7 @@ namespace audio {
|
|||
if(!media_function) return InputStartResult.ENOTSUPPORTED;
|
||||
|
||||
try {
|
||||
console.info(tr("Requesting a microphone stream for device %s in group %s"), device_id, group_id);
|
||||
log.info(LogCategory.AUDIO, tr("Requesting a microphone stream for device %s in group %s"), device_id, group_id);
|
||||
|
||||
const audio_constrains: MediaTrackConstraints = {};
|
||||
audio_constrains.deviceId = device_id;
|
||||
|
@ -420,13 +420,13 @@ namespace audio {
|
|||
//createErrorModal(tr("Failed to create microphone"), tr("Microphone recording failed. Please allow TeaWeb access to your microphone")).open();
|
||||
//FIXME: Move this to somewhere else!
|
||||
|
||||
console.warn(tr("Microphone request failed (No permissions). Browser message: %o"), error.message);
|
||||
log.warn(LogCategory.AUDIO, tr("Microphone request failed (No permissions). Browser message: %o"), error.message);
|
||||
return InputStartResult.ENOTALLOWED;
|
||||
} else {
|
||||
console.warn(tr("Microphone request failed. Request resulted in error: %o: %o"), error.name, error);
|
||||
log.warn(LogCategory.AUDIO, tr("Microphone request failed. Request resulted in error: %o: %o"), error.name, error);
|
||||
}
|
||||
} else {
|
||||
console.warn(tr("Failed to initialize recording stream (%o)"), error);
|
||||
log.warn(LogCategory.AUDIO, tr("Failed to initialize recording stream (%o)"), error);
|
||||
}
|
||||
return InputStartResult.EUNKNOWN;
|
||||
}
|
||||
|
@ -506,7 +506,7 @@ namespace audio {
|
|||
try {
|
||||
await this.stop();
|
||||
} catch(error) {
|
||||
console.warn(tr("Failed to stop previous record session (%o)"), error);
|
||||
log.warn(LogCategory.AUDIO, tr("Failed to stop previous record session (%o)"), error);
|
||||
}
|
||||
|
||||
this._current_device = device as any; /* TODO: Test for device_id and device_group */
|
||||
|
@ -519,7 +519,7 @@ namespace audio {
|
|||
try {
|
||||
await this.start()
|
||||
} catch(error) {
|
||||
console.warn(tr("Failed to start new recording stream (%o)"), error);
|
||||
log.warn(LogCategory.AUDIO, tr("Failed to start new recording stream (%o)"), error);
|
||||
throw "failed to start record";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,23 +34,23 @@ namespace audio {
|
|||
|
||||
playback_buffer(buffer: AudioBuffer) {
|
||||
if(!buffer) {
|
||||
console.warn(tr("[AudioController] Got empty or undefined buffer! Dropping it"));
|
||||
log.warn(LogCategory.VOICE, tr("[AudioController] Got empty or undefined buffer! Dropping it"));
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this.speakerContext) {
|
||||
console.warn(tr("[AudioController] Failed to replay audio. Global audio context not initialized yet!"));
|
||||
log.warn(LogCategory.VOICE, tr("[AudioController] Failed to replay audio. Global audio context not initialized yet!"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer.sampleRate != this.speakerContext.sampleRate)
|
||||
console.warn(tr("[AudioController] Source sample rate isn't equal to playback sample rate! (%o | %o)"), buffer.sampleRate, this.speakerContext.sampleRate);
|
||||
log.warn(LogCategory.VOICE, tr("[AudioController] Source sample rate isn't equal to playback sample rate! (%o | %o)"), buffer.sampleRate, this.speakerContext.sampleRate);
|
||||
|
||||
this.apply_volume_to_buffer(buffer);
|
||||
|
||||
this._buffered_samples.push(buffer);
|
||||
if(this._player_state == connection.voice.PlayerState.STOPPED || this._player_state == connection.voice.PlayerState.STOPPING) {
|
||||
console.log(tr("[Audio] Starting new playback"));
|
||||
log.info(LogCategory.VOICE, tr("[Audio] Starting new playback"));
|
||||
this.set_state(connection.voice.PlayerState.PREBUFFERING);
|
||||
}
|
||||
|
||||
|
@ -67,11 +67,11 @@ namespace audio {
|
|||
break;
|
||||
}
|
||||
if(this._player_state == connection.voice.PlayerState.PREBUFFERING) {
|
||||
console.log(tr("[Audio] Prebuffering succeeded (Replaying now)"));
|
||||
log.info(LogCategory.VOICE, tr("[Audio] Prebuffering succeeded (Replaying now)"));
|
||||
if(this.callback_playback)
|
||||
this.callback_playback();
|
||||
} else if(this.allowBuffering) {
|
||||
console.log(tr("[Audio] Buffering succeeded (Replaying now)"));
|
||||
log.info(LogCategory.VOICE, tr("[Audio] Buffering succeeded (Replaying now)"));
|
||||
}
|
||||
this._player_state = connection.voice.PlayerState.PLAYING;
|
||||
case connection.voice.PlayerState.PLAYING:
|
||||
|
@ -86,7 +86,7 @@ namespace audio {
|
|||
let buffer: AudioBuffer;
|
||||
while((buffer = this._buffered_samples.pop_front())) {
|
||||
if(this._playing_nodes.length >= this._latency_buffer_length * 1.5 + 3) {
|
||||
console.log(tr("Dropping buffer because playing queue grows to much"));
|
||||
log.info(LogCategory.VOICE, tr("Dropping buffer because playing queue grows to much"));
|
||||
continue; /* drop the data (we're behind) */
|
||||
}
|
||||
if(this._time_index < this.speakerContext.currentTime)
|
||||
|
@ -135,7 +135,7 @@ namespace audio {
|
|||
|
||||
this._player_state = connection.voice.PlayerState.BUFFERING;
|
||||
if(!this.allowBuffering)
|
||||
console.warn(tr("[Audio] Detected a buffer underflow!"));
|
||||
log.warn(LogCategory.VOICE, tr("[Audio] Detected a buffer underflow!"));
|
||||
this.reset_buffer_timeout(true);
|
||||
} else {
|
||||
this._player_state = connection.voice.PlayerState.STOPPED;
|
||||
|
@ -152,7 +152,7 @@ namespace audio {
|
|||
if(restart)
|
||||
this._buffer_timeout = setTimeout(() => {
|
||||
if(this._player_state == connection.voice.PlayerState.PREBUFFERING || this._player_state == connection.voice.PlayerState.BUFFERING) {
|
||||
console.warn(tr("[Audio] Buffering exceeded timeout. Flushing and stopping replay"));
|
||||
log.warn(LogCategory.VOICE, tr("[Audio] Buffering exceeded timeout. Flushing and stopping replay"));
|
||||
this.stopAudio();
|
||||
}
|
||||
this._buffer_timeout = undefined;
|
||||
|
|
|
@ -26,16 +26,16 @@ namespace audio {
|
|||
const dummy_client_id = 0xFFEF;
|
||||
|
||||
this.ownCodec(dummy_client_id, _ => {}).then(codec => {
|
||||
console.log(tr("Release again! (%o)"), codec);
|
||||
log.info(LogCategory.VOICE, tr("Release again! (%o)"), codec);
|
||||
this.releaseCodec(dummy_client_id);
|
||||
}).catch(error => {
|
||||
if(this._supported) {
|
||||
console.warn(tr("Disabling codec support for "), this.name);
|
||||
log.warn(LogCategory.VOICE, tr("Disabling codec support for "), this.name);
|
||||
createErrorModal(tr("Could not load codec driver"), tr("Could not load or initialize codec ") + this.name + "<br>" +
|
||||
"Error: <code>" + JSON.stringify(error) + "</code>").open();
|
||||
console.error(tr("Failed to initialize the opus codec. Error: %o"), error);
|
||||
log.error(LogCategory.VOICE, tr("Failed to initialize the opus codec. Error: %o"), error);
|
||||
} else {
|
||||
console.debug(tr("Failed to initialize already disabled codec. Error: %o"), error);
|
||||
log.debug(LogCategory.VOICE, tr("Failed to initialize already disabled codec. Error: %o"), error);
|
||||
}
|
||||
this._supported = false;
|
||||
});
|
||||
|
@ -61,7 +61,7 @@ namespace audio {
|
|||
//TODO test success flag
|
||||
this.ownCodec(clientId, callback_encoded, false).then(resolve).catch(reject);
|
||||
}).catch(error => {
|
||||
console.error(tr("Could not initialize codec!\nError: %o"), error);
|
||||
log.error(LogCategory.VOICE, tr("Could not initialize codec!\nError: %o"), error);
|
||||
reject(typeof(error) === 'string' ? error : tr("Could not initialize codec!"));
|
||||
});
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ namespace audio {
|
|||
clearInterval(this.send_task);
|
||||
this.dropSession();
|
||||
this.acquire_voice_recorder(undefined, true).catch(error => {
|
||||
console.warn(tr("Failed to release voice recorder: %o"), error);
|
||||
log.warn(LogCategory.VOICE, tr("Failed to release voice recorder: %o"), error);
|
||||
}).then(() => {
|
||||
for(const client of this._audio_clients) {
|
||||
client.abort_replay();
|
||||
|
@ -306,17 +306,17 @@ namespace audio {
|
|||
try {
|
||||
this.dataChannel.send(packet);
|
||||
} catch (error) {
|
||||
console.warn(tr("Failed to send voice packet. Error: %o"), error);
|
||||
log.warn(LogCategory.VOICE, tr("Failed to send voice packet. Error: %o"), error);
|
||||
}
|
||||
} else {
|
||||
console.warn(tr("Could not transfer audio (not connected)"));
|
||||
log.warn(LogCategory.VOICE, tr("Could not transfer audio (not connected)"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
createSession() {
|
||||
if(!audio.player.initialized()) {
|
||||
console.log(tr("Audio player isn't initialized yet. Waiting for gesture."));
|
||||
log.info(LogCategory.VOICE, tr("Audio player isn't initialized yet. Waiting for gesture."));
|
||||
audio.player.on_ready(() => this.createSession());
|
||||
return;
|
||||
}
|
||||
|
@ -352,13 +352,13 @@ namespace audio {
|
|||
this.rtcPeerConnection.onicecandidate = this.on_local_ice_candidate.bind(this);
|
||||
if(this.local_audio_stream) { //May a typecheck?
|
||||
this.rtcPeerConnection.addStream(this.local_audio_stream.stream);
|
||||
console.log(tr("Adding stream (%o)!"), this.local_audio_stream.stream);
|
||||
log.info(LogCategory.VOICE, tr("Adding stream (%o)!"), this.local_audio_stream.stream);
|
||||
}
|
||||
|
||||
this.rtcPeerConnection.createOffer(sdpConstraints).then(offer => {
|
||||
this.on_local_offer_created(offer);
|
||||
}).catch(error => {
|
||||
console.error(tr("Could not create ice offer! error: %o"), error);
|
||||
log.error(LogCategory.VOICE, tr("Could not create ice offer! error: %o"), error);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -384,27 +384,27 @@ namespace audio {
|
|||
handleControlPacket(json) {
|
||||
if(json["request"] === "answer") {
|
||||
const session_description = new RTCSessionDescription(json["msg"]);
|
||||
console.log(tr("Received answer to our offer. Answer: %o"), session_description);
|
||||
log.info(LogCategory.VOICE, tr("Received answer to our offer. Answer: %o"), session_description);
|
||||
this.rtcPeerConnection.setRemoteDescription(session_description).then(() => {
|
||||
console.log(tr("Answer applied successfully. Applying ICE candidates (%d)."), this._ice_cache.length);
|
||||
log.info(LogCategory.VOICE, tr("Answer applied successfully. Applying ICE candidates (%d)."), this._ice_cache.length);
|
||||
this._ice_use_cache = false;
|
||||
for(let msg of this._ice_cache) {
|
||||
this.rtcPeerConnection.addIceCandidate(new RTCIceCandidate(msg)).catch(error => {
|
||||
console.log(tr("Failed to add remote cached ice candidate %s: %o"), msg, error);
|
||||
log.info(LogCategory.VOICE, tr("Failed to add remote cached ice candidate %s: %o"), msg, error);
|
||||
});
|
||||
}
|
||||
this._ice_cache = [];
|
||||
}).catch(error => {
|
||||
console.log(tr("Failed to apply remote description: %o"), error); //FIXME error handling!
|
||||
log.info(LogCategory.VOICE, tr("Failed to apply remote description: %o"), error); //FIXME error handling!
|
||||
});
|
||||
} else if(json["request"] === "ice") {
|
||||
if(!this._ice_use_cache) {
|
||||
console.log(tr("Add remote ice! (%o)"), json["msg"]);
|
||||
log.info(LogCategory.VOICE, tr("Add remote ice! (%o)"), json["msg"]);
|
||||
this.rtcPeerConnection.addIceCandidate(new RTCIceCandidate(json["msg"])).catch(error => {
|
||||
console.log(tr("Failed to add remote ice candidate %s: %o"), json["msg"], error);
|
||||
log.info(LogCategory.VOICE, tr("Failed to add remote ice candidate %s: %o"), json["msg"], error);
|
||||
});
|
||||
} else {
|
||||
console.log(tr("Cache remote ice! (%o)"), json["msg"]);
|
||||
log.info(LogCategory.VOICE, tr("Cache remote ice! (%o)"), json["msg"]);
|
||||
this._ice_cache.push(json["msg"]);
|
||||
}
|
||||
} else if(json["request"] == "status") {
|
||||
|
@ -429,7 +429,7 @@ namespace audio {
|
|||
//if(event.candidate && event.candidate.protocol !== "udp")
|
||||
// return;
|
||||
|
||||
console.log(tr("Gathered local ice candidate %o."), event.candidate);
|
||||
log.info(LogCategory.VOICE, tr("Gathered local ice candidate %o."), event.candidate);
|
||||
if(event.candidate) {
|
||||
this.connection.sendData(JSON.stringify({
|
||||
type: 'WebRTC',
|
||||
|
@ -446,18 +446,18 @@ namespace audio {
|
|||
}
|
||||
|
||||
private on_local_offer_created(localSession) {
|
||||
console.log(tr("Local offer created. Setting up local description. (%o)"), localSession);
|
||||
log.info(LogCategory.VOICE, tr("Local offer created. Setting up local description. (%o)"), localSession);
|
||||
this.rtcPeerConnection.setLocalDescription(localSession).then(() => {
|
||||
console.log(tr("Offer applied successfully. Sending offer to server."));
|
||||
log.info(LogCategory.VOICE, tr("Offer applied successfully. Sending offer to server."));
|
||||
this.connection.sendData(JSON.stringify({type: 'WebRTC', request: "create", msg: localSession}));
|
||||
}).catch(error => {
|
||||
console.log(tr("Failed to apply local description: %o"), error);
|
||||
log.info(LogCategory.VOICE, tr("Failed to apply local description: %o"), error);
|
||||
//FIXME error handling
|
||||
});
|
||||
}
|
||||
|
||||
private on_data_channel(channel) {
|
||||
console.log(tr("Got new data channel! (%s)"), this.dataChannel.readyState);
|
||||
log.info(LogCategory.VOICE, tr("Got new data channel! (%s)"), this.dataChannel.readyState);
|
||||
|
||||
this.connection.client.update_voice_status();
|
||||
}
|
||||
|
@ -471,16 +471,16 @@ namespace audio {
|
|||
let clientId = bin[2] << 8 | bin[3];
|
||||
let packetId = bin[0] << 8 | bin[1];
|
||||
let codec = bin[4];
|
||||
//console.log("Client id " + clientId + " PacketID " + packetId + " Codec: " + codec);
|
||||
//log.info(LogCategory.VOICE, "Client id " + clientId + " PacketID " + packetId + " Codec: " + codec);
|
||||
let client = this.find_client(clientId);
|
||||
if(!client) {
|
||||
console.error(tr("Having voice from unknown audio client? (ClientID: %o)"), clientId);
|
||||
log.error(LogCategory.VOICE, tr("Having voice from unknown audio client? (ClientID: %o)"), clientId);
|
||||
return;
|
||||
}
|
||||
|
||||
let codec_pool = VoiceConnection.codec_pool[codec];
|
||||
if(!codec_pool) {
|
||||
console.error(tr("Could not playback codec %o"), codec);
|
||||
log.error(LogCategory.VOICE, tr("Could not playback codec %o"), codec);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -496,9 +496,9 @@ namespace audio {
|
|||
codec_pool.ownCodec(clientId, e => this.handleEncodedVoicePacket(e, codec), true)
|
||||
.then(decoder => decoder.decodeSamples(client.get_codec_cache(codec), encodedData))
|
||||
.then(buffer => client.playback_buffer(buffer)).catch(error => {
|
||||
console.error(tr("Could not playback client's (%o) audio (%o)"), clientId, error);
|
||||
log.error(LogCategory.VOICE, tr("Could not playback client's (%o) audio (%o)"), clientId, error);
|
||||
if(error instanceof Error)
|
||||
console.error(error.stack);
|
||||
log.error(LogCategory.VOICE, error.stack);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -539,7 +539,7 @@ namespace audio {
|
|||
return false;
|
||||
if(chandler.client_status.input_muted)
|
||||
return false;
|
||||
console.log(tr("Local voice ended"));
|
||||
log.info(LogCategory.VOICE, tr("Local voice ended"));
|
||||
|
||||
if(this.dataChannel)
|
||||
this.send_voice_packet(new Uint8Array(0), this.current_channel_codec());
|
||||
|
@ -547,14 +547,14 @@ namespace audio {
|
|||
|
||||
private handleVoiceStarted() {
|
||||
const chandler = this.connection.client;
|
||||
console.log(tr("Local voice started"));
|
||||
log.info(LogCategory.VOICE, tr("Local voice started"));
|
||||
|
||||
const ch = chandler.getClient();
|
||||
if(ch) ch.speaking = true;
|
||||
}
|
||||
|
||||
private on_recoder_yield() {
|
||||
console.log("Lost recorder!");
|
||||
log.info(LogCategory.VOICE, "Lost recorder!");
|
||||
this._audio_source = undefined;
|
||||
this.acquire_voice_recorder(undefined, true); /* we can ignore the promise because we should finish this directly */
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue