A lot of changes
parent
824ae8c4ff
commit
6d2ecc3c69
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
@include chat-scrollbar-vertical();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* the channel tree */
|
/* the channel tree */
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
}
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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"> </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>
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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[]) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
@ -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(() => {
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
29
todo.txt
29
todo.txt
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue