A lot of changes

canary
WolverinDEV 2019-09-12 23:59:35 +02:00
parent 824ae8c4ff
commit 6d2ecc3c69
38 changed files with 3138 additions and 620 deletions

View File

@ -17,6 +17,7 @@
- Fixed some general memory leaks - Fixed some general memory leaks
- Implemented the hostmessage functions - Implemented the hostmessage functions
- Fixed bookmark server password - Fixed bookmark server password
- Improved UI performance
Big UI Improvement: Big UI Improvement:
- New "dark theme" design - New "dark theme" design

View File

@ -48,7 +48,7 @@ fi
#Now lets build the declarations #Now lets build the declarations
echo "Building declarations" echo "Building declarations"
./scripts/build_declarations.sh ./scripts/build_declarations.sh force
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
echo "Failed to generate declarations" echo "Failed to generate declarations"
exit 1 exit 1
@ -64,7 +64,7 @@ if [[ "$type" == "release" ]]; then #Compile everything for release mode
fi fi
#Now compile the web client itself #Now compile the web client itself
echo "Building web client" echo "Building client UI"
./client/generate_packed.sh ./client/generate_packed.sh
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
echo "Failed to build web client" echo "Failed to build web client"
@ -78,7 +78,7 @@ elif [[ "$type" == "development" ]]; then
exit 1 exit 1
fi fi
echo "Building client client source" echo "Building client UI source"
execute_ttsc -p ./client/tsconfig/tsconfig.json execute_ttsc -p ./client/tsconfig/tsconfig.json
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
echo "Failed to compile web sources" echo "Failed to compile web sources"

View File

@ -9,6 +9,8 @@
overflow: hidden; overflow: hidden;
overflow-y: auto; overflow-y: auto;
@include chat-scrollbar-vertical();
} }
/* the channel tree */ /* the channel tree */

View File

@ -1,179 +1,808 @@
.banlist { @import "mixin";
@import "properties";
$category_slide_animation_length: .25s;
.modal-body.modal-ban-list {
padding: 0!important;
display: flex; display: flex;
flex-direction: column; flex-direction: row!important;
justify-content: stretch; justify-content: stretch!important;
flex-grow: 1;
height: 100%; //min-width: 30em!important;
width: 100%; max-height: calc(100vh - 10em);
height: 50em;
width: 80em;
min-height: 20em;
.container-tooltip {
flex-shrink: 0;
flex-grow: 0;
position: relative;
width: 1.6em;
margin-left: .5em;
margin-right: .25em;
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;
}
}
/* general for shrink */
input {
min-width: 4em;
}
.left, .right {
flex-grow: 1;
flex-shrink: 1;
.frame-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: stretch; justify-content: stretch;
flex-grow: 1; }
.container-seperator {
width: 3px;
height: unset!important;
background-color: #222224!important;
.top {
height: 3.75em;
background-color: #303036;
opacity: 0;
@include transition($category_slide_animation_length ease-in-out);
}
}
.left {
background-color: #222226;
min-width: 15em;
.head {
flex-grow: 0;
flex-shrink: 0;
height: 2.5em;
font-size: 1.5em;
.top-menu {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: stretch; justify-content: stretch;
flex-grow: 0;
flex-shrink: 0;
margin-bottom: 5px;
.manage-buttons { background-color: #303036;
flex-grow: 0;
flex-shrink: 0;
margin-right: 5px; .category {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: column;
justify-content: center;
cursor: pointer;
text-align: center;
color: #fefefe;
position: relative;
.background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #222226;
@include transition($category_slide_animation_length ease-in-out);
}
a {
z-index: 1;
}
&.disabled {
color: #454545;
pointer-events: none!important;
}
} }
.search { .category-edit {
flex-grow: 1; .background {
right: 100%;
}
input { &.selected {
width: 100%; .background {
right: 0;
}
}
}
.category-add {
.background {
left: 100%;
}
&.selected {
.background {
left: 0;
}
} }
} }
} }
.entry-container { .body {
flex-grow: 1; flex-grow: 1;
flex-shrink: 1; flex-shrink: 1;
min-height: 6em;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: stretch;
margin-bottom: 5px;
position: relative; position: relative;
overflow: hidden;
width: 100%; .container-add, .container-edit {
height: 100%; flex-grow: 1;
flex-shrink: 1;
.entries { min-height: 6em;
width: 100%; overflow-y: auto;
min-height: 300px;
overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
display: flex; position: absolute;
flex-direction: column;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: .5em;
.container-no-permissions {
position: absolute;
top: 0;
left: 0;
right: -1em; /* due to the translateX the padding gets a bit miscalculated */
bottom: 0;
z-index: 10;
display: flex;
flex-direction: column;
justify-content: center;
background-color: #222226;
a {
align-self: center;
color: hsla(0, 0%, 30%, 1);;
}
}
.group {
flex-grow: 0;
flex-shrink: 0;
> a {
color: #557edc;
text-transform: uppercase;
}
.input-boxed {
height: 2em;
}
&:not(:first-of-type) {
margin-top: .5em;
}
&.group-reason {
textarea {
flex-shrink: 1;
height: 6em; /* show by default 3 rows */
max-height: 12em; /* 6 rows */
}
}
&.group-duration {
.container-value {
display: flex;
flex-direction: row;
justify-content: stretch;
.value {
flex-grow: 2;
flex-shrink: 2;
}
select {
flex-grow: 1;
flex-shrink: 1;
margin-left: .5em;
min-width: 2em;
}
}
}
}
.group-enforcements, .group-creator {
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: row;
justify-content: stretch;
height: 2.5em;
padding-top: .5em;
.key {
flex-grow: 0;
flex-shrink: 1;
min-width: 6em;
color: #557edc;
text-transform: uppercase;
align-self: center;
margin-right: .5em;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.value {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: row;
justify-content: flex-end;
min-width: 4em;
align-self: center;
button {
height: 2em;
font-size: .8em;
align-self: center;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
a {
align-self: center;
margin-right: .5em;
}
.htmltag-client {
color: #999999!important;
font-weight: normal!important;
}
}
}
.group-global {
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: row;
justify-content: stretch;
height: 2.5em;
padding-top: .5em;
.checkbox {
align-self: center;
}
a {
flex-grow: 1;
flex-shrink: 1;
min-width: 2em;
align-self: center;
margin-left: .5em;
color: #557edc;
text-transform: uppercase;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
@include transition($category_slide_animation_length ease-in-out);
@include transform(translateX(0));
} }
.ban-entry { .container-add.hidden {
flex-grow: 0; @include transform(translateX(100%));
}
.container-edit.hidden {
@include transform(translateX(-100%));
}
}
.buttons {
flex-grow: 0;
flex-shrink: 0;
padding: .5em;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
}
.right {
position: relative;
display: flex;
flex-direction: column;
justify-content: stretch;
min-width: 30em;
.container-filter {
flex-shrink: 0;
flex-grow: 0;
display: flex;
flex-direction: row;
justify-content: stretch;
/* well this is shit, but thats how it works */
.form-group {
margin-top: -1em;
margin-left: .25em;
margin-right: .25em;
margin-bottom: .5em;
flex-grow: 1;
flex-shrink: 1;
min-width: 5em;
}
.container-close {
cursor: pointer;
flex-shrink: 0; flex-shrink: 0;
flex-grow: 0;
font-size: 5em;
opacity: 0.3;
align-self: center;
position: relative;
margin-right: .125em;
margin-left: .125em;
width: .5em;
height: .5em;
&:hover {
opacity: 1;
}
@include transition(opacity $button_hover_animation_time ease-in-out);
&:before, &:after {
position: absolute;
left: .25em;
content: ' ';
height: .5em;
width: .05em;
background-color: #5a5a5a;
}
&:before {
transform: rotate(45deg);
}
&:after {
transform: rotate(-45deg);
}
}
}
.container-list {
flex-shrink: 1;
flex-grow: 1;
min-height: 10em;
padding: .25em;
display: flex;
flex-direction: column;
justify-content: stretch;
.entry {
flex-shrink: 0;
flex-grow: 0;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: stretch; justify-content: stretch;
&.header { &.header {
.column { color: #557edc;
font-weight: bold;
&.column-time { margin-right: .5em; /* scroll bar */
margin-right: 0!important; -moz-margin-end: 12px; /* moz scroll bar */
}
}
}
&:not(.header) {
cursor: pointer;
&:hover {
background-color: #00000011;
}
&.selected {
background: blue;
&:hover {
background-color: #0000FFAA;
}
}
} }
.column { .column {
flex-grow: 1; padding: .25em;
&.column-keys { &:not(:first-of-type) {
width: 25%; border-left: 0.125em solid transparent;
}
}
}
display: flex; .body {
flex-direction: column; flex-shrink: 1;
flex-grow: 1;
min-height: 6em;
overflow-x: hidden;
overflow-y: scroll;
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-start;
@include chat-scrollbar-vertical();
.entry {
cursor: pointer;
border: 0.125em solid #222425;
border-radius: $border_radius_large;
background-color: #292a2c;
&:not(:first-of-type) {
margin-top: .3em;
} }
&.column-reason { .column {
border-left-color: #222425;
width: 25%;
} }
&.column-creator { @include transition($button_hover_animation_time ease-in-out);
}
width: 25%; .container-empty, .container-error {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
background-color: #303036;
color: hsla(0, 0%, 30%, 1);
font-size: 1.8em;
> a {
margin-top: -1em; /* looks better then totally centered */
align-self: center;
} }
&.column-time { &.container-error {
width: 25%;
min-width: 230px;
max-width: 300px;
display: flex;
flex-direction: column;
margin-right: -15px; /* because of the scroll bar */
> div {
display: flex;
flex-direction: row;
}
a { a {
display: inline; color: red;
width: 100px;
} }
} }
} }
&.ban-entry-global {
color: red;
}
&.ban-entry-own-bold {
font-weight: bold;
}
} }
} }
.bottom-menu { /* for the ban list only */
.container-banlist {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex; display: flex;
flex-direction: row; flex-direction: column;
justify-content: space-between; justify-content: stretch;
flex-grow: 0;
flex-shrink: 0;
margin-bottom: 5px;
.left { background-color: #303036;
display: flex;
flex-direction: row;
div { .entry {
margin-left: 2px; .column-key {
margin-right: 2px; flex-shrink: 1;
flex-grow: 0;
display: flex;
flex-direction: column;
justify-content: center;
> * {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
min-width: 4em;
width: 20em; /* UUID length */
}
.column-reason {
flex-shrink: 1;
flex-grow: 1;
min-width: 12em;
//width: calc(100% - 28em);
}
.column-expires {
flex-shrink: 0;
flex-grow: 0;
width: 10em;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.column-delete {
flex-shrink: 0;
flex-grow: 0;
display: flex;
flex-direction: column;
justify-content: center;
width: 1.75em;
.button-delete {
width: 1.25em;
height: 1.25em;
cursor: pointer;
align-self: center;
display: flex;
flex-direction: column;
justify-content: center;
> div {
align-self: center;
}
&:hover {
background-color: #393c40;
border-radius: $border_radius_middle;
@include transition($button_hover_animation_time ease-in-out);
}
}
} }
} }
input { .body {
vertical-align: bottom; .entry {
&.selected {
background-color: #18191b;
}
&:hover {
background-color: #333539;
}
&.global {
background-color: #3b2626;
border-color: #7d3536;
&.selected {
background-color: #221717;
}
&:hover {
background-color: #4e3434;
}
}
&.highlight {
border-color: #328f33;
}
}
} }
.bmd-form-group { }
padding-top: 0;
align-self: center;
margin-left: 20px; /* for the trigger list only */
.container-triggerlist {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-direction: column;
justify-content: stretch;
background-color: #303036;
.entry {
.column-properties {
flex-shrink: 1;
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
> * {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
min-width: 4em;
width: 20em;
.property {
display: flex;
flex-direction: row;
justify-content: stretch;
.key, .value {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.key {
flex-grow: 0;
flex-shrink: 4;
width: 9em;
}
.value {
flex-grow: 1;
flex-shrink: 1;
}
@include transition($button_hover_animation_time ease-in-out);
}
}
.column-timestamp {
flex-shrink: 0;
flex-grow: 0;
display: flex;
flex-direction: column;
justify-content: center;
width: 10em;
text-align: center;
}
}
.container-list.highlight {
.property.highlighted {
color: #328f33;
}
}
}
.container-options {
flex-shrink: 0;
flex-grow: 0;
padding: .5em; //TODO: May use .25 from the list above?
min-width: 14em;
display: flex;
flex-direction: row;
justify-content: space-between;
background-color: #222224;
.container-option {
display: flex;
flex-direction: row;
justify-content: stretch;
min-width: 4em;
.checkbox {
flex-grow: 0;
flex-shrink: 0;
align-self: center;
margin-right: .25em;
}
a {
flex-grow: 1;
flex-shrink: 1;
min-width: 2em;
align-self: center;
color: #557edc;
text-transform: uppercase;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
button {
height: 2em;
} }
} }
} }

View File

@ -41,7 +41,7 @@
justify-content: stretch!important; justify-content: stretch!important;
min-width: 30em!important; min-width: 30em!important;
height: 60em; height: 45em;
width: 80em; width: 80em;
.container-tooltip { .container-tooltip {

View File

@ -9,6 +9,7 @@
justify-content: stretch; justify-content: stretch;
width: 1000000em; /* get us some width */ width: 1000000em; /* get us some width */
min-height: 20em; /* Set it here, so we dont have a inner modal scroll */
@include user-select(none); @include user-select(none);
@ -146,6 +147,7 @@
flex-grow: 1; flex-grow: 1;
flex-shrink: 1; flex-shrink: 1;
min-height: 10em;
min-width: 30em; min-width: 30em;
.permission-editor { .permission-editor {
@ -162,6 +164,7 @@
.container >.left { .container >.left {
width: 25%; width: 25%;
min-width: 10em; min-width: 10em;
min-height: 10em;
background-color: #222226; background-color: #222226;
@ -193,6 +196,9 @@
flex-grow: 1; flex-grow: 1;
flex-shrink: 1; flex-shrink: 1;
min-height: 15em;
overflow-y: auto;
flex-direction: column; flex-direction: column;
} }
} }

View File

@ -0,0 +1,212 @@
@import "mixin";
$color_upload: #0a5eaa;
$color_download: #9f2712;
.modal-body.modal-server-info-bandwidth {
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;
}
}
.top {
flex-grow: 0;
flex-shrink: 0;
margin: 1em;
padding: .5em;
display: flex;
flex-direction: row;
justify-content: stretch;
height: 12em;
max-height: 12em;
.container-image {
flex-grow: 0;
flex-shrink: 0;
max-width: 18em;
max-height: 11em; /* 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-stats {
flex-shrink: 1;
flex-grow: 1;
min-width: 25em;
display: flex;
flex-direction: column;
justify-content: space-evenly;
.statistic {
display: flex;
flex-direction: column;
justify-content: flex-start;
> a {
font-size: 1.25em;
color: #e3e3e4;
line-height: normal;
text-transform: uppercase;
}
.values {
display: flex;
flex-direction: row;
justify-content: space-between;
> a {
font-size: 1.2em;
line-height: normal;
}
.upload {
color: $color_upload;
}
.download {
color: $color_download;
}
}
&:not(:first-of-type) {
margin-top: 1em;
}
}
}
}
.bottom {
flex-grow: 0;
flex-shrink: 0;
margin: 1em;
padding: .5em;
border-radius: .2em;
border: 1px solid #1f2122;
background-color: #28292b;
display: flex;
flex-direction: column;
justify-content: stretch;
//height: 15em;
//max-height: 10em;
.statistic {
display: flex;
flex-direction: column;
justify-content: stretch;
.title {
flex-grow: 0;
flex-shrink: 0;
color: #244c78;
font-size: 1.25em;
text-transform: uppercase;
}
.body {
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: row;
justify-content: stretch;
height: 7em;
.container-canvas {
flex-grow: 1;
flex-shrink: 1;
min-width: 6em;
margin-right: 1em;
}
.values {
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: column;
justify-content: center;
width: 8em;
text-align: right;
.upload {
color: $color_upload;
}
.download {
color: $color_download;
}
}
}
&:not(:first-of-type) {
margin-top: 1.5em;
}
}
}
}
@media all and (max-width: 50em) {
.modal-body.modal-server-info {
.container-image {
margin: 0!important;
max-width: 0!important;
}
}
}

View File

@ -958,4 +958,13 @@ input[type=number] {
input.input-boxed { input.input-boxed {
padding: 0.5em; padding: 0.5em;
}
textarea.input-boxed {
resize: vertical;
width: 100%;
min-height: 2em;
padding: .2em .5em;
@include chat-scrollbar-vertical();
} }

View File

@ -212,7 +212,9 @@
<!-- <img src="http://puu.sh/E6NXv/eb2f19c7c3.png"> --> <!-- <img src="http://puu.sh/E6NXv/eb2f19c7c3.png"> -->
<!-- <img src="http://puu.sh/E9jT6/302912ae34.png"> --> <!-- <img src="http://puu.sh/E9jT6/302912ae34.png"> -->
<img src="http://puu.sh/Eb5w4/8d38fe5b8f.png"> <!-- <img src="http://puu.sh/E9jTe/b41f6386de.png"> -->
<!-- <img src="img/style/ban-list.png"> -->
<img src="http://puu.sh/E9jTe/b41f6386de.png">
</div> </div>
<button class="toggle-spoiler-style" style="height: 30px; width: 100px; z-index: 100000000; position: absolute; bottom: 2px;">toggle style</button> <button class="toggle-spoiler-style" style="height: 30px; width: 100px; z-index: 100000000; position: absolute; bottom: 2px;">toggle style</button>
<script> <script>

View File

@ -3278,74 +3278,289 @@
<!-- General management templates --> <!-- General management templates -->
<script class="jsrender-template" id="tmpl_ban_list" type="text/html"> <script class="jsrender-template" id="tmpl_ban_list" type="text/html">
<div class="banlist"> <div> <!-- required for jquery -->
<div class="frame-container"> <div class="left">
<div class="top-menu"> <div class="head">
<div class="form-group bmd-form-group manage-buttons"> <div class="category category-add">
<button class="btn btn-success button-add">{{tr "Add" /}}</button> <a>{{tr "Create" /}}</a>
<button class="btn btn-primary button-edit">{{tr "Edit" /}}</button> <div class="background"></div>
<button class="btn btn-danger button-remove">{{tr "Remove" /}}</button>
</div>
<div class="search form-group">
<label class="bmd-label-floating">{{tr "filter" /}}</label>
<input type="text" class="form-control entry-filter">
</div>
</div> </div>
<div class="entry-container"> <div class="category category-edit">
<div class="ban-entry header"> <a>{{tr "Edit" /}}</a>
<div class="column column-keys">{{tr "Name/IP/UID/HWID"/}}</div> <div class="background"></div>
<div class="column column-reason">{{tr "Reason"/}}</div>
<div class="column column-creator">{{tr "Creator"/}}</div>
<div class="column column-time">{{tr "Created / Expires"/}}</div>
</div>
<div class="entries"></div>
</div> </div>
<div class="bottom-menu"> </div>
<div class="left"> <div class="body">
<button class="btn btn-primary button-refresh btn-raised">{{tr "Reload"/}}</button> <div class="container-add">
<div class="switch"> <div class="container-no-permissions">
<label> <a>{{tr "You dont have permission to create bans" /}}</a>
<input type="checkbox" class="filter-flag-force-own"> </div>
{{tr "Show only own bans" /}} <div class="group group-name">
</label> <a>{{tr "Name" /}}</a>
</div> <div class="input-boxed">
<div class="switch"> <input placeholder="{{tr 'Username to ban' /}}" />
<label>
<input type="checkbox" class="filter-flag-highlight-own">
{{tr "Highlight own bans" /}}
</label>
</div> </div>
</div> </div>
<div class="right"> <div class="group group-ip">
<a class="entry-count-info"></a> <a>{{tr "IP" /}}</a>
<button class="btn btn-secondary btn-raised button-close">{{tr "Close" /}}</button> <div class="input-boxed">
<input placeholder="{{tr 'IP to ban' /}}" />
</div>
</div> </div>
<!-- Currently not supported by TeaSpeak
<div class="group group-interpret">
<a>{{tr "Interpret IP / Name as" /}}</a>
<div class="input-boxed">
<select class="ban-regex-type">
<option value="wildcard-4">{{tr "Wildcard IPv4" /}}</option>
<option value="wildcard-6">{{tr "Wildcard IPv6" /}}</option>
<option value="fixed" selected>{{tr "Fixed string" /}}</option>
<option value="regex">{{tr "Regular Expression" /}}</option>
</select>
</div>
</div>
-->
<div class="group group-unique-id">
<a>{{tr "Unique ID" /}}</a>
<div class="input-boxed">
<input placeholder="{{tr 'Client unique id to ban' /}}" />
</div>
</div>
<div class="group group-hwid">
<a>{{tr "Hardware ID" /}}</a>
<div class="input-boxed">
<input placeholder="{{tr 'No Hardware ID given' /}}" />
<div class="container-tooltip">
<img src="img/icon_tooltip.svg"/>
<div class="tooltip">
<p>
{{tr "The hardware id has different meanings, depends on the users agent<br>" +
"TeaClient: The hardware id will be generate out of some unique system id's<br>" +
"TeaWeb: The TeaSpeak web client hasn't a hardware id, it will be random<br>" +
"TeamSpeak 3 client: The hardware id will be a result of a random generate string within the system registry" /}}
</p>
</div>
</div>
</div>
</div>
<div class="group group-reason">
<a>{{tr "Reason" /}}</a>
<textarea class="input-boxed" placeholder="{{tr 'No reason given' /}}"></textarea>
</div>
<div class="group group-duration">
<a>{{tr "Duration" /}}</a>
<div class="container-value">
<div class="input-boxed value">
<input type="number" value="1" min="1" />
<div class="container-tooltip tooltip-max-time">
<img src="img/icon_tooltip.svg"/>
<div class="tooltip">
<a class="max">error: max duration</a>
</div>
</div>
</div>
<select class="input-boxed">
<option value="sec">{{tr "seconds"/}}</option>
<option value="min">{{tr "minutes"/}}</option>
<option value="hours">{{tr "hours"/}}</option>
<option value="days">{{tr "days"/}}</option>
<option value="perm">{{tr "permanent"/}}</option>
</select>
</div>
</div>
<label class="group-global">
<div class="checkbox">
<input type="checkbox">
<div class="mark"></div>
</div>
<a>{{tr "Use this ban as a global ban" /}}</a>
</label>
</div>
<div class="container-edit">
<div class="group group-name">
<a>{{tr "Name" /}}</a>
<div class="input-boxed">
<input placeholder="{{tr 'No username given' /}}" />
</div>
</div>
<div class="group group-ip">
<a>{{tr "IP" /}}</a>
<div class="input-boxed">
<input placeholder="{{tr 'No IP given' /}}" />
</div>
</div>
<!-- Currently not supported by TeaSpeak
<div class="group group-interpret">
<a>{{tr "Interpret IP / Name as" /}}</a>
<div class="input-boxed">
<select class="ban-regex-type">
<option value="wildcard-4">{{tr "Wildcard IPv4" /}}</option>
<option value="wildcard-6">{{tr "Wildcard IPv6" /}}</option>
<option value="fixed">{{tr "Fixed string" /}}</option>
<option value="regex">{{tr "Regular Expression" /}}</option>
</select>
</div>
</div>
-->
<div class="group group-unique-id">
<a>{{tr "Unique ID" /}}</a>
<div class="input-boxed">
<input placeholder="{{tr 'No Unique ID given' /}}" />
</div>
</div>
<div class="group group-hwid">
<a>{{tr "Hardware ID" /}}</a>
<div class="input-boxed">
<input placeholder="{{tr 'No Hardware ID given' /}}" />
<div class="container-tooltip">
<img src="img/icon_tooltip.svg"/>
<div class="tooltip">
<p>
{{tr "The hardware id has different meanings, depends on the users agent<br>" +
"TeaClient: The hardware id will be generate out of some unique system id's<br>" +
"TeaWeb: The TeaSpeak web client hasn't a hardware id, it will be random<br>" +
"TeamSpeak 3 client: The hardware id will be a result of a random generate string within the system registry" /}}
</p>
</div>
</div>
</div>
</div>
<div class="group group-reason">
<a>{{tr "Reason" /}}</a>
<textarea class="input-boxed" placeholder="{{tr 'No reason given' /}}"></textarea>
</div>
<div class="group group-duration">
<a>{{tr "Duration" /}}</a>
<div class="container-value">
<div class="input-boxed value">
<input type="number" value="1" min="1" />
<div class="container-tooltip tooltip-max-time">
<img src="img/icon_tooltip.svg"/>
<div class="tooltip">
<a class="detailed">error: details</a>
<a class="max">error: max duration</a>
</div>
</div>
</div>
<select class="input-boxed">
<option value="sec">{{tr "seconds"/}}</option>
<option value="min">{{tr "minutes"/}}</option>
<option value="hours">{{tr "hours"/}}</option>
<option value="days">{{tr "days"/}}</option>
<option value="perm">{{tr "permanent"/}}</option>
</select>
</div>
</div>
<div class="group-creator">
<a class="key">{{tr "Creator" /}}</a>
<div class="value">
error: creator value
</div>
</div>
<div class="group-enforcements">
<a class="key">{{tr "Enforcements" /}}</a>
<div class="value">
<a>43</a>
<button class="btn btn-yellow button-enforcement-list">{{tr "Show list" /}}</button>
</div>
</div>
<label class="group-global">
<div class="checkbox disabled">
<input type="checkbox" disabled>
<div class="mark"></div>
</div>
<a>{{tr "This ban is a global ban" /}}</a>
</label>
</div>
</div>
<div class="buttons">
<button class="btn btn-success button-apply">error: button apply</button>
</div>
</div>
<div class="container-seperator vertical" seperator-id="seperator-banlist">
<div class="top"></div>
</div>
<div class="right">
<div class="container-banlist">
<div class="container-filter">
<div class="form-group">
<label class="bmd-label-floating">{{tr "Filter" /}}</label>
<input class="form-control">
</div>
</div>
<div class="container-list">
<div class="entry header">
<div class="column column-key">{{tr "Name/IP/UID/HWID" /}}</div>
<div class="column column-reason">{{tr "Reason" /}}</div>
<div class="column column-expires">{{tr "Expires" /}}</div>
<div class="column column-delete">&nbsp;</div>
</div>
<div class="body">
<div class="container-empty">
<a>error: empty message</a>
</div>
<div class="container-error">
<a>error: no permissions</a>
</div>
<!-- <div class="entry local"></div> -->
</div>
</div>
<div class="container-options">
<label class="container-option">
<div class="checkbox">
<input type="checkbox" class="option-show-own">
<div class="mark"></div>
</div>
<a>{{tr "Show only own bans" /}}</a>
</label>
<label class="container-option">
<div class="checkbox">
<input type="checkbox" class="option-highlight-own">
<div class="mark"></div>
</div>
<a>{{tr "Highlight own bans" /}}</a>
</label>
<button class="btn btn-success button-refresh-banlist">{{tr "Refresh" /}}</button>
</div>
</div>
<div class="container-triggerlist">
<div class="container-filter">
<div class="form-group">
<label class="bmd-label-floating">{{tr "Filter" /}}</label>
<input class="form-control">
</div>
<div class="container-close">
<!-- TODO -->
</div>
</div>
<div class="container-list">
<div class="entry header">
<div class="column column-properties">{{tr "Properties" /}}</div>
<div class="column column-timestamp">{{tr "Timestamp" /}}</div>
</div>
<div class="body">
<div class="container-empty">
<a>error: empty message</a>
</div>
<div class="container-error">
<a>error: no permissions</a>
</div>
<!-- <div class="entry local"></div> -->
</div>
</div>
<div class="container-options">
<label class="container-option">
<div class="checkbox">
<input type="checkbox" class="option-highlight-cause">
<div class="mark"></div>
</div>
<a>{{tr "Highlight cause" /}}</a>
</label>
<button class="btn btn-success button-refresh-triggerlist">{{tr "Refresh" /}}</button>
</div> </div>
</div> </div>
</div> </div>
</script> </div>
<script class="jsrender-template" id="tmpl_ban_entry" type="text/html">
<div class="ban-entry {{if server_id == 0}}ban-entry-global{{/if}} {{if flag_own}}ban-entry-own{{/if}}"
ban-id={{>banid}} server-id={{>server_id}}>
<div class="column column-keys">
{{if name }}<a class="property property-name">name={{>name}}</a>{{/if}}
{{if ip }} <a class="property property-ip">ip={{>ip}}</a>{{/if}}
{{if unique_id }} <a class="property property-unique-id">uid={{>unique_id}}</a>{{/if}}
{{if hardware_id }}<a class="property property-hardware-id">hwid={{>hardware_id}}</a>{{/if}}
</div>
<div class="column column-reason">{{>reason}}</div>
<div class="column column-creator">{{>invoker_name}}</div>
<div class="column column-time">
<div class="timestamp-created"><a>{{tr "Created:" /}}</a> {{fmt_date timestamp_created "DD.MM.YYYY
HH:mm" /}}
</div>
<div class="timestamp-expire"><a>{{tr "Expire:" /}}</a> {{if (!timestamp_expire ||
timestamp_expire.getTime() == 0) }}never{{else}}{{fmt_date timestamp_expire "DD.MM.YYYY HH:mm"
/}}{{/if}}
</div>
</div>
</div>
</script> </script>
<script class="jsrender-template" id="tmpl_client_ban" type="text/html"> <script class="jsrender-template" id="tmpl_client_ban" type="text/html">
@ -3408,6 +3623,7 @@
</div> </div>
</script> </script>
<!-- TODO: Delete me! -->
<script class="jsrender-template" id="tmpl_ban_create" type="text/html"> <script class="jsrender-template" id="tmpl_ban_create" type="text/html">
<div class="bancreate"> <div class="bancreate">
<div class="frame-container"> <div class="frame-container">
@ -5205,5 +5421,63 @@
</div> </div>
</div> </div>
</script> </script>
<script class="jsrender-template" id="tmpl_server_info_bandwidth" type="text/html">
<div> <!-- required for the renderer -->
<div class="top">
<div class="container-image">
<img src="img/serveredit_3.png"> <!-- TODO Get the right image! -->
</div>
<div class="container-stats">
<div class="statistic statistic-packets">
<a>{{tr "Transmitted packets" /}}</a>
<div class="values">
<a class="upload" title="{{tr 'Upload bandwidth' /}}">error: upload</a>
<a class="download" title="{{tr 'Download bandwidth' /}}">error: download</a>
</div>
</div>
<div class="statistic statistic-bytes">
<a>{{tr "Transmitted bytes" /}}</a>
<div class="values">
<a class="upload" title="{{tr 'Upload bandwidth' /}}">error: upload</a>
<a class="download" title="{{tr 'Download bandwidth' /}}">error: download</a>
</div>
</div>
<div class="statistic statistic-ft-bytes">
<a>{{tr "Transferred file transfer bytes" /}}</a>
<div class="values">
<a class="upload" title="{{tr 'Upload bandwidth' /}}">error: upload</a>
<a class="download" title="{{tr 'Download bandwidth' /}}">error: download</a>
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="statistic statistic-bandwidth">
<a class="title">{{tr "Current bandwidth" /}}</a>
<div class="body">
<div class="container-canvas">
<canvas></canvas>
</div>
<div class="values">
<a class="upload" title="{{tr 'Upload bandwidth' /}}">N Bytes/s</a>
<a class="download" title="{{tr 'Download bandwidth' /}}">N Bytes/s</a>
</div>
</div>
</div>
<div class="statistic statistic-ft-bandwidth">
<a class="title">{{tr "Current file transfer bandwidth" /}}</a>
<div class="body">
<div class="container-canvas">
<canvas></canvas>
</div>
<div class="values">
<a class="upload" title="{{tr 'Upload bandwidth' /}}">N Bytes/s</a>
<a class="download" title="{{tr 'Download bandwidth' /}}">N Bytes/s</a>
</div>
</div>
</div>
</div>
</div>
</script>
</body> </body>
</html> </html>

View File

@ -408,8 +408,10 @@ class ConnectionHandler {
case DisconnectReason.SERVER_HOSTMESSAGE: /* already handled */ case DisconnectReason.SERVER_HOSTMESSAGE: /* already handled */
break; break;
case DisconnectReason.HANDLER_DESTROYED: case DisconnectReason.HANDLER_DESTROYED:
if(data) if(data) {
this.sound.play(Sound.CONNECTION_DISCONNECTED); this.sound.play(Sound.CONNECTION_DISCONNECTED);
this.log.log(log.server.Type.DISCONNECTED, {});
}
break; break;
case DisconnectReason.DNS_FAILED: case DisconnectReason.DNS_FAILED:
log.error(LogCategory.CLIENT, tr("Failed to resolve hostname: %o"), data); log.error(LogCategory.CLIENT, tr("Failed to resolve hostname: %o"), data);
@ -622,6 +624,9 @@ class ConnectionHandler {
client_output_muted: this.client_status.output_muted client_output_muted: this.client_status.output_muted
}; };
if(support_record && basic_voice_support)
vconnection.set_encoder_codec(targetChannel.properties.channel_codec);
if(!this.serverConnection.support_voice() || !vconnection.connected()) { if(!this.serverConnection.support_voice() || !vconnection.connected()) {
property_update["client_input_hardware"] = false; property_update["client_input_hardware"] = false;
property_update["client_output_hardware"] = false; property_update["client_output_hardware"] = false;

View File

@ -51,9 +51,9 @@ namespace transfer {
} }
export interface UploadTransfer { export interface UploadTransfer {
put_data(data: BlobPart | File) : Promise<void>; get_key(): UploadKey;
get_key(): transfer.UploadKey; put_data(data: BlobPart | File) : Promise<void>;
} }
export type DownloadKey = TransferKey; export type DownloadKey = TransferKey;

View File

@ -143,7 +143,7 @@ namespace connection {
this.connection_handler.channelTree.registerClient(this.connection_handler.getClient()); this.connection_handler.channelTree.registerClient(this.connection_handler.getClient());
this.connection.client.side_bar.channel_conversations().reset(); this.connection.client.side_bar.channel_conversations().reset();
this.connection.client.clientId = parseInt(json["aclid"]); this.connection.client.clientId = parseInt(json["aclid"]);
this.connection.client.getClient().updateVariables({key: "client_nickname", value: json["acn"]}); this.connection.client.getClient().updateVariables( {key: "client_nickname", value: json["acn"]});
let updates: { let updates: {
key: string, key: string,
@ -412,6 +412,8 @@ namespace connection {
} else if(reason_id == ViewReasonId.VREASON_CHANNEL_KICK) { } else if(reason_id == ViewReasonId.VREASON_CHANNEL_KICK) {
if(own_channel == channel) if(own_channel == channel)
this.connection_handler.sound.play(Sound.USER_ENTERED_KICKED); this.connection_handler.sound.play(Sound.USER_ENTERED_KICKED);
} else if(reason_id == ViewReasonId.VREASON_SYSTEM) {
} else { } else {
console.warn(tr("Unknown reasonid for %o"), reason_id); console.warn(tr("Unknown reasonid for %o"), reason_id);
} }
@ -550,6 +552,8 @@ namespace connection {
json = json[0]; //Only one bulk json = json[0]; //Only one bulk
let tree = this.connection.client.channelTree; let tree = this.connection.client.channelTree;
let client = tree.findClient(json["clid"]); let client = tree.findClient(json["clid"]);
let self = client instanceof LocalClientEntry;
let channel_to = tree.findChannel(json["ctid"]); let channel_to = tree.findChannel(json["ctid"]);
let channel_from = tree.findChannel(json["cfid"]); let channel_from = tree.findChannel(json["cfid"]);
@ -562,12 +566,21 @@ namespace connection {
log.error(LogCategory.NETWORKING, tr("Unknown client move (Channel to)!")); log.error(LogCategory.NETWORKING, tr("Unknown client move (Channel to)!"));
return 0; return 0;
} }
if(!channel_from) //Not critical
log.error(LogCategory.NETWORKING, tr("Unknown client move (Channel from)!"));
let self = client instanceof LocalClientEntry; if(!self) {
if(!channel_from) {
log.error(LogCategory.NETWORKING, tr("Unknown client move (Channel from)!"));
} else if(channel_to !== client.currentChannel()) {
log.error(LogCategory.NETWORKING,
tr("Client move from invalid source channel! Local client registered in channel %d but server send %d."),
client.currentChannel().channelId, channel_from.channelId
);
}
}
let current_clients: ClientEntry[]; let current_clients: ClientEntry[];
if(self) { if(self) {
channel_from = client.currentChannel();
current_clients = client.channelTree.clientsByChannel(client.currentChannel()); current_clients = client.channelTree.clientsByChannel(client.currentChannel());
this.connection_handler.update_voice_status(channel_to); this.connection_handler.update_voice_status(channel_to);
} }
@ -585,9 +598,11 @@ namespace connection {
if(conversation_to) if(conversation_to)
conversation_to.update_private_state(); conversation_to.update_private_state();
const conversation_from = side_bar.channel_conversations().conversation(channel_from.channelId, false); if(channel_from) {
if(conversation_from) const conversation_from = side_bar.channel_conversations().conversation(channel_from.channelId, false);
conversation_from.update_private_state(); if(conversation_from)
conversation_from.update_private_state();
}
side_bar.channel_conversations().update_chat_box(); side_bar.channel_conversations().update_chat_box();
} }
@ -695,6 +710,11 @@ namespace connection {
updates.push({key: key, value: json[key]}); updates.push({key: key, value: json[key]});
} }
channel.updateVariables(...updates); channel.updateVariables(...updates);
if(this.connection_handler.getClient().currentChannel() === channel) {
//TODO: Playback sound that your channel has been edited
this.connection_handler.update_voice_status();
}
} }
handleNotifyTextMessage(json) { handleNotifyTextMessage(json) {
@ -979,7 +999,12 @@ namespace connection {
sender_database_id: parseInt(entry["sender_database_id"]) sender_database_id: parseInt(entry["sender_database_id"])
}, false); }, false);
} }
/* now update the boxes */
/* No update needed because the command which triggers this notify should update the chat box on success
conversation.fix_scroll(true); conversation.fix_scroll(true);
conversation.handle.update_chat_box();
*/
} }
handleNotifyConversationMessageDelete(json: any[]) { handleNotifyConversationMessageDelete(json: any[]) {

View File

@ -88,6 +88,9 @@ namespace connection {
abstract voice_recorder() : RecorderProfile; abstract voice_recorder() : RecorderProfile;
abstract acquire_voice_recorder(recorder: RecorderProfile | undefined) : Promise<void>; abstract acquire_voice_recorder(recorder: RecorderProfile | undefined) : Promise<void>;
abstract get_encoder_codec() : number;
abstract set_encoder_codec(codec: number);
} }
} }

View File

@ -10,6 +10,7 @@ enum LogCategory {
GENERAL, GENERAL,
NETWORKING, NETWORKING,
VOICE, VOICE,
CHAT,
AUDIO, AUDIO,
I18N, I18N,
IPC, IPC,
@ -37,6 +38,7 @@ namespace log {
[LogCategory.NETWORKING, "Network "], [LogCategory.NETWORKING, "Network "],
[LogCategory.VOICE, "Voice "], [LogCategory.VOICE, "Voice "],
[LogCategory.AUDIO, "Audio "], [LogCategory.AUDIO, "Audio "],
[LogCategory.CHANNEL, "Chat "],
[LogCategory.I18N, "I18N "], [LogCategory.I18N, "I18N "],
[LogCategory.IDENTITIES, "Identities "], [LogCategory.IDENTITIES, "Identities "],
[LogCategory.IPC, "IPC "], [LogCategory.IPC, "IPC "],
@ -54,6 +56,7 @@ namespace log {
[LogCategory.NETWORKING, true], [LogCategory.NETWORKING, true],
[LogCategory.VOICE, true], [LogCategory.VOICE, true],
[LogCategory.AUDIO, true], [LogCategory.AUDIO, true],
[LogCategory.CHAT, true],
[LogCategory.I18N, false], [LogCategory.I18N, false],
[LogCategory.IDENTITIES, true], [LogCategory.IDENTITIES, true],
[LogCategory.IPC, true], [LogCategory.IPC, true],

View File

@ -307,7 +307,7 @@ function main() {
/* initialize font */ /* initialize font */
{ {
const font = settings.static_global(Settings.KEY_FONT_SIZE, parseInt(getComputedStyle(document.body).fontSize)); const font = settings.static_global(Settings.KEY_FONT_SIZE, 14); //parseInt(getComputedStyle(document.body).fontSize)
$(document.body).css("font-size", font + "px"); $(document.body).css("font-size", font + "px");
} }
@ -422,10 +422,46 @@ function main() {
*/ */
// Modals.openServerInfo(connection.channelTree.server); // Modals.openServerInfo(connection.channelTree.server);
//Modals.createServerModal(connection.channelTree.server, properties => Promise.resolve()); //Modals.createServerModal(connection.channelTree.server, properties => Promise.resolve());
}, 1000);
//Modals.openClientInfo(connection.getClient());
//Modals.openServerInfoBandwidth(connection.channelTree.server);
Modals.openBanList(connection);
}, 4000);
//Modals.spawnSettingsModal("identity-profiles"); //Modals.spawnSettingsModal("identity-profiles");
//Modals.spawnKeySelect(console.log); //Modals.spawnKeySelect(console.log);
Modals.spawnBookmarkModal(); //Modals.spawnBookmarkModal();
/*
{
const modal = createModal({
header: tr("Test Net Graph"),
body: () => {
const canvas = $.spawn("canvas")
.css("position", "absolute")
.css({
top: 0,
bottom: 0,
right: 0,
left: 0
});
return $.spawn("div")
.css("height", "5em")
.css("width", "30em")
.css("position", "relative")
.append(canvas);
},
footer: null
});
const graph = new net.graph.Graph(modal.htmlTag.find("canvas")[0] as any);
graph.initialize();
modal.close_listener.push(() => graph.terminate());
modal.open();
}
*/
} }
const task_teaweb_starter: loader.Task = { const task_teaweb_starter: loader.Task = {

View File

@ -47,7 +47,8 @@ class Group {
} }
updateProperty(key, value) { updateProperty(key, value) {
JSON.map_field_to(this.properties, value, key); if(!JSON.map_field_to(this.properties, value, key))
return; /* no updates */
if(key == "iconid") { if(key == "iconid") {
this.properties.iconid = (new Uint32Array([this.properties.iconid]))[0]; this.properties.iconid = (new Uint32Array([this.properties.iconid]))[0];

View File

@ -8,8 +8,8 @@ interface Array<T> {
} }
interface JSON { interface JSON {
map_to<T>(object: T, json: any, variables?: string | string[], validator?: (map_field: string, map_value: string) => boolean, variable_direction?: number) : T; map_to<T>(object: T, json: any, variables?: string | string[], validator?: (map_field: string, map_value: string) => boolean, variable_direction?: number) : number;
map_field_to<T>(object: T, value: any, field: string) : T; map_field_to<T>(object: T, value: any, field: string) : boolean;
} }
type JQueryScrollType = "height" | "width"; type JQueryScrollType = "height" | "width";
@ -42,7 +42,7 @@ interface String {
} }
if(!JSON.map_to) { if(!JSON.map_to) {
JSON.map_to = function <T>(object: T, json: any, variables?: string | string[], validator?: (map_field: string, map_value: string) => boolean, variable_direction?: number): T { JSON.map_to = function <T>(object: T, json: any, variables?: string | string[], validator?: (map_field: string, map_value: string) => boolean, variable_direction?: number): number {
if (!validator) validator = (a, b) => true; if (!validator) validator = (a, b) => true;
if (!variables) { if (!variables) {
@ -59,6 +59,7 @@ if(!JSON.map_to) {
variables = [variables]; variables = [variables];
} }
let updates = 0;
for (let field of variables) { for (let field of variables) {
if (!json[field]) { if (!json[field]) {
console.trace(tr("Json does not contains %s"), field); console.trace(tr("Json does not contains %s"), field);
@ -69,24 +70,32 @@ if(!JSON.map_to) {
continue; continue;
} }
JSON.map_field_to(object, json[field], field); if(JSON.map_field_to(object, json[field], field))
updates++;
} }
return object; return updates;
} }
} }
if(!JSON.map_field_to) { if(!JSON.map_field_to) {
JSON.map_field_to = function<T>(object: T, value: any, field: string) : T { JSON.map_field_to = function<T>(object: T, value: any, field: string) : boolean {
let field_type = typeof(object[field]); let field_type = typeof(object[field]);
let new_object;
if(field_type == "string" || field_type == "object" || field_type == "undefined") if(field_type == "string" || field_type == "object" || field_type == "undefined")
object[field as string] = value; new_object = value;
else if(field_type == "number") else if(field_type == "number")
object[field as string] = parseFloat(value); new_object = parseFloat(value);
else if(field_type == "boolean") else if(field_type == "boolean")
object[field as string] = value == "1" || value == "true"; new_object = value == "1" || value == "true";
else console.warn(tr("Invalid object type %s for entry %s"), field_type, field); else {
console.warn(tr("Invalid object type %s for entry %s"), field_type, field);
return false;
}
return object; if(new_object === object[field as string]) return false;
object[field as string] = new_object;
return true;
} }
} }

View File

@ -371,7 +371,20 @@ class ChannelEntry {
return this._tag_clients; return this._tag_clients;
} }
reorderClients() { private _reorder_timer: number;
reorderClients(sync?: boolean) {
if(this._reorder_timer) {
if(!sync) return;
clearTimeout(this._reorder_timer);
this._reorder_timer = undefined;
} else if(!sync) {
this._reorder_timer = setTimeout(() => {
this._reorder_timer = undefined;
this.reorderClients(true);
}, 5) as any;
return;
}
let clients = this.clients(); let clients = this.clients();
if(clients.length > 1) { if(clients.length > 1) {
@ -674,35 +687,18 @@ class ChannelEntry {
tag_container_name.removeClass(ChannelEntry.NAME_ALIGNMENTS.join(" ")); tag_container_name.removeClass(ChannelEntry.NAME_ALIGNMENTS.join(" "));
const tag_name = tag_container_name.find(".channel-name"); const tag_name = tag_container_name.find(".channel-name");
tag_name.text(this._channel_name_formatted === undefined ? this.properties.channel_name : this._channel_name_formatted); let text = this._channel_name_formatted === undefined ? this.properties.channel_name : this._channel_name_formatted;
if(this._channel_name_formatted !== undefined) { if(this._channel_name_formatted !== undefined) {
tag_container_name.addClass(this._channel_name_alignment); tag_container_name.addClass(this._channel_name_alignment);
if(this._channel_name_alignment == "align-repetitive") { if(this._channel_name_alignment == "align-repetitive") {
if(tag_name.parent().width() != 0) { while(text.length < 1024 * 8)
let lastSuccess = ""; text += text;
let index = 6;
let name = this._channel_name_formatted;
if(name.length < 1) throw "invalid name!";
while(index-- > 0)
name = name + name;
tag_name.text(name);
do {
tag_name.text(name = name + name);
if(name.length > 1024 * 8)
index = 63;
} while (tag_name.parent().width() >= tag_name.width() && ++index < 64);
if(index == 64)
log.warn(LogCategory.CHANNEL, tr("Repeating spacer took too much repeats!"));
if(lastSuccess.length > 0) {
tag_name.text(lastSuccess);
}
}
} }
} }
tag_name.text(text);
} }
recalculate_repetitive_name() { recalculate_repetitive_name() {

View File

@ -1393,7 +1393,8 @@ class MusicClientEntry extends ClientEntry {
handlePlayerInfo(json) { handlePlayerInfo(json) {
if(json) { if(json) {
let info = JSON.map_to(new MusicClientPlayerInfo(), json); const info = new MusicClientPlayerInfo();
JSON.map_to(info, json);
if(this._info_promise_resolve) if(this._info_promise_resolve)
this._info_promise_resolve(info); this._info_promise_resolve(info);
this._info_promise_reject = undefined; this._info_promise_reject = undefined;

View File

@ -109,10 +109,9 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
} }
private on_global_click(event) { private on_global_click(event) {
let menu = this._context_menu || (this._context_menu = $(".context-menu")); //let menu = this._context_menu || (this._context_menu = $(".context-menu"));
if(!menu.is(":visible")) return;
if(!this._visible) return;
if ($(event.target).parents(".context-menu").length == 0) { if ($(event.target).parents(".context-menu").length == 0) {
this.despawn_context_menu(); this.despawn_context_menu();
event.preventDefault(); event.preventDefault();

View File

@ -0,0 +1,426 @@
namespace net.graph {
export type Entry = {
timestamp: number;
upload: number;
download: number;
highlight?: boolean;
}
export type Style = {
background_color: string;
separator_color: string;
separator_count: number;
separator_width: number;
upload: {
fill: string;
stroke: string;
strike_width: number;
},
download: {
fill: string;
stroke: string;
strike_width: number;
}
}
export type TimeSpan = {
origin: {
begin: number;
end: number;
time: number;
},
target: {
begin: number;
end: number;
time: number;
}
}
/* Great explanation of Bezier curves: http://en.wikipedia.org/wiki/Bezier_curve#Quadratic_curves
*
* Assuming A was the last point in the line plotted and B is the new point,
* we draw a curve with control points P and Q as below.
*
* A---P
* |
* |
* |
* Q---B
*
* Importantly, A and P are at the same y coordinate, as are B and Q. This is
* so adjacent curves appear to flow as one.
*/
export class Graph {
private static _loops: (() => any)[] = [];
readonly canvas: HTMLCanvasElement;
public style: Style = {
background_color: "#28292b",
//background_color: "red",
separator_color: "#283036",
//separator_color: 'blue',
separator_count: 10,
separator_width: 1,
upload: {
fill: "#2d3f4d",
stroke: "#336e9f",
strike_width: 2,
},
download: {
fill: "#532c26",
stroke: "#a9321c",
strike_width: 2,
}
};
private _canvas_context: CanvasRenderingContext2D;
private _entries: Entry[] = [];
private _entry_max = {
upload: 1,
download: 1,
};
private _max_space = 1.12;
private _max_gap = 5;
private _listener_mouse_move;
private _listener_mouse_out;
private _animate_loop;
_time_span: TimeSpan = {
origin: {
begin: 0,
end: 1,
time: 0
},
target: {
begin: 0,
end: 1,
time: 1
}
};
private _detailed_shown = false;
callback_detailed_info: (upload: number, download: number, timestamp: number, event: MouseEvent) => any;
callback_detailed_hide: () => any;
constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas;
this._animate_loop = () => this.draw();
this.recalculate_cache(); /* initialize cache */
}
initialize() {
this._canvas_context = this.canvas.getContext("2d");
Graph._loops.push(this._animate_loop);
if(Graph._loops.length == 1) {
const static_loop = () => {
Graph._loops.forEach(l => l());
if(Graph._loops.length > 0)
requestAnimationFrame(static_loop);
else
console.log("STATIC terminate!");
};
static_loop();
}
this.canvas.onmousemove = this.on_mouse_move.bind(this);
this.canvas.onmouseleave = this.on_mouse_leave.bind(this);
}
terminate() {
Graph._loops.remove(this._animate_loop);
}
max_gap_size(value?: number) : number { return typeof(value) === "number" ? (this._max_gap = value) : this._max_gap; }
private recalculate_cache(time_span?: boolean) {
this._entries = this._entries.sort((a, b) => a.timestamp - b.timestamp);
this._entry_max = {
download: 1,
upload: 1
};
if(time_span) {
this._time_span = {
origin: {
begin: 0,
end: 0,
time: 0
},
target: {
begin: this._entries.length > 0 ? this._entries[0].timestamp : 0,
end: this._entries.length > 0 ? this._entries.last().timestamp : 0,
time: 0
}
};
}
for(const entry of this._entries) {
this._entry_max.upload = Math.max(this._entry_max.upload, entry.upload);
this._entry_max.download = Math.max(this._entry_max.download, entry.download);
}
this._entry_max.upload *= this._max_space;
this._entry_max.download *= this._max_space;
}
insert_entry(entry: Entry) {
if(this._entries.length > 0 && entry.timestamp < this._entries.last().timestamp)
throw "invalid timestamp";
this._entries.push(entry);
this._entry_max.upload = Math.max(this._entry_max.upload, entry.upload * this._max_space);
this._entry_max.download = Math.max(this._entry_max.download, entry.download * this._max_space);
}
insert_entries(entries: Entry[]) {
this._entries.push(...entries);
this.recalculate_cache();
this.cleanup();
}
resize() {
this.canvas.style.height = "100%";
this.canvas.style.width = "100%";
const cstyle = getComputedStyle(this.canvas);
this.canvas.width = parseInt(cstyle.width);
this.canvas.height = parseInt(cstyle.height);
}
cleanup() {
const time = this.calculate_time_span();
let index = 0;
for(;index < this._entries.length; index++) {
if(this._entries[index].timestamp < time.begin)
continue;
if(index == 0)
return;
break;
}
/* keep the last entry as a reference point to the left */
if(index > 1) {
this._entries.splice(0, index - 1);
this.recalculate_cache();
}
}
calculate_time_span() : { begin: number; end: number } {
const time = Date.now();
if(time >= this._time_span.target.time)
return this._time_span.target;
if(time <= this._time_span.origin.time)
return this._time_span.origin;
const ob = this._time_span.origin.begin;
const oe = this._time_span.origin.end;
const ot = this._time_span.origin.time;
const tb = this._time_span.target.begin;
const te = this._time_span.target.end;
const tt = this._time_span.target.time;
const offset = (time - ot) / (tt - ot);
return {
begin: ob + (tb - ob) * offset,
end: oe + (te - oe) * offset,
};
}
draw() {
let ctx = this._canvas_context;
const height = this.canvas.height;
const width = this.canvas.width;
//console.log("Painting on %ox%o", height, width);
ctx.shadowBlur = 0;
ctx.filter = "";
ctx.lineCap = "square";
ctx.fillStyle = this.style.background_color;
ctx.fillRect(0, 0, width, height);
/* first of all print the separators */
{
const sw = this.style.separator_width;
const swh = this.style.separator_width / 2;
ctx.lineWidth = sw;
ctx.strokeStyle = this.style.separator_color;
ctx.beginPath();
/* horizontal */
{
const dw = width / this.style.separator_count;
let dx = dw / 2;
while(dx < width) {
ctx.moveTo(Math.floor(dx - swh) + .5, .5);
ctx.lineTo(Math.floor(dx - swh) + .5, Math.floor(height) + .5);
dx += dw;
}
}
/* vertical */
{
const dh = height / 3; //tree lines (top, center, bottom)
let dy = dh / 2;
while(dy < height) {
ctx.moveTo(.5, Math.floor(dy - swh) + .5);
ctx.lineTo(Math.floor(width) + .5, Math.floor(dy - swh) + .5);
dy += dh;
}
}
ctx.stroke();
ctx.closePath();
}
/* draw the lines */
{
const t = this.calculate_time_span();
const tb = t.begin; /* time begin */
const dt = t.end - t.begin; /* delta time */
const dtw = width / dt; /* delta time width */
const draw_graph = (type: "upload" | "download", direction: number, max: number) => {
const hy = Math.floor(height / 2); /* half y */
const by = hy - direction * this.style[type].strike_width; /* the "base" line */
const marked_points: ({x: number, y: number})[] = [];
ctx.beginPath();
ctx.moveTo(0, by);
let x, y, lx = 0, ly = by; /* last x, last y */
const floor = a => a; //Math.floor;
for(const entry of this._entries) {
x = floor((entry.timestamp - tb) * dtw);
y = floor(hy - direction * Math.max(hy * (entry[type] / max), this.style[type].strike_width));
if(entry.timestamp < tb) {
lx = x;
ly = y;
continue;
}
if(x - lx > this._max_gap && this._max_gap > 0) {
ctx.lineTo(lx, by);
ctx.lineTo(x, by);
ctx.lineTo(x, y);
lx = x;
ly = y;
continue;
}
ctx.bezierCurveTo((x + lx) / 2, ly, (x + lx) / 2, y, x, y);
if(entry.highlight)
marked_points.push({x: x, y: y});
lx = x;
ly = y;
}
ctx.strokeStyle = this.style[type].stroke;
ctx.lineWidth = this.style[type].strike_width;
ctx.lineJoin = "miter";
ctx.stroke();
//Close the path and fill
ctx.lineTo(width, hy);
ctx.lineTo(0, hy);
ctx.fillStyle = this.style[type].fill;
ctx.fill();
ctx.closePath();
{
ctx.beginPath();
const radius = 3;
for(const point of marked_points) {
ctx.moveTo(point.x, point.y);
ctx.ellipse(point.x, point.y, radius, radius, 0, 0, 2 * Math.PI, false);
}
ctx.stroke();
ctx.fill();
ctx.closePath();
}
};
const shared_max = Math.max(this._entry_max.upload, this._entry_max.download);
draw_graph("upload", 1, shared_max);
draw_graph("download", -1, shared_max);
}
}
private on_mouse_move(event: MouseEvent) {
const offset = event.offsetX;
const max_offset = this.canvas.width;
if(offset < 0) return;
if(offset > max_offset) return;
const time_span = this.calculate_time_span();
const time = time_span.begin + (time_span.end - time_span.begin) * (offset / max_offset);
let index = 0;
for(;index < this._entries.length; index++) {
if(this._entries[index].timestamp > time)
break;
}
const entry_before = this._entries[index - 1]; /* In JS negative array access is allowed and returns undefined */
const entry_next = this._entries[index]; /* In JS negative array access is allowed and returns undefined */
let entry: Entry;
if(!entry_before || !entry_next) {
entry = entry_before || entry_next;
} else {
const dn = entry_next.timestamp - time;
const db = time - entry_before.timestamp;
if(dn > db)
entry = entry_before;
else
entry = entry_next;
}
if(!entry) {
this.on_mouse_leave(event);
} else {
this._entries.forEach(e => e.highlight = false);
this._detailed_shown = true;
entry.highlight = true;
if(this.callback_detailed_info)
this.callback_detailed_info(entry.upload, entry.download, entry.timestamp, event);
}
}
private on_mouse_leave(event: MouseEvent) {
if(!this._detailed_shown) return;
this._detailed_shown = false;
this._entries.forEach(e => e.highlight = false);
if(this.callback_detailed_hide)
this.callback_detailed_hide();
}
}
}

View File

@ -77,9 +77,9 @@ class ControlBar {
.attr("title", server.properties.virtualserver_hostbutton_tooltip || server.properties.virtualserver_hostbutton_gfx_url) .attr("title", server.properties.virtualserver_hostbutton_tooltip || server.properties.virtualserver_hostbutton_gfx_url)
.attr("href", server.properties.virtualserver_hostbutton_url); .attr("href", server.properties.virtualserver_hostbutton_url);
this._button_hostbanner.find("img").attr("src", server.properties.virtualserver_hostbutton_gfx_url); this._button_hostbanner.find("img").attr("src", server.properties.virtualserver_hostbutton_gfx_url);
this._button_hostbanner.show(); this._button_hostbanner.each((_, e) => { e.style.display = null; });
} else { } else {
this._button_hostbanner.hide(); this._button_hostbanner.each((_, e) => { e.style.display = "none"; });
} }
} }
@ -470,6 +470,7 @@ class ControlBar {
this.connection_handler.handleDisconnect(DisconnectReason.REQUESTED); //TODO message? this.connection_handler.handleDisconnect(DisconnectReason.REQUESTED); //TODO message?
this.update_connection_state(); this.update_connection_state();
this.connection_handler.sound.play(Sound.CONNECTION_DISCONNECTED); this.connection_handler.sound.play(Sound.CONNECTION_DISCONNECTED);
this.log.log(log.server.Type.DISCONNECTED, {});
} }
private on_token_use() { private on_token_use() {

View File

@ -304,6 +304,7 @@ namespace top_menu {
handler.handleDisconnect(DisconnectReason.REQUESTED); //TODO message? handler.handleDisconnect(DisconnectReason.REQUESTED); //TODO message?
server_connections.active_connection_handler().serverConnection.disconnect(); server_connections.active_connection_handler().serverConnection.disconnect();
handler.sound.play(Sound.CONNECTION_DISCONNECTED); handler.sound.play(Sound.CONNECTION_DISCONNECTED);
this.log.log(log.server.Type.DISCONNECTED, {});
} }
control_bar.update_connection_state(); control_bar.update_connection_state();
update_state(); update_state();
@ -438,7 +439,6 @@ namespace top_menu {
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to view the ban list")).open(); createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to view the ban list")).open();
scon.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS); scon.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
} }
Modals.spawnPlaylistManage(scon);
} else { } else {
createErrorModal(tr("You have to be connected"), tr("You have to be connected to use this function!")).open(); createErrorModal(tr("You have to be connected"), tr("You have to be connected to use this function!")).open();
} }

View File

@ -145,11 +145,11 @@ namespace MessageHelper {
unit?: string, unit?: string,
exact?: boolean exact?: boolean
}) : string { }) : string {
options = Object.assign(options || {}, { options = options || {};
time: undefined, if(typeof options.exact !== "boolean")
unit: "Bytes", options.exact = true;
exact: true if(typeof options.unit !== "string")
}); options.unit = "Bytes";
let points = value.toFixed(0).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'); let points = value.toFixed(0).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
@ -170,9 +170,22 @@ namespace MessageHelper {
unit = ""; unit = "";
v = value; v = value;
} }
if(unit && options.time)
unit = unit + "/" + options.time; let result = "";
return (options.exact || !unit ? (points + " " + (options.unit || "")) : "") + (unit ? (" / " + v.toFixed(2) + " " + unit) : ""); if(options.exact || !unit) {
result += points;
if(options.unit) {
result += " " + options.unit;
if(options.time)
result += "/" + options.time;
}
}
if(unit) {
result += (result ? " / " : "") + v.toFixed(2) + " " + unit;
if(options.time)
result += "/" + options.time;
}
return result;
} }
} }

View File

@ -19,6 +19,9 @@ namespace chat {
private _value_ping: JQuery; private _value_ping: JQuery;
private _ping_updater: number; private _ping_updater: number;
private _channel_text: ChannelEntry;
private _channel_voice: ChannelEntry;
private _button_conversation: HTMLElement; private _button_conversation: HTMLElement;
constructor(handle: Frame) { constructor(handle: Frame) {
@ -99,6 +102,7 @@ namespace chat {
update_channel_talk() { update_channel_talk() {
const client = this.handle.handle.getClient(); const client = this.handle.handle.getClient();
const channel = client ? client.currentChannel() : undefined; const channel = client ? client.currentChannel() : undefined;
this._channel_voice = channel;
const html_tag = this._html_tag.find(".value-voice-channel"); const html_tag = this._html_tag.find(".value-voice-channel");
const html_limit_tag = this._html_tag.find(".value-voice-limit"); const html_limit_tag = this._html_tag.find(".value-voice-limit");
@ -111,15 +115,7 @@ namespace chat {
client.handle.fileManager.icons.generateTag(channel.properties.channel_icon_id).appendTo(html_tag); client.handle.fileManager.icons.generateTag(channel.properties.channel_icon_id).appendTo(html_tag);
$.spawn("div").text(channel.channelName()).appendTo(html_tag); $.spawn("div").text(channel.channelName()).appendTo(html_tag);
//channel.properties.channel_maxclients this.update_channel_limit(channel, html_limit_tag);
let channel_limit = tr("Unlimited");
if(!channel.properties.channel_flag_maxclients_unlimited)
channel_limit = "" + channel.properties.channel_maxclients;
else if(!channel.properties.channel_flag_maxfamilyclients_unlimited) {
if(channel.properties.channel_maxfamilyclients >= 0)
channel_limit = "" + channel.properties.channel_maxfamilyclients;
}
html_limit_tag.text(channel.clients(false).length + " / " + channel_limit);
} else { } else {
$.spawn("div").text("Not connected").appendTo(html_tag); $.spawn("div").text("Not connected").appendTo(html_tag);
} }
@ -129,6 +125,7 @@ namespace chat {
const channel_tree = this.handle.handle.connected ? this.handle.handle.channelTree : undefined; const channel_tree = this.handle.handle.connected ? this.handle.handle.channelTree : undefined;
const current_channel_id = channel_tree ? this.handle.channel_conversations().current_channel() : 0; 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 channel = channel_tree ? channel_tree.findChannel(current_channel_id) : undefined;
this._channel_text = channel;
const tag_container = this._html_tag.find(".mode-channel_chat.channel"); const tag_container = this._html_tag.find(".mode-channel_chat.channel");
const html_tag_title = tag_container.find(".title"); const html_tag_title = tag_container.find(".title");
@ -146,14 +143,7 @@ namespace chat {
this.handle.handle.fileManager.icons.generateTag(channel.properties.channel_icon_id).appendTo(html_tag); this.handle.handle.fileManager.icons.generateTag(channel.properties.channel_icon_id).appendTo(html_tag);
$.spawn("div").text(channel.channelName()).appendTo(html_tag); $.spawn("div").text(channel.channelName()).appendTo(html_tag);
let channel_limit = tr("Unlimited"); this.update_channel_limit(channel, html_limit_tag);
if(!channel.properties.channel_flag_maxclients_unlimited)
channel_limit = "" + channel.properties.channel_maxclients;
else if(!channel.properties.channel_flag_maxfamilyclients_unlimited) {
if(channel.properties.channel_maxfamilyclients >= 0)
channel_limit = "" + channel.properties.channel_maxfamilyclients;
}
html_limit_tag.text(channel.clients(false).length + " / " + channel_limit);
} else if(channel_tree && current_channel_id > 0) { } else if(channel_tree && current_channel_id > 0) {
html_tag.append(MessageHelper.formatMessage(tr("Unknown channel id {}"), current_channel_id)); html_tag.append(MessageHelper.formatMessage(tr("Unknown channel id {}"), current_channel_id));
} else if(channel_tree && current_channel_id == 0) { } else if(channel_tree && current_channel_id == 0) {
@ -169,6 +159,24 @@ namespace chat {
} }
} }
update_channel_client_count(channel: ChannelEntry) {
if(channel === this._channel_text)
this.update_channel_limit(channel, this._html_tag.find(".value-text-limit"));
if(channel === this._channel_voice)
this.update_channel_limit(channel, this._html_tag.find(".value-voice-limit"));
}
private update_channel_limit(channel: ChannelEntry, tag: JQuery) {
let channel_limit = tr("Unlimited");
if(!channel.properties.channel_flag_maxclients_unlimited)
channel_limit = "" + channel.properties.channel_maxclients;
else if(!channel.properties.channel_flag_maxfamilyclients_unlimited) {
if(channel.properties.channel_maxfamilyclients >= 0)
channel_limit = "" + channel.properties.channel_maxfamilyclients;
}
tag.text(channel.clients(false).length + " / " + channel_limit);
}
update_chat_counter() { update_chat_counter() {
const conversations = this.handle.private_conversations().conversations(); const conversations = this.handle.private_conversations().conversations();
{ {
@ -226,18 +234,24 @@ namespace chat {
private _html_input: JQuery<HTMLDivElement>; private _html_input: JQuery<HTMLDivElement>;
private _enabled: boolean; private _enabled: boolean;
private __callback_text_changed; private __callback_text_changed;
private __callback_key_down; private __callback_key_down
private __callback_key_up;
private __callback_paste; private __callback_paste;
private _typing_timeout: number; /* ID when the next callback_typing will be called */ private _typing_timeout: number; /* ID when the next callback_typing will be called */
private _typing_last_event: number; /* timestamp of the last typing event */ private _typing_last_event: number; /* timestamp of the last typing event */
private _message_history: string[] = [];
private _message_history_length = 100;
private _message_history_index = 1;
typing_interval: number = 2000; /* update frequency */ typing_interval: number = 2000; /* update frequency */
callback_typing: () => any; callback_typing: () => any;
callback_text: (text: string) => any; callback_text: (text: string) => any;
constructor() { constructor() {
this._enabled = true; this._enabled = true;
this.__callback_key_up = this._callback_key_up.bind(this);
this.__callback_key_down = this._callback_key_down.bind(this); this.__callback_key_down = this._callback_key_down.bind(this);
this.__callback_text_changed = this._callback_text_changed.bind(this); this.__callback_text_changed = this._callback_text_changed.bind(this);
this.__callback_paste = event => this._callback_paste(event); this.__callback_paste = event => this._callback_paste(event);
@ -269,6 +283,7 @@ namespace chat {
this._html_input.on("cut paste drop keydown 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("change", this.__callback_text_changed);
this._html_input.on("keydown", this.__callback_key_down); this._html_input.on("keydown", this.__callback_key_down);
this._html_input.on("keyup", this.__callback_key_up);
this._html_input.on("paste", this.__callback_paste); this._html_input.on("paste", this.__callback_paste);
} }
@ -380,10 +395,7 @@ namespace chat {
} }
private _callback_key_down(event: KeyboardEvent) { private _callback_key_down(event: KeyboardEvent) {
if(event.shiftKey) if(event.key.toLowerCase() === "enter" && !event.shiftKey) {
return;
if(event.key.toLowerCase() === "enter") {
event.preventDefault(); event.preventDefault();
/* deactivate chatbox when no callback? */ /* deactivate chatbox when no callback? */
@ -391,6 +403,11 @@ namespace chat {
if(!this.test_message(text)) if(!this.test_message(text))
return; return;
this._message_history.push(text);
this._message_history_index = this._message_history.length;
if(this._message_history.length > this._message_history_length)
this._message_history = this._message_history.slice(this._message_history.length - this._message_history_length);
if(this.callback_text) { if(this.callback_text) {
this.callback_text(helpers.preprocess_chat_message(text)); this.callback_text(helpers.preprocess_chat_message(text));
} }
@ -403,16 +420,48 @@ namespace chat {
this.__callback_text_changed(); this.__callback_text_changed();
this._typing_timeout = 0; /* enable text change listener again */ this._typing_timeout = 0; /* enable text change listener again */
}); });
} else if(event.key.toLowerCase() === "arrowdown") {
//TODO: Test for at the last line within the box
if(this._message_history_index < 0) return;
if(this._message_history_index >= this._message_history.length) return; /* OOB, even with the empty message */
this._message_history_index++;
this._html_input[0].innerText = this._message_history[this._message_history_index] || ""; /* OOB just returns "undefined" */
} else if(event.key.toLowerCase() === "arrowup") {
//TODO: Test for at the first line within the box
if(this._message_history_index <= 0) return; /* we cant go "down" */
this._message_history_index--;
this._html_input[0].innerText = this._message_history[this._message_history_index];
} else {
if(this._message_history_index >= 0) {
if(this._message_history_index >= this._message_history.length) {
if("" !== this._html_input[0].innerText)
this._message_history_index = -1;
} else if(this._message_history[this._message_history_index] !== this._html_input[0].innerText)
this._message_history_index = -1;
}
} }
} }
private _callback_key_up(event: KeyboardEvent) {
if("" === this._html_input[0].innerText)
this._message_history_index = this._message_history.length;
}
private _context_task: number;
set_enabled(flag: boolean) { set_enabled(flag: boolean) {
if(this._enabled === flag) if(this._enabled === flag)
return; return;
this._enabled = flag; if(!this._context_task) {
this._html_input.prop("contenteditable", flag); this._enabled = flag;
this._html_tag.find('.button-emoji').toggleClass("disabled", !flag); /* Allow the browser to asynchronously recalculate everything */
this._context_task = setTimeout(() => {
this._context_task = undefined;
this._html_input.each((_, e) => { e.contentEditable = this._enabled ? "true" : "false"; });
});
this._html_tag.find('.button-emoji').toggleClass("disabled", !flag);
}
} }
is_enabled() { is_enabled() {
@ -589,10 +638,10 @@ test
} }
private render_token(token: RemarkToken, index: number) { private render_token(token: RemarkToken, index: number) {
console.log("Render token: %o", token); log.debug(LogCategory.GENERAL, tr("Render Markdown token: %o"), token);
const renderer = Renderer.renderers[token.type]; const renderer = Renderer.renderers[token.type];
if(typeof(renderer) === "undefined") { if(typeof(renderer) === "undefined") {
console.warn(tr("Missing markdown to bbcode renderer for token %s: %o"), token.type, token); log.warn(LogCategory.CHAT, tr("Missing markdown to bbcode renderer for token %s: %o"), token.type, token);
return token.content || ""; return token.content || "";
} }
@ -936,14 +985,14 @@ test
this.fix_scroll(false); this.fix_scroll(false);
this.save_history(); this.save_history();
}).catch(error => { }).catch(error => {
console.warn(tr("Failed to load private conversation history for user %s on server %s: %o"), log.warn(LogCategory.CHAT, tr("Failed to load private conversation history for user %s on server %s: %o"),
this.client_unique_id, this.handle.handle.handle.channelTree.server.properties.virtualserver_unique_identifier, error); this.client_unique_id, this.handle.handle.handle.channelTree.server.properties.virtualserver_unique_identifier, error);
}) })
} }
private save_history() { private save_history() {
helpers.history.save_history(this.history_key(), this._message_history).catch(error => { helpers.history.save_history(this.history_key(), this._message_history).catch(error => {
console.warn(tr("Failed to save private conversation history for user %s on server %s: %o"), log.warn(LogCategory.CHAT, tr("Failed to save private conversation history for user %s on server %s: %o"),
this.client_unique_id, this.handle.handle.handle.channelTree.server.properties.virtualserver_unique_identifier, error); this.client_unique_id, this.handle.handle.handle.channelTree.server.properties.virtualserver_unique_identifier, error);
}); });
} }
@ -1107,13 +1156,10 @@ test
let offset; let offset;
if(this._spacer_unread_message) { if(this._spacer_unread_message) {
offset = this._displayed_message_first_tag(this._spacer_unread_message)[0].offsetTop; offset = this._displayed_message_first_tag(this._spacer_unread_message)[0].offsetTop;
console.log("Scroll by unread: %o", offset);
} else if(typeof(this._scroll_position) !== "undefined") { } else if(typeof(this._scroll_position) !== "undefined") {
offset = this._scroll_position; offset = this._scroll_position;
console.log("Scroll by scroll: %o", offset);
} else { } else {
offset = this._html_message_container[0].scrollHeight; offset = this._html_message_container[0].scrollHeight;
console.log("Height: %o", offset);
} }
if(animate) { if(animate) {
this._html_message_container.stop(true).animate({ this._html_message_container.stop(true).animate({
@ -1446,7 +1492,7 @@ test
if(this._callback_message) if(this._callback_message)
this._callback_message(message); this._callback_message(message);
else { else {
console.warn(tr("Dropping conversation message for client %o because of no message callback."), { log.warn(LogCategory.CHAT, tr("Dropping conversation message for client %o because of no message callback."), {
client_name: this.client_name, client_name: this.client_name,
client_id: this.client_id, client_id: this.client_id,
client_unique_id: this.client_unique_id client_unique_id: this.client_unique_id
@ -1505,7 +1551,7 @@ test
this.update_typing_state(); this.update_typing_state();
this._chat_box.callback_text = message => { this._chat_box.callback_text = message => {
if(!this._current_conversation) { if(!this._current_conversation) {
console.warn(tr("Dropping conversation message because of no active conversation.")); log.warn(LogCategory.CHAT, tr("Dropping conversation message because of no active conversation."));
return; return;
} }
this._current_conversation.call_message(message); this._current_conversation.call_message(message);
@ -1513,11 +1559,10 @@ test
this._chat_box.callback_typing = () => { this._chat_box.callback_typing = () => {
if(!this._current_conversation) { if(!this._current_conversation) {
console.warn(tr("Dropping conversation typing action because of no active conversation.")); log.warn(LogCategory.CHAT, tr("Dropping conversation typing action because of no active conversation."));
return; return;
} }
console.log("TYPING!");
const connection = this.handle.handle.serverConnection; const connection = this.handle.handle.serverConnection;
if(!connection || !connection.connected()) if(!connection || !connection.connected())
return; return;
@ -1602,7 +1647,7 @@ test
if(conv) { if(conv) {
conv.set_text_callback(message => { conv.set_text_callback(message => {
console.log(tr("Sending text message %s to %o"), message, partner); log.debug(LogCategory.CLIENT, tr("Sending text message %s to %o"), message, partner);
this.handle.handle.serverConnection.send_command("sendtextmessage", {"targetmode": 1, "target": partner.client_id, "msg": message}).catch(error => { this.handle.handle.serverConnection.send_command("sendtextmessage", {"targetmode": 1, "target": partner.client_id, "msg": message}).catch(error => {
if(error instanceof CommandResult) { if(error instanceof CommandResult) {
if(error.id == ErrorID.CLIENT_INVALID_ID) { if(error.id == ErrorID.CLIENT_INVALID_ID) {
@ -1615,7 +1660,7 @@ test
} }
} else { } else {
conv.append_error(tr("Failed to send message. Lookup the console for more details")); conv.append_error(tr("Failed to send message. Lookup the console for more details"));
console.error(tr("Failed to send conversation message: %o", error)); log.error(LogCategory.CHAT, tr("Failed to send conversation message: %o", error));
} }
}); });
}); });
@ -1732,8 +1777,12 @@ test
private _html_tag: JQuery; private _html_tag: JQuery;
private _container_messages: JQuery; private _container_messages: JQuery;
private _container_new_message: JQuery; private _container_new_message: JQuery;
private _container_no_permissions: JQuery; private _container_no_permissions: JQuery;
private _container_no_permissions_shown: boolean = false
private _container_is_private: JQuery; private _container_is_private: JQuery;
private _container_is_private_shown: boolean = false;
private _view_max_messages = 40; /* reset to 40 again as soon we tab out :) */ private _view_max_messages = 40; /* reset to 40 again as soon we tab out :) */
private _view_older_messages: ViewEntry; private _view_older_messages: ViewEntry;
@ -1904,13 +1953,15 @@ test
return; return;
} else if(error.id == ErrorID.PERMISSION_ERROR) { } else if(error.id == ErrorID.PERMISSION_ERROR) {
this._container_no_permissions.show(); this._container_no_permissions.show();
this._container_no_permissions_shown = true;
} else if(error.id == ErrorID.CONVERSATION_IS_PRIVATE) { } else if(error.id == ErrorID.CONVERSATION_IS_PRIVATE) {
this.set_flag_private(true); this.set_flag_private(true);
} }
} }
//TODO log and handle! //TODO log and handle!
console.error(tr("Failed to fetch conversation history. %o"), error); log.error(LogCategory.CHAT, tr("Failed to fetch conversation history. %o"), error);
}).then(() => { }).then(() => {
this.fix_scroll(true);
this.handle.update_chat_box(); this.handle.update_chat_box();
}); });
} }
@ -1919,7 +1970,6 @@ test
this._view_older_messages.html_element.toggleClass('shown', false); this._view_older_messages.html_element.toggleClass('shown', false);
const entry = this._view_entries.slice().reverse().find(e => 'timestamp' in e) as any as {timestamp: number}; const entry = this._view_entries.slice().reverse().find(e => 'timestamp' in e) as any as {timestamp: number};
console.log("Last messages: %o", entry);
//conversationhistory cid=1 [cpw=xxx] [timestamp_begin] [timestamp_end (0 := no end)] [message_count (default 25| max 100)] [-merge] //conversationhistory cid=1 [cpw=xxx] [timestamp_begin] [timestamp_end (0 := no end)] [message_count (default 25| max 100)] [-merge]
this.handle.handle.handle.serverConnection.send_command("conversationhistory", { this.handle.handle.handle.serverConnection.send_command("conversationhistory", {
cid: this.channel_id, cid: this.channel_id,
@ -1935,7 +1985,9 @@ test
} }
} }
//TODO log and handle! //TODO log and handle!
console.error(tr("Failed to fetch conversation history. %o"), error); log.error(LogCategory.CHAT, tr("Failed to fetch conversation history. %o"), error);
}).then(() => {
this.fix_scroll(true);
}); });
} }
@ -2003,26 +2055,40 @@ test
/* update chat state */ /* update chat state */
this._container_no_permissions.hide(); this._container_no_permissions.hide();
this.handle.update_chat_box(); this._container_no_permissions_shown = false;
if(update_view) this.handle.update_chat_box();
} }
/* using a timeout here to not cause a force style recalculation */
private _scroll_fix_timer: number;
private _scroll_animate: boolean;
fix_scroll(animate: boolean) { fix_scroll(animate: boolean) {
let offset; if(this._scroll_fix_timer) {
if(this._first_unread_message) { this._scroll_animate = this._scroll_animate && animate;
offset = this._first_unread_message.html_element[0].offsetTop; return;
} else if(typeof(this._scroll_position) !== "undefined") {
offset = this._scroll_position;
} else {
offset = this._container_messages[0].scrollHeight;
} }
if(animate) { this._scroll_fix_timer = setTimeout(() => {
this._container_messages.stop(true).animate({ this._scroll_fix_timer = undefined;
scrollTop: offset
}, 'slow'); let offset;
} else { if(this._first_unread_message) {
this._container_messages.stop(true).scrollTop(offset); offset = this._first_unread_message.html_element[0].offsetTop;
} } else if(typeof(this._scroll_position) !== "undefined") {
offset = this._scroll_position;
} else {
offset = this._container_messages[0].scrollHeight;
}
if(this._scroll_animate) {
this._container_messages.stop(true).animate({
scrollTop: offset
}, 'slow');
} else {
this._container_messages.stop(true).scrollTop(offset);
}
}, 5);
} }
fix_view_size() { fix_view_size() {
@ -2046,16 +2112,17 @@ test
} }
chat_available() : boolean { chat_available() : boolean {
return !this._container_no_permissions.is(':visible') && !this._container_is_private.is(':visible'); return !this._container_no_permissions_shown && !this._container_is_private_shown;
} }
text_send_failed(error: CommandResult | any) { text_send_failed(error: CommandResult | any) {
console.warn("Failed to send text message! (%o)", error); log.warn(LogCategory.CHAT, "Failed to send text message! (%o)", error);
//TODO: Log if message send failed? //TODO: Log if message send failed?
if(error instanceof CommandResult) { if(error instanceof CommandResult) {
if(error.id == ErrorID.PERMISSION_ERROR) { if(error.id == ErrorID.PERMISSION_ERROR) {
//TODO: Split up between channel_text_message_send permission and no view permission //TODO: Split up between channel_text_message_send permission and no view permission
if(error.json["failed_permid"] == 0) { if(error.json["failed_permid"] == 0) {
this._container_no_permissions_shown = true;
this._container_no_permissions.show(); this._container_no_permissions.show();
this.handle.update_chat_box(); this.handle.update_chat_box();
} }
@ -2076,12 +2143,16 @@ test
update_private_state() { update_private_state() {
if(!this._flag_private) { if(!this._flag_private) {
this._container_is_private.hide(); this._container_is_private.hide();
this._container_is_private_shown = false;
} else { } else {
const client = this.handle.handle.handle.getClient(); const client = this.handle.handle.handle.getClient();
if(client && client.currentChannel() && client.currentChannel().channelId === this.channel_id) if(client && client.currentChannel() && client.currentChannel().channelId === this.channel_id) {
this._container_is_private_shown = false;
this._container_is_private.hide(); this._container_is_private.hide();
else } else {
this._container_is_private.show(); this._container_is_private.show();
this._container_is_private_shown = true;
}
} }
} }
@ -2099,12 +2170,12 @@ test
}).then(() => { }).then(() => {
return; /* in general it gets deleted via notify */ return; /* in general it gets deleted via notify */
}).catch(error => { }).catch(error => {
console.error(tr("Failed to delete conversation message for conversation %o: %o"), this.channel_id, error); log.error(LogCategory.CHAT, tr("Failed to delete conversation message for conversation %o: %o"), this.channel_id, error);
if(error instanceof CommandResult) if(error instanceof CommandResult)
error = error.extra_message || error.message; error = error.extra_message || error.message;
createErrorModal(tr("Failed to delete message"), MessageHelper.formatMessage(tr("Failed to delete conversation message{:br:}Error: {}"), error)).open(); createErrorModal(tr("Failed to delete message"), MessageHelper.formatMessage(tr("Failed to delete conversation message{:br:}Error: {}"), error)).open();
}); });
console.log(tr("Deleting message: %o"), message); log.debug(LogCategory.CLIENT, tr("Deleting text message %o"), message);
} }
delete_messages(begin: number, end: number, sender: number, limit: number) { delete_messages(begin: number, end: number, sender: number, limit: number) {

View File

@ -41,7 +41,7 @@ class Hostbanner {
const tag = this.generate_tag(); const tag = this.generate_tag();
tag.then(element => { tag.then(element => {
console.log("Regenrated result: %o", element); log.debug(LogCategory.CLIENT, tr("Regenerated hostbanner tag. Replacing it: %o"), element);
if(!element) { if(!element) {
this.html_tag.empty().addClass("disabled"); this.html_tag.empty().addClass("disabled");
return; return;
@ -61,7 +61,7 @@ class Hostbanner {
}, 250); }, 250);
} }
}).catch(error => { }).catch(error => {
console.warn(tr("Failed to load hostbanner: %o"), error); log.warn(LogCategory.CLIENT, tr("Failed to load the hostbanner: %o"), error);
this.html_tag.empty().addClass("disabled"); this.html_tag.empty().addClass("disabled");
}); });
const server = this.client.channelTree.server; const server = this.client.channelTree.server;
@ -94,7 +94,7 @@ class Hostbanner {
image_element.src = banner_url; image_element.src = banner_url;
image_element.style.display = 'none'; image_element.style.display = 'none';
document.body.append(image_element); document.body.append(image_element);
console.log("Loading hostbanner image!"); log.debug(LogCategory.CLIENT, tr("Successfully loaded hostbanner image."));
}); });
image_element.parentNode.removeChild(image_element); image_element.parentNode.removeChild(image_element);

View File

@ -9,6 +9,8 @@ namespace log {
CONNECTION_CONNECTED = "connection_connected", CONNECTION_CONNECTED = "connection_connected",
CONNECTION_FAILED = "connection_failed", CONNECTION_FAILED = "connection_failed",
DISCONNECTED = "disconnected",
CONNECTION_VOICE_SETUP_FAILED = "connection_voice_setup_failed", CONNECTION_VOICE_SETUP_FAILED = "connection_voice_setup_failed",
CONNECTION_COMMAND_ERROR = "connection_command_error", CONNECTION_COMMAND_ERROR = "connection_command_error",
@ -249,6 +251,8 @@ namespace log {
"channel_create": event.ChannelCreate; "channel_create": event.ChannelCreate;
"channel_delete": event.ChannelDelete; "channel_delete": event.ChannelDelete;
"disconnected": any;
} }
export type MessageBuilderOptions = {}; export type MessageBuilderOptions = {};
@ -315,6 +319,8 @@ namespace log {
this._log = undefined; this._log = undefined;
} }
private _scroll_task: number;
private append_log(message: server.LogMessage) { private append_log(message: server.LogMessage) {
let container = $.spawn("div").addClass("log-message"); let container = $.spawn("div").addClass("log-message");
@ -351,8 +357,15 @@ namespace log {
const hide_elements = messages.filter(idx => idx < index); const hide_elements = messages.filter(idx => idx < index);
hide_elements.hide(250, () => hide_elements.remove()); hide_elements.hide(250, () => hide_elements.remove());
if(this.auto_follow) if(this.auto_follow) {
this._html_tag.scrollTop(this._html_tag[0].scrollHeight); clearTimeout(this._scroll_task);
/* do not enforce a recalculate style here */
this._scroll_task = setTimeout(() => {
this._html_tag.scrollTop(this._html_tag[0].scrollHeight);
this._scroll_task = 0;
}, 5) as any;
}
} }
} }
} }
@ -514,7 +527,9 @@ namespace log {
MessageBuilders["global_message"] = (data: event.GlobalMessage, options) => { MessageBuilders["global_message"] = (data: event.GlobalMessage, options) => {
return []; /* we do not show global messages within log */ return []; /* we do not show global messages within log */
} };
MessageBuilders["disconnected"] = () => MessageHelper.formatMessage(tr("Disconnected from server"));
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,9 @@ namespace Modals {
tooltip(template); tooltip(template);
return template.children(); return template.children();
}, },
footer: null footer: null,
width: '60em'
}); });
const updater = setInterval(() => { const updater = setInterval(() => {

View File

@ -32,7 +32,7 @@ namespace Modals {
}).then(() => { }).then(() => {
button_update.prop("disabled", false); button_update.prop("disabled", false);
}); });
}); }).trigger('click');
update_values(); update_values();
tooltip(template); tooltip(template);
@ -49,7 +49,15 @@ namespace Modals {
modal.htmlTag.find(".button-close").on('click', event => modal.close()); modal.htmlTag.find(".button-close").on('click', event => modal.close());
modal.htmlTag.find(".button-show-bandwidth").on('click', event => { modal.htmlTag.find(".button-show-bandwidth").on('click', event => {
//TODO! const intervals = [];
const updater = (info) => {
intervals.forEach(e => e(info));
};
update_callbacks.push(updater);
Modals.openServerInfoBandwidth(server, intervals).close_listener.push(() => {
update_callbacks.remove(updater);
});
}); });
modal.htmlTag.find(".modal-body").addClass("modal-server-info"); modal.htmlTag.find(".modal-body").addClass("modal-server-info");

View File

@ -0,0 +1,157 @@
namespace Modals {
type InfoUpdateCallback = (info: ServerConnectionInfo | boolean) => any;
export function openServerInfoBandwidth(server: ServerEntry, update_callbacks?: InfoUpdateCallback[]) : Modal {
let modal: Modal;
let own_callbacks = !update_callbacks;
update_callbacks = update_callbacks || [];
modal = createModal({
header: tr("Server bandwidth data"),
body: () => {
const template = $("#tmpl_server_info_bandwidth").renderTag();
const children = template.children();
initialize_current_bandwidth(modal, children.find(".statistic-bandwidth"), update_callbacks);
initialize_ft_bandwidth(modal, children.find(".statistic-ft-bandwidth"), update_callbacks);
initialize_general(template.find(".top"), update_callbacks);
tooltip(template);
return template.children();
},
footer: null,
min_width: "25em"
});
if(own_callbacks) {
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.close_listener.push(() => clearInterval(updater));
}
modal.htmlTag.find(".button-close").on('click', event => modal.close());
modal.htmlTag.find(".modal-body").addClass("modal-server-info-bandwidth");
modal.open();
return modal;
}
function initialize_graph(modal: Modal, tag: JQuery, callbacks: InfoUpdateCallback[], fields: {uplaod: string, download: string}) {
const canvas = tag.find("canvas")[0] as HTMLCanvasElement;
const label_upload = tag.find(".upload");
const label_download = tag.find(".download");
let last_info: ServerConnectionInfo | false = false;
let custom_info = false;
const show_info = (upload: number | undefined, download: number | undefined) => {
if(typeof upload !== "number")
upload = last_info ? last_info[fields.uplaod] : undefined;
if(typeof download !== "number")
download = last_info ? last_info[fields.download] : undefined;
if(typeof upload !== "number")
label_upload.text(tr("receiving..."));
else
label_upload.text(MessageHelper.network.format_bytes(upload, {unit: "Bytes", time: "s", exact: false}));
if(typeof download !== "number")
label_download.text(tr("receiving..."));
else
label_download.text(MessageHelper.network.format_bytes(download, {unit: "Bytes", time: "s", exact: false}));
};
show_info(undefined, undefined);
const graph = new net.graph.Graph(canvas);
graph.insert_entry({ timestamp: Date.now(), upload: 0, download: 0});
callbacks.push((values: ServerConnectionInfo | false) => {
last_info = values;
if(!values) {
graph.insert_entry({ timestamp: Date.now(), upload: 0, download: 0});
} else {
graph.insert_entry({
timestamp: Date.now(),
download: values[fields.download], //values.connection_bandwidth_received_last_second_total,
upload: values[fields.uplaod], //values.connection_bandwidth_sent_last_second_total
});
}
/* set set that we want to show the entry within one second */
graph._time_span.origin = Object.assign(graph.calculate_time_span(), { time: Date.now() });
graph._time_span.target = {
begin: Date.now() - 120 * 1000,
end: Date.now(),
time: Date.now() + 200
};
graph.cleanup();
if(!custom_info) {
show_info(undefined, undefined);
graph.resize(); /* just to ensure (we have to rethink this maybe; cause it causes a recalculates the style */
}
});
graph.max_gap_size(0);
graph.initialize();
graph.callback_detailed_hide = () => {
custom_info = false;
show_info(undefined, undefined);
};
graph.callback_detailed_info = (upload, download, timestamp, event) => {
custom_info = true;
show_info(upload, download);
};
modal.close_listener.push(() => graph.terminate());
modal.open_listener.push(() => graph.resize());
tag.addClass("window-resize-listener").on('resize', event => graph.resize());
}
function initialize_current_bandwidth(modal: Modal, tag: JQuery, callbacks: InfoUpdateCallback[]) {
initialize_graph(modal, tag, callbacks, {
uplaod: "connection_bandwidth_sent_last_second_total",
download: "connection_bandwidth_received_last_second_total"
});
}
function initialize_ft_bandwidth(modal: Modal, tag: JQuery, callbacks: InfoUpdateCallback[]) {
initialize_graph(modal, tag, callbacks, {
uplaod: "connection_filetransfer_bandwidth_sent",
download: "connection_filetransfer_bandwidth_received"
});
}
function initialize_general(tag: JQuery, callbacks: InfoUpdateCallback[]) {
const tag_packets_upload = tag.find(".statistic-packets .upload");
const tag_packets_download = tag.find(".statistic-packets .download");
const tag_bytes_upload = tag.find(".statistic-bytes .upload");
const tag_bytes_download = tag.find(".statistic-bytes .download");
const tag_ft_bytes_upload = tag.find(".statistic-ft-bytes .upload");
const tag_ft_bytes_download = tag.find(".statistic-ft-bytes .download");
const update = (tag, value) => {
if(typeof value === "undefined")
tag.text(tr("receiving..."));
else
tag.text(MessageHelper.network.format_bytes(value, {unit: "Bytes", exact: false}));
};
callbacks.push((info: ServerConnectionInfo) => {
info = info ? info : {} as ServerConnectionInfo;
update(tag_packets_download, info.connection_packets_received_total);
update(tag_packets_upload, info.connection_packets_sent_total);
update(tag_bytes_download, info.connection_bytes_received_total);
update(tag_bytes_upload, info.connection_bytes_sent_total);
update(tag_ft_bytes_upload, info.connection_filetransfer_bytes_received_total);
update(tag_ft_bytes_download, info.connection_filetransfer_bytes_sent_total);
});
}
}

View File

@ -174,8 +174,9 @@ class ServerEntry {
} }
initializeListener(){ initializeListener(){
this._htmlTag.click(() => { this._htmlTag.on('click' ,() => {
this.channelTree.onSelect(this); this.channelTree.onSelect(this);
this.updateProperties(); /* just prepare to show some server info */
}); });
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) { if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {

View File

@ -106,7 +106,10 @@ class ChannelTree {
this._tree_detached = false; this._tree_detached = false;
this._tag_entries.appendTo(this._tag_container); this._tag_entries.appendTo(this._tag_container);
this.channels.forEach(e => e.recalculate_repetitive_name()); this.channels.forEach(e => {
e.recalculate_repetitive_name();
e.reorderClients();
});
} }
showContextMenu(x: number, y: number, on_close: () => void = undefined) { showContextMenu(x: number, y: number, on_close: () => void = undefined) {
@ -326,6 +329,7 @@ class ChannelTree {
} }
deleteClient(client: ClientEntry, animate_tag?: boolean) { deleteClient(client: ClientEntry, animate_tag?: boolean) {
const old_channel = client.currentChannel();
this.clients.remove(client); this.clients.remove(client);
if(typeof(animate_tag) !== "boolean" || animate_tag) if(typeof(animate_tag) !== "boolean" || animate_tag)
this.__deleteAnimation(client); this.__deleteAnimation(client);
@ -333,6 +337,10 @@ class ChannelTree {
client.tag.detach(); client.tag.detach();
client.onDelete(); client.onDelete();
if(old_channel) {
this.client.side_bar.info_frame().update_channel_client_count(old_channel);
}
const voice_connection = this.client.serverConnection.voice_connection(); const voice_connection = this.client.serverConnection.voice_connection();
if(client.get_audio_handle()) { if(client.get_audio_handle()) {
if(!voice_connection) { if(!voice_connection) {
@ -359,6 +367,9 @@ class ChannelTree {
client.tree_unregistered(); client.tree_unregistered();
} }
private _update_timer: number;
private _reorder_channels = new Set<ChannelEntry>();
insertClient(client: ClientEntry, channel: ChannelEntry) : ClientEntry { insertClient(client: ClientEntry, channel: ChannelEntry) : ClientEntry {
let newClient = this.findClient(client.clientId()); let newClient = this.findClient(client.clientId());
if(newClient) if(newClient)
@ -376,10 +387,22 @@ class ChannelTree {
tag.css("display", "none").fadeIn("slow"); tag.css("display", "none").fadeIn("slow");
tag.appendTo(channel.clientTag()); tag.appendTo(channel.clientTag());
client.currentChannel().reorderClients(); channel.reorderClients();
channel.updateChannelTypeIcon(); /* schedule a reorder for this channel. */
client.update_family_index(); this._reorder_channels.add(client.currentChannel());
if(!this._update_timer) {
this._update_timer = setTimeout(() => {
this._update_timer = undefined;
for(const channel of this._reorder_channels) {
channel.updateChannelTypeIcon();
this.client.side_bar.info_frame().update_channel_client_count(channel);
}
this._reorder_channels.clear();
}, 5) as any;
}
client.update_family_index(); /* why the hell is this here?! */
return client; return client;
} }
@ -392,10 +415,12 @@ class ChannelTree {
tag.appendTo(client.currentChannel().clientTag()); tag.appendTo(client.currentChannel().clientTag());
if(oldChannel) { if(oldChannel) {
oldChannel.updateChannelTypeIcon(); oldChannel.updateChannelTypeIcon();
this.client.side_bar.info_frame().update_channel_client_count(oldChannel);
} }
if(client.currentChannel()) { if(channel) {
client.currentChannel().reorderClients(); channel.reorderClients();
client.currentChannel().updateChannelTypeIcon(); channel.updateChannelTypeIcon();
this.client.side_bar.info_frame().update_channel_client_count(channel);
} }
client.updateClientStatusIcons(); client.updateClientStatusIcons();
client.update_family_index(); client.update_family_index();

View File

@ -191,6 +191,7 @@ const loader_javascript = {
"js/ui/elements/tab.js", "js/ui/elements/tab.js",
"js/ui/elements/slider.js", "js/ui/elements/slider.js",
"js/ui/elements/tooltip.js", "js/ui/elements/tooltip.js",
"js/ui/elements/net_graph.js",
//Load UI //Load UI
"js/ui/modal/ModalAbout.js", "js/ui/modal/ModalAbout.js",
@ -207,6 +208,7 @@ const loader_javascript = {
"js/ui/modal/ModalCreateChannel.js", "js/ui/modal/ModalCreateChannel.js",
"js/ui/modal/ModalServerEdit.js", "js/ui/modal/ModalServerEdit.js",
"js/ui/modal/ModalServerInfo.js", "js/ui/modal/ModalServerInfo.js",
"js/ui/modal/ModalServerInfoBandwidth.js",
"js/ui/modal/ModalChangeVolume.js", "js/ui/modal/ModalChangeVolume.js",
"js/ui/modal/ModalBanClient.js", "js/ui/modal/ModalBanClient.js",
"js/ui/modal/ModalIconSelect.js", "js/ui/modal/ModalIconSelect.js",
@ -372,6 +374,7 @@ const loader_style = {
"css/static/modal-bancreate.css", "css/static/modal-bancreate.css",
"css/static/modal-clientinfo.css", "css/static/modal-clientinfo.css",
"css/static/modal-serverinfo.css", "css/static/modal-serverinfo.css",
"css/static/modal-serverinfobandwidth.css",
"css/static/modal-identity.css", "css/static/modal-identity.css",
"css/static/modal-settings.css", "css/static/modal-settings.css",
"css/static/modal-poke.css", "css/static/modal-poke.css",
@ -465,7 +468,7 @@ loader.register_task(loader.Stage.INITIALIZING, {
return {name:M[0], version:M[1]}; return {name:M[0], version:M[1]};
})(); })();
console.log("Resolved browser specs: %o", navigator.browserSpecs); //Object { name: "Firefox", version: "42" } console.log("Resolved browser manufacturer to \"%s\" version \"%s\"", navigator.browserSpecs.name, navigator.browserSpecs.version);
}, },
priority: 30 priority: 30
}); });

View File

@ -1,23 +1,26 @@
- Modals - Modals
- Settings (X) - Settings (X)
- Banlist
- Add/Edit
- Query - Query
- List - List
- Create - Username
- Manage favorites - Unique ID
- Create favorite - Bounded server
Buttons:
- Create
- Delete
- Rename
- Change password
- Refresh
- "Ban Client" dialog
- Entity info (Popup) - Entity info (Popup)
- Server - Server (Bandwidth (MH))
- Client
- Channel - Channel
- Icon Select - Icon Select
- Icon uploiad - Icon upload
- Avatar list - Avatar list
- Server group assignments checkbox - Server group assignments checkbox
- Invite buddy - Invite buddy
- Identity improve
- Identity import
- Ban Liste - Ban Liste
@ -108,6 +111,8 @@
- Description - Description
- Password protected - Password protected
- "Texting mode" | Private | "No saving" | "Logged (+ history length)" - "Texting mode" | Private | "No saving" | "Logged (+ history length)"
- Channel creator
- Channel crate timestamp
@ -127,11 +132,7 @@
- File Transfer bytes transferred, month & global (Up + Download) - File Transfer bytes transferred, month & global (Up + Download)
TODO: TODO:
Server Info (Bandwidth usage)
Fix these icons: https://img.did.science/Screenshot_20-11-06.png Fix these icons: https://img.did.science/Screenshot_20-11-06.png
Make identity settings a bit smaller (Even scroll on my Laptop)
- Application Options - Application Options
- Crash - Crash
- Focus crash window on crash - Focus crash window on crash

View File

@ -752,7 +752,7 @@ if(typeof jQuery !== 'undefined'){
const _tab = (name: string, hidden: boolean) => { const _tab = (name: string, hidden: boolean) => {
let tab_html = '<div ' + let tab_html = '<div ' +
'class="lsx-emojipicker-emoji lsx-emoji-tab lsx-emoji-' + name + (hidden ? " hidden" : "") + '"' + 'class="lsx-emojipicker-emoji lsx-emoji-tab lsx-emoji-' + name + (hidden ? " hidden" : "") + '"' +
'style="width: ' + settings.width + 'px; height: ' + settings.height + 'px;"' + ' style="width: ' + settings.width + 'px; height: ' + settings.height + 'px;"' +
'>'; '>';
if(settings.twemoji) { if(settings.twemoji) {

View File

@ -137,6 +137,8 @@ namespace audio {
private _audio_source: RecorderProfile; private _audio_source: RecorderProfile;
private _audio_clients: audio.js.VoiceClientController[] = []; private _audio_clients: audio.js.VoiceClientController[] = [];
private _encoder_codec: number = 5;
constructor(connection: connection.ServerConnection) { constructor(connection: connection.ServerConnection) {
super(connection); super(connection);
this.connection = connection; this.connection = connection;
@ -162,7 +164,7 @@ namespace audio {
}); });
} }
native_encoding_supported() : boolean { static native_encoding_supported() : boolean {
const context = window.webkitAudioContext || window.AudioContext; const context = window.webkitAudioContext || window.AudioContext;
if(!context) if(!context)
return false; return false;
@ -173,7 +175,7 @@ namespace audio {
return true; return true;
} }
javascript_encoding_supported() : boolean { static javascript_encoding_supported() : boolean {
if(!window.RTCPeerConnection) if(!window.RTCPeerConnection)
return false; return false;
if(!RTCPeerConnection.prototype.createDataChannel) if(!RTCPeerConnection.prototype.createDataChannel)
@ -184,16 +186,16 @@ namespace audio {
current_encoding_supported() : boolean { current_encoding_supported() : boolean {
switch (this._type) { switch (this._type) {
case VoiceEncodeType.JS_ENCODE: case VoiceEncodeType.JS_ENCODE:
return this.javascript_encoding_supported(); return audio.js.VoiceConnection.javascript_encoding_supported();
case VoiceEncodeType.NATIVE_ENCODE: case VoiceEncodeType.NATIVE_ENCODE:
return this.native_encoding_supported(); return audio.js.VoiceConnection.native_encoding_supported();
} }
return false; return false;
} }
private setup_native() { private setup_native() {
log.info(LogCategory.VOICE, tr("Setting up native voice stream!")); log.info(LogCategory.VOICE, tr("Setting up native voice stream!"));
if(!this.native_encoding_supported()) { if(!audio.js.VoiceConnection.native_encoding_supported()) {
log.warn(LogCategory.VOICE, tr("Native codec isn't supported!")); log.warn(LogCategory.VOICE, tr("Native codec isn't supported!"));
return; return;
} }
@ -203,7 +205,7 @@ namespace audio {
} }
private setup_js() { private setup_js() {
if(!this.javascript_encoding_supported()) return; if(!audio.js.VoiceConnection.javascript_encoding_supported()) return;
} }
async acquire_voice_recorder(recorder: RecorderProfile | undefined, enforce?: boolean) { async acquire_voice_recorder(recorder: RecorderProfile | undefined, enforce?: boolean) {
@ -216,15 +218,15 @@ namespace audio {
if(this._audio_source) if(this._audio_source)
await this._audio_source.unmount(); await this._audio_source.unmount();
this.handleVoiceEnded(); this.handle_local_voice_ended();
this._audio_source = recorder; this._audio_source = recorder;
if(recorder) { if(recorder) {
recorder.current_handler = this.connection.client; recorder.current_handler = this.connection.client;
recorder.callback_unmount = this.on_recoder_yield.bind(this); recorder.callback_unmount = this.on_recorder_yield.bind(this);
recorder.callback_start = this.handleVoiceStarted.bind(this); recorder.callback_start = this.handle_local_voice_started.bind(this);
recorder.callback_stop = this.handleVoiceEnded.bind(this); recorder.callback_stop = this.handle_local_voice_ended.bind(this);
if(this._type == VoiceEncodeType.NATIVE_ENCODE) { if(this._type == VoiceEncodeType.NATIVE_ENCODE) {
if(!this.local_audio_stream) if(!this.local_audio_stream)
@ -248,7 +250,7 @@ namespace audio {
} else { } else {
await recorder.input.set_consumer({ await recorder.input.set_consumer({
type: audio.recorder.InputConsumerType.CALLBACK, type: audio.recorder.InputConsumerType.CALLBACK,
callback_audio: buffer => this.handleVoiceData(buffer, false) callback_audio: buffer => this.handle_local_voice(buffer, false)
} as audio.recorder.CallbackInputConsumer); } as audio.recorder.CallbackInputConsumer);
} }
} }
@ -273,7 +275,7 @@ namespace audio {
voice_send_support() : boolean { voice_send_support() : boolean {
if(this._type == VoiceEncodeType.NATIVE_ENCODE) if(this._type == VoiceEncodeType.NATIVE_ENCODE)
return this.native_encoding_supported() && this.rtcPeerConnection.getLocalStreams().length > 0; return audio.js.VoiceConnection.native_encoding_supported() && this.rtcPeerConnection.getLocalStreams().length > 0;
else else
return this.voice_playback_support(); return this.voice_playback_support();
} }
@ -503,12 +505,7 @@ namespace audio {
} }
} }
private current_channel_codec() : number { private handle_local_voice(data: AudioBuffer, head: boolean) {
const chandler = this.connection.client;
return (chandler.getClient().currentChannel() || {properties: { channel_codec: 4}}).properties.channel_codec;
}
private handleVoiceData(data: AudioBuffer, head: boolean) {
const chandler = this.connection.client; const chandler = this.connection.client;
if(!chandler.connected) if(!chandler.connected)
return false; return false;
@ -524,13 +521,13 @@ namespace audio {
return; return;
} }
const codec = this.current_channel_codec(); const codec = this._encoder_codec;
VoiceConnection.codec_pool[codec] VoiceConnection.codec_pool[codec]
.ownCodec(chandler.getClientId(), e => this.handleEncodedVoicePacket(e, codec), true) .ownCodec(chandler.getClientId(), e => this.handleEncodedVoicePacket(e, codec), true)
.then(encoder => encoder.encodeSamples(client.get_codec_cache(codec), data)); .then(encoder => encoder.encodeSamples(client.get_codec_cache(codec), data));
} }
private handleVoiceEnded() { private handle_local_voice_ended() {
const chandler = this.connection.client; const chandler = this.connection.client;
const ch = chandler.getClient(); const ch = chandler.getClient();
if(ch) ch.speaking = false; if(ch) ch.speaking = false;
@ -541,11 +538,11 @@ namespace audio {
return false; return false;
log.info(LogCategory.VOICE, tr("Local voice ended")); log.info(LogCategory.VOICE, tr("Local voice ended"));
if(this.dataChannel) if(this.dataChannel && this._encoder_codec >= 0)
this.send_voice_packet(new Uint8Array(0), this.current_channel_codec()); this.send_voice_packet(new Uint8Array(0), this._encoder_codec);
} }
private handleVoiceStarted() { private handle_local_voice_started() {
const chandler = this.connection.client; const chandler = this.connection.client;
log.info(LogCategory.VOICE, tr("Local voice started")); log.info(LogCategory.VOICE, tr("Local voice started"));
@ -553,7 +550,7 @@ namespace audio {
if(ch) ch.speaking = true; if(ch) ch.speaking = true;
} }
private on_recoder_yield() { private on_recorder_yield() {
log.info(LogCategory.VOICE, "Lost recorder!"); log.info(LogCategory.VOICE, "Lost recorder!");
this._audio_source = undefined; this._audio_source = undefined;
this.acquire_voice_recorder(undefined, true); /* we can ignore the promise because we should finish this directly */ this.acquire_voice_recorder(undefined, true); /* we can ignore the promise because we should finish this directly */
@ -599,6 +596,14 @@ namespace audio {
encoding_supported(codec: number): boolean { encoding_supported(codec: number): boolean {
return VoiceConnection.codecSupported(codec); return VoiceConnection.codecSupported(codec);
} }
get_encoder_codec(): number {
return this._encoder_codec;
}
set_encoder_codec(codec: number) {
this._encoder_codec = codec;
}
} }
} }
} }