Beginning with the new dark design and the new chat functionality

canary
WolverinDEV 2019-07-10 00:52:08 +02:00
parent 9d00228a2a
commit 724317e6c3
31 changed files with 2579 additions and 501 deletions

View File

@ -1,4 +1,8 @@
# Changelog:
* **XX.XX.XX**
- Removed icon size restriction for SVGs
- Fixed permission editor icon select for not granted icon permissions
* **22.06.19**
- Fixed channel create not working issue
- Added BB-Code support for pokes

View File

@ -34,6 +34,22 @@
flex-grow: 0;
flex-shrink: 0;
&.server, > .container-channel, &.client {
padding-left: 5px;
padding-right: 5px;
&:hover {
background-color: $channel_tree_entry_hovered;
}
&.selected {
background-color: $channel_tree_entry_selected;
.channel-name {
color: whitesmoke;
}
}
}
&.server {
display: flex;
flex-direction: row;
@ -54,20 +70,13 @@
flex-shrink: 1;
align-self: center;
color: $channel_tree_entry_text_color;
}
.icon_property {
flex-grow: 0;
flex-shrink: 0;
}
&.selected {
background-color: $channel_tree_entry_selected;
.name {
color: whitesmoke;
}
}
}
&.channel {
@ -115,6 +124,7 @@
.channel-name {
align-self: center;
color: $channel_tree_entry_text_color;
}
}
@ -130,13 +140,6 @@
border-bottom: 1px solid black;
}
&.selected {
background-color: $channel_tree_entry_selected;
.channel-name {
color: whitesmoke;
}
}
.show-channel-normal-only {
display: none;
@ -181,13 +184,14 @@
flex-shrink: 1;
min-width: 75px;
color: $channel_tree_entry_text_color;
}
.container-icons {
margin-right: 0; /* override from previous thing */
position: absolute;
right: 0;
right: 5px;
display: flex;
flex-direction: row;
@ -207,11 +211,7 @@
}
&.selected {
background-color: $channel_tree_entry_selected;
.client-name {
color: whitesmoke;
&:focus {
color: black;

View File

@ -1,11 +1,18 @@
.container-connection-handlers {
height: 35px;
background-color: lightgray;
$animation_length: .25s;
border: 4px solid lightgray;
border-top: 1px dotted gray;
border-bottom-width: 0;
margin-top: 0;
height: 0;
transition: all $animation_length ease-in-out;
&.shown {
margin-top: -4px;
height: 24px;
transition: all $animation_length ease-in-out;
}
background-color: transparent;
-webkit-user-select: none;
-moz-user-select: none;
@ -22,20 +29,22 @@
flex-direction: row;
justify-content: left;
overflow-x: auto;
overflow-y: visible;
.connection-container {
padding-top: 4px;
flex-grow: 0;
flex-shrink: 0;
cursor: pointer;
display: inline-flex;
margin-top: 5px;
padding-left: 5px;
padding-right: 5px;
border: 1px #2222223b solid;
border-radius: 2px 2px 0 0;
height: 24px;
.server-icon {
align-self: center;
@ -43,6 +52,8 @@
}
.server-name {
color: #a8a8a8;
align-self: center;
margin-right: 5px;
}
@ -51,21 +62,27 @@
align-self: center;
&:hover {
background-color: #e7e7e7;
background-color: #212121;
}
}
&:hover {
background-color: #242425;
}
&.active {
background-color: #FFFFFF33;
background-color: #2d2f32;
border-bottom: 1px solid #0d9cfd;
//-webkit-box-shadow: inset 4px -17px 50px -30px #0d9cfd99;
//-moz-box-shadow: inset 4px -17px 50px -30px #0d9cfd99;
//box-shadow: inset 4px -17px 50px -30px #0d9cfd99;
}
}
&::-webkit-scrollbar {
display: none;
}
overflow-x: auto;
overflow-y: visible;
}
.container-scroll {

View File

@ -1,6 +1,4 @@
$border_color_activated: rgba(255, 255, 255, .75);
$background_activated: rgba(0,0,0,0.25);
$background:lightgray;
.control_bar {
display: flex;
@ -11,83 +9,114 @@ $background:lightgray;
-ms-user-select: none;
user-select: none;
height: 100%;
align-items: center;
/* tmp fix for ultra small devices */
overflow-y: visible;
.divider {
border-left:2px solid gray;
height: auto;
margin-left: 5px;
margin-right: 5px
border-left:2px solid #393838;
height: calc(100% - 3px);
margin: 3px;
}
.button {
cursor: pointer;
background-color: lightgray;
border-radius: 5px;
align-items: center;
border: 2px solid rgba(0, 0, 0, 0);
height: 36px;
width: 36px;
margin-right: 5px;
margin-left: 5px;
/* border etc */
.button, .dropdown-arrow {
text-align: center;
border: 1px solid rgba(0, 0, 0, 0);
border-radius: 3px;
background-color: #454545;
&:hover {
background-color: rgba(0,0,0,0.4);
border-color: rgba(255, 255, 255, .75);
background-color: #393c43;
border-color: #4a4c55;
/*box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);*/
}
&.activated {
background-color: rgba(0,0,0,0.25);
border-color: rgba(255, 255, 255, .75);
background-color: #2f3841;
border-color: #005fa1;
&:hover {
background-color: rgba(0,0,0,0.4);
border-color: rgba(255, 255, 255, .75);
background-color: #263340;
border-color: #005fa1;
}
&.button-red {
background-color: #412f2f;
border-color: #a10000;
&:hover {
background-color: #402626;
border-color: #a10000;
}
}
}
> .icon_x24 {
vertical-align: middle;
}
}
.button {
cursor: pointer;
align-items: center;
margin-right: 5px;
margin-left: 5px;
&:not(.icon_x24) {
min-width: 28px;
max-width: 28px;
height: 28px;
}
}
.button-dropdown {
height: 100%;
position: relative;
.buttons {
margin-top: 1px;
height: 28px;
align-items: center;
display: flex;
flex-direction: row;
.button {
margin-right: 0;
}
.dropdown-arrow {
height: 28px;
.button-dropdown {
display: inline-flex;
justify-content: space-around;
width: 18px;
cursor: pointer;
border-radius: 0 5px 5px 0;
border-radius: 0 3px 3px 0;
align-items: center;
border: 2px solid rgba(0, 0, 0, 0);
border-left: 0;
}
&:hover {
.button {
border-right: 1px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
.button {
margin-right: 0;
}
background-color: rgba(0,0,0,0.4);
border-color: rgba(255, 255, 255, .75);
/*box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);*/
&:hover {
.button, .dropdown-arrow {
background-color: #393c43;
border-color: #4a4c55;
}
.button-dropdown {
background-color: rgba(0,0,0,0.4);
border-color: rgba(255, 255, 255, .75);
/*box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);*/
border-left: 2px solid rgba(255, 255, 255, .75);
.button {
padding-right: 1px;
border-right: 0;
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
}
}
@ -98,10 +127,13 @@ $background:lightgray;
position: absolute;
margin-left: 5px;
background-color: $background;
border-radius: 5px;
color: #c4c5c5;
background-color: #2d3032;
align-items: center;
border: 2px solid $border_color_activated;
border: 1px solid #2c2525;
border-radius: 0 5px 5px 5px;
width: 230px;
z-index: 1000;
@ -122,7 +154,7 @@ $background:lightgray;
padding: 1px 2px 1px 4px;
&:hover {
background-color: $background_activated;
background-color: #252729;
}
}
@ -135,7 +167,8 @@ $background:lightgray;
}
&.display_left {
margin-left: -165px;
margin-left: -179px;
border-radius: 5px 0 5px 5px;
}
}
@ -143,6 +176,22 @@ $background:lightgray;
.dropdown {
display: block;
}
.button, .dropdown-arrow {
background-color: #393c43;
border-color: #4a4c55;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.button {
padding-right: 1px;
border-right: 0;
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
}

View File

@ -0,0 +1,410 @@
.container-chat-frame {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: column;
justify-content: stretch;
.container-info {
user-select: none;
flex-grow: 0;
flex-shrink: 0;
height: 9em;
display: flex;
flex-direction: column;
justify-content: space-evenly;
background-color: #2e2e2e;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
-moz-box-shadow: inset 0 0 5px #00000040;
-webkit-box-shadow: inset 0 0 5px #00000040;
box-shadow: inset 0 0 5px #00000040;
.lane {
padding-right: 10px;
padding-left: 10px;
display: flex;
flex-direction: row;
justify-content: stretch;
.block {
flex-shrink: 1;
flex-grow: 1;
min-width: 0;
&.right {
text-align: right;
}
&.left {
text-align: left;
padding-right: 10px;
}
.title, .value, .small-value {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
min-width: 0;
max-width: 100%;
}
.title {
display: block;
color: #8b8b8b;
.container-indicator {
display: inline-flex;
flex-direction: column;
justify-content: space-around;
background: #ca3e22;
border: 1px solid #6a0e0e;
border-radius: 4px;
text-align: center;
vertical-align: text-top;
color: #dab8b4;
font-size: .66em;
height: .9em;
min-width: .9em;
padding-right: 2px;
padding-left: 2px;
}
}
.value {
color: #5a5a5a;
background-color: #373737;
display: inline-block;
border-radius: 3px;
padding-right: 5px;
padding-left: 5px;
> div {
display: inline-block;
}
.icon-container {
margin-right: 5px;
vertical-align: middle;
}
}
.small-value {
display: inline-block;
color: #5a5a5a;
font-size: .66em;
vertical-align: top;
margin-top: -.2em;
}
}
}
}
.container-chat {
flex-grow: 1;
flex-shrink: 1;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
min-width: 350px;
min-height: 200px;
.container-private-conversations {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: row;
justify-content: stretch;
.conversation-list {
margin-right: -2px; /* the fix for the seperator with of 3px */
user-select: none;
overflow-x: hidden;
overflow-y: auto;
width: 25%;
min-width: 100px;
position: relative;
.no-chats {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
> div {
display: inline-block;
color: #5a5a5a;
}
}
//Avatar within cat 40x40
.conversation-entry {
display: flex;
flex-direction: row;
justify-content: stretch;
cursor: pointer;
border-bottom: 1px solid #313132;
.container-avatar {
flex-grow: 0;
flex-shrink: 0;
position: relative;
display: inline-block;
margin: 5px 10px 5px 5px;
.avatar {
overflow: hidden;
width: 30px;
height: 30px;
border-radius: 50%;
}
.chat-unread {
display: none;
position: absolute;
top: 0;
right: 0;
background-color: #a81414;
width: 7px;
height: 7px;
border-radius: 50%;
-webkit-box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.20);
-moz-box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.20);
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.20);
}
}
&.unread {
.chat-unread {
display: block;
}
}
.info {
flex-grow: 1;
flex-shrink: 1;
min-width: 50px;
display: flex;
flex-direction: column;
justify-content: center;
> * {
flex-grow: 0;
flex-shrink: 0;
display: inline-block;
width: 100%;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.client-name {
color: #bebebe;
font-weight: bold;
margin-bottom: -.4em;
}
.last-message {
color: #555353;
display: inline-block;
font-size: .66em;
}
}
&:hover {
background-color: #393939;
}
&.selected {
background-color: #2c2c2c;
}
}
}
.conversation {
min-width: 250px;
width: 75%;
display: flex;
flex-direction: column;
justify-content: stretch;
.messages {
flex-grow: 1;
flex-shrink: 1;
min-height: 100px;
}
.chatbox {
flex-grow: 0;
flex-shrink: 1;
display: flex;
justify-content: stretch;
flex-direction: column;
min-height: 2em;
}
}
.container-seperator {
z-index: 100;
height: unset!important;
width: 3px;
background-color: transparent;
border-right: 1px solid #292a2c;
}
}
.container-chatbox {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
flex-grow: 1;
flex-shrink: 1;
min-height: calc(1.5em + 10px);
display: flex;
flex-direction: row;
justify-content: stretch;
width: 100%;
padding: 5px;
.container-emojis {
display: flex;
flex-direction: column;
justify-content: flex-end;
margin-right: 5px;
.button-emoji {
border-radius: 5px;
padding: 2px;
cursor: pointer;
&:hover {
background-color: #393939;
}
.container-icon {
display: flex;
width: calc(1.5em - 4px);
height: calc(1.5em - 4px);
> img {
height: 100%;
width: 100%;
}
}
}
}
.container-input {
display: flex;
flex-direction: column;
justify-content: stretch;
min-height: 1.5em;
width: 100%;
background-color: #464646;
padding: 0 5px;
overflow: hidden;
border-radius: 5px;
textarea {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
width: 100%;
resize: vertical;
min-height: 1.5em;
max-height: 6em;
height: 1.5em;
background-color: transparent;
padding: 0;
margin: 0;
border: none;
outline: none;
color: #a9a9a9;
}
textarea::-webkit-input-placeholder {
color: #363535;
font-style: oblique;
}
textarea:-moz-placeholder {
color: #363535;
font-style: oblique;
}
textarea::-moz-placeholder {
color: #363535;
font-style: oblique;
}
textarea:-ms-input-placeholder {
color: #363535;
font-style: oblique;
}
textarea::-ms-input-placeholder {
color: #363535;
font-style: oblique;
}
textarea::placeholder {
color: #363535;
font-style: oblique;
}
}
}
}
}

View File

@ -212,249 +212,6 @@ code {
padding: 2px;
}
footer {
position: fixed;
width: 100%;
bottom: 0px;
left: 0px;
right: 0px;
height: 25px;
background-color: lightgray;
display: flex;
}
footer .container {
width: 100%;
display: flex;
position: relative;
vertical-align: center;
justify-content: center;
}
$separator_thickness: 4px;
$small_device: 650px;
$animation_length: .5s;
.app {
min-width: 350px;
min-height: 330px;
.container-app-main {
position: relative;
display: flex;
flex-direction: row;
justify-content: stretch;
min-height: 0;
height: 100%;
width: 100%;
border: $separator_thickness solid lightgray;
border-top-width: 0;
}
.container-control-bar {
flex-shrink: 0;
height: 45px;
width: 100%;
border-radius: 2px 2px 0 0;
border-bottom-width: 0;
background-color: lightgrey;
display: flex;
flex-direction: column;
justify-content: center;
}
.container-channel-chat {
min-width: 100px;
width: 60%;
display: flex;
flex-direction: column;
justify-content: stretch;
.container-channel-tree {
background: white;
display: flex;
justify-content: stretch;
height: calc(100% - 250px);
min-height: 100px;
/*
overflow: auto;
overflow-x: visible;
*/
overflow: hidden;
overflow-y: auto;
}
.container-chat {
background: white;
display: flex;
flex-direction: column;
justify-content: stretch;
//max-height: 400px;
height: 250px;
min-height: 100px;
}
}
.container-info {
background: white;
min-width: 100px;
width: 40%;
display: flex;
flex-direction: row;
justify-content: stretch;
}
.hide-small {
opacity: 1;
transition: opacity $animation_length linear;
}
.show-small {
display: none;
opacity: 0;
transition: opacity $animation_length linear;
}
}
@media only screen and (max-width: 400px), only screen and (max-height: 400px) {
.app-container {
overflow: auto;
}
}
@media only screen and (max-width: $small_device) {
.app-container {
right: 0!important;
left: 0!important;;
top: 0!important;;
transition: all $animation_length linear;
overflow: auto;
}
.app {
.container-app-main {
.container-info {
display: none;
position: absolute;
width: 100% !important; /* override the seperator property */
height: 100%;
z-index: 1000;
&.shown {
display: block;
}
.select_info {
> .close {
display: block;
}
}
}
.container-channel-chat + .container-seperator {
display: none;
animation: fadeout $animation_length linear;
}
.container-channel-chat {
width: 100% !important; /* override the seperator property */
}
}
}
.hide-small {
display: none;
opacity: 0;
transition: opacity $animation_length linear;
}
.show-small {
display: block !important;
opacity: 1 !important;
transition: opacity $animation_length linear;
}
}
.container-seperator {
background: lightgray;
flex-grow: 0;
flex-shrink: 0;
&.horizontal {
height: $separator_thickness;
width: 100%;
cursor: row-resize;
}
&.vertical {
width: $separator_thickness;
height: 100%;
cursor: col-resize;
}
&.seperator-selected {
background-color: #00000011;
}
}
.icon-container {
position: relative;
display: inline-block;
height: 16px;
width: 16px;
> img {
position: absolute;
}
}
#mouse-move {
display: none;
position: absolute;
z-index: 10000;
.container {
position: relative;
display: block;
border: 2px solid gray;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
}
}
html, body {
overflow: hidden;
}
body {
padding: 8px;
background: darkgray !important;
}
.icon-playlist-manage {
&.icon {
width: 16px;
@ -472,6 +229,14 @@ body {
background-size: 50px;
}
&.icon_x24 {
width: 24px;
height: 24px;
background-position: -11px -9px;
background-size: 50px;
}
display: inline-block;
background: url('../../img/music/playlist.svg') no-repeat;
}

View File

@ -0,0 +1,260 @@
$separator_thickness: 5px;
$small_device: 650px;
$animation_length: .5s;
.app {
min-width: 350px;
min-height: 330px;
.container-app-main {
margin-top: 5px;
position: relative;
display: flex;
flex-direction: column;
justify-content: stretch;
height: 100%;
width: 100%;
.container-channel-chat {
min-height: 200px;
min-width: 100px;
width: 100%;
display: flex;
flex-direction: row;
justify-content: stretch;
& > * {
height: 100%;
min-height: 250px;
border-radius: 5px;
}
.container-channel-tree {
background: #353535;
min-width: 200px;
display: flex;
justify-content: stretch;
height: 100%;
min-height: 100px;
padding-top: 5px;
/*
overflow: auto;
overflow-x: visible;
*/
overflow: hidden;
overflow-y: auto;
}
.container-chat {
background: #353535;
min-width: 350px;
display: flex;
flex-direction: column;
justify-content: stretch;
}
}
.container-server-log {
min-height: 0;
height: 250px;
width: 100%;
border-radius: 5px;
padding-right: 5px;
padding-left: 5px;
background: #353535;
}
}
.container-control-bar {
z-index: 200;
flex-shrink: 0;
border-radius: 5px;
height: 30px;
width: 100%;
background-color: #454545;
display: flex;
flex-direction: column;
justify-content: center;
}
.hide-small {
opacity: 1;
transition: opacity $animation_length linear;
}
.show-small {
display: none;
opacity: 0;
transition: opacity $animation_length linear;
}
}
.app-container {
right: 0;
left: 0;
top: 0;
overflow: auto;
padding: 5px;
}
@media only screen and (max-width: 400px), only screen and (max-height: 400px) {
.app-container {
overflow: auto;
}
}
@media only screen and (max-width: $small_device) {
.app {
.container-app-main {
.container-info {
display: none;
position: absolute;
width: 100% !important; /* override the seperator property */
height: 100%;
z-index: 1000;
&.shown {
display: block;
}
.select_info {
> .close {
display: block;
}
}
}
.container-channel-chat + .container-seperator {
display: none;
animation: fadeout $animation_length linear;
}
.container-channel-chat {
width: 100% !important; /* override the seperator property */
}
}
}
.hide-small {
display: none;
opacity: 0;
transition: opacity $animation_length linear;
}
.show-small {
display: block !important;
opacity: 1 !important;
transition: opacity $animation_length linear;
}
}
$animation_seperator_length: .1s;
.container-seperator {
-moz-transition: all $animation_seperator_length ease-in;
-o-transition: all $animation_seperator_length ease-in;
-webkit-transition: all $animation_seperator_length ease-in;
transition: all $animation_seperator_length ease-in;
background: #1e1e1e;
flex-grow: 0;
flex-shrink: 0;
&.horizontal {
height: $separator_thickness;
width: 100%;
cursor: row-resize;
}
&.vertical {
width: $separator_thickness;
height: 100%;
cursor: col-resize;
}
&.seperator-selected {
-moz-transition: all $animation_seperator_length ease-in;
-o-transition: all $animation_seperator_length ease-in;
-webkit-transition: all $animation_seperator_length ease-in;
transition: all $animation_seperator_length ease-in;
background-color: #707070;
}
}
.icon-container {
position: relative;
display: inline-block;
height: 16px;
width: 16px;
> img {
position: absolute;
}
}
#mouse-move {
display: none;
position: absolute;
z-index: 10000;
.container {
position: relative;
display: block;
border: 2px solid gray;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
}
}
html, body {
overflow: hidden;
}
body {
background: #1e1e1e !important;
}
footer {
position: fixed;
width: 100%;
bottom: 0px;
left: 0px;
right: 0px;
height: 25px;
background-color: lightgray;
display: flex;
}
footer .container {
width: 100%;
display: flex;
position: relative;
vertical-align: center;
justify-content: center;
}

View File

@ -1 +1,4 @@
$channel_tree_entry_selected: blue;
$channel_tree_entry_selected: #2d2d2d;
$channel_tree_entry_hovered: #393939;
$channel_tree_entry_text_color: #828282;

View File

@ -0,0 +1,56 @@
.container-log {
display: block;
overflow-y: auto;
height: 100%;
width: 100%;
.container-messages {
width: 100%;
line-height: 16px;
}
.log-message {
flex-shrink: 0;
flex-grow: 0;
color: #6e6e6e;
overflow-x: hidden;
overflow-y: hidden;
display: block;
&, > * {
overflow-wrap: break-word;
word-wrap: break-word;
max-width: 100%;
}
> * {
display: inline-block;
font-family: sans-serif;
font-size: 13px;
line-height: 1;
}
> .timestamp {
padding-right: 5px;
}
.log-error {
color: rgba(230, 34, 34, 1);
&:hover {
color: rgba(230, 34, 34, 1);
}
}
.htmltag-client, .htmltag-channel {
color: #d8d8d8;
}
}
}

View File

@ -928,7 +928,7 @@
background-position: calc(-128px * 2) calc(-192px * 2);
}
.icon_x32.client-message_info {
background-position: calc(-160px * 2)pe the key you wish calc(-192px * 2);
background-position: calc(-160px * 2) calc(-192px * 2);
}
.icon_x32.client-message_outgoing {
background-position: calc(-192px * 2) calc(-192px * 2);
@ -969,7 +969,7 @@
.icon_x32.client-permission_overview {
background-position: calc(-64px * 2) calc(-224px * 2);
}
.icon_x32.client-permission_server_groups pe the key you wish{
.icon_x32.client-permission_server_groups {
background-position: calc(-96px * 2) calc(-224px * 2);
}
.icon_x32.client-phoneticsnickname {
@ -1220,4 +1220,615 @@
}
.icon_x32.client-home {
background-position: calc(-192px * 2) calc(-384px * 2);
}
/* replace pattern: "background-position: calc\(((\-?[0-9]{1,3})(px)? \* 2)\) calc\(((\-?[0-9]{1,3})(px)? \* 2)\);" */
/* replace with: "background-position: calc\($2px * 1.5) calc($5px * 1.5);" */
/* Icons x24 */
.icon_x24 {
display: inline-block;
background: url('../../../img/client_icon_sprite.svg'), url('../../img/client_icon_sprite.svg') no-repeat;
background-size: calc(496px * 1.5) calc(400px * 1.5);
height: 24px;
width: 24px;
}
.icon_x24.client-d_sound {
background-position: calc(0px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-d_sound_me {
background-position: calc(-32px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-d_sound_user {
background-position: calc(-64px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-about {
background-position: calc(-96px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-activate_microphone {
background-position: calc(-128px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-add {
background-position: calc(-160px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-add_foe {
background-position: calc(-192px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-add_folder {
background-position: calc(-224px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-add_friend {
background-position: calc(-256px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-addon {
background-position: calc(-288px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-addon-collection {
background-position: calc(-320px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-apply {
background-position: calc(-352px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-arrow_down {
background-position: calc(-384px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-arrow_left {
background-position: calc(-416px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-arrow_right {
background-position: calc(-448px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-arrow_up {
background-position: calc(-480px * 1.5) calc(0px * 1.5);
}
.icon_x24.client-away {
background-position: calc(0px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-ban_client {
background-position: calc(-32px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-ban_list {
background-position: calc(-64px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-bookmark_add {
background-position: calc(-96px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-bookmark_add_folder {
background-position: calc(-128px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-bookmark_duplicate {
background-position: calc(-160px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-bookmark_manager {
background-position: calc(-192px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-bookmark_remove {
background-position: calc(-224px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-broken_image {
background-position: calc(-256px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-browse-addon-online {
background-position: calc(-288px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-capture {
background-position: calc(-320px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-changelog {
background-position: calc(-352px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-change_nickname {
background-position: calc(-384px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-channel_chat {
background-position: calc(-416px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-channel_collapse_all {
background-position: calc(-448px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-channel_commander {
background-position: calc(-480px * 1.5) calc(-32px * 1.5);
}
.icon_x24.client-channel_create {
background-position: calc(0px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-channel_create_sub {
background-position: calc(-32px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-channel_default {
background-position: calc(-64px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-channel_delete {
background-position: calc(-96px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-channel_edit {
background-position: calc(-128px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-channel_expand_all {
background-position: calc(-160px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-channel_green {
background-position: calc(-192px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-channel_green_subscribed {
background-position: calc(-224px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-channel_private {
background-position: calc(-256px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-channel_red {
background-position: calc(-288px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-channel_red_subscribed {
background-position: calc(-320px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-channel_switch {
background-position: calc(-352px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-channel_unsubscribed {
background-position: calc(-384px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-channel_yellow {
background-position: calc(-416px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-channel_yellow_subscribed {
background-position: calc(-448px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-check_update {
background-position: calc(-480px * 1.5) calc(-64px * 1.5);
}
.icon_x24.client-client_hide {
background-position: calc(0px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-client_show {
background-position: calc(-32px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-close_button {
background-position: calc(-64px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-complaint_list {
background-position: calc(-96px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-conflict-icon {
background-position: calc(-128px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-connect {
background-position: calc(-160px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-contact {
background-position: calc(-192px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-copy {
background-position: calc(-224px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-copy_url {
background-position: calc(-256px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-default {
background-position: calc(-288px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-default_for_all_bookmarks {
background-position: calc(-320px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-delete {
background-position: calc(-352px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-delete_avatar {
background-position: calc(-384px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-disconnect {
background-position: calc(-416px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-down {
background-position: calc(-448px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-download {
background-position: calc(-480px * 1.5) calc(-96px * 1.5);
}
.icon_x24.client-edit {
background-position: calc(0px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-edit_friend_foe_status {
background-position: calc(-32px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-emoticon {
background-position: calc(-64px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-error {
background-position: calc(-96px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-file_home {
background-position: calc(-128px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-file_refresh {
background-position: calc(-160px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-filetransfer {
background-position: calc(-192px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-find {
background-position: calc(-224px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-folder {
background-position: calc(-256px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-folder_up {
background-position: calc(-288px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-group_100 {
background-position: calc(-320px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-group_200 {
background-position: calc(-352px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-group_300 {
background-position: calc(-384px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-group_500 {
background-position: calc(-416px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-group_600 {
background-position: calc(-448px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-guisetup {
background-position: calc(-480px * 1.5) calc(-128px * 1.5);
}
.icon_x24.client-hardware_input_muted {
background-position: calc(0px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-hardware_output_muted {
background-position: calc(-32px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-hoster_button {
background-position: calc(-64px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-hotkeys {
background-position: calc(-96px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-icon-pack {
background-position: calc(-128px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-iconsview {
background-position: calc(-160px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-iconviewer {
background-position: calc(-192px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-identity_default {
background-position: calc(-224px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-identity_export {
background-position: calc(-256px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-identity_import {
background-position: calc(-288px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-identity_manager {
background-position: calc(-320px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-info {
background-position: calc(-352px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-input_muted {
background-position: calc(-384px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-input_muted_local {
background-position: calc(-416px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-invite_buddy {
background-position: calc(-448px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-is_talker {
background-position: calc(-480px * 1.5) calc(-160px * 1.5);
}
.icon_x24.client-kick_channel {
background-position: calc(0px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-kick_server {
background-position: calc(-32px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-listview {
background-position: calc(-64px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-loading_image {
background-position: calc(-96px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-message_incoming {
background-position: calc(-128px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-message_info {
background-position: calc(-160px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-message_outgoing {
background-position: calc(-192px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-messages {
background-position: calc(-224px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-moderated {
background-position: calc(-256px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-move_client_to_own_channel {
background-position: calc(-288px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-music {
background-position: calc(-320px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-new_chat {
background-position: calc(-352px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-notifications {
background-position: calc(-384px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-offline_messages {
background-position: calc(-416px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-on_whisperlist {
background-position: calc(-448px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-output_muted {
background-position: calc(-480px * 1.5) calc(-192px * 1.5);
}
.icon_x24.client-permission_channel {
background-position: calc(0px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-permission_client {
background-position: calc(-32px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-permission_overview {
background-position: calc(-64px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-permission_server_groups {
background-position: calc(-96px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-phoneticsnickname {
background-position: calc(-128px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-ping_1 {
background-position: calc(-160px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-ping_2 {
background-position: calc(-192px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-ping_3 {
background-position: calc(-224px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-ping_4 {
background-position: calc(-256px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-ping_calculating {
background-position: calc(-288px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-ping_disconnected {
background-position: calc(-320px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-play {
background-position: calc(-352px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-player_chat {
background-position: calc(-384px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-player_commander_off {
background-position: calc(-416px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-player_commander_on {
background-position: calc(-448px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-player_off {
background-position: calc(-480px * 1.5) calc(-224px * 1.5);
}
.icon_x24.client-player_on {
background-position: calc(0px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-player_whisper {
background-position: calc(-32px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-plugins {
background-position: calc(-64px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-poke {
background-position: calc(-96px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-present {
background-position: calc(-128px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-recording_start {
background-position: calc(-160px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-recording_stop {
background-position: calc(-192px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-refresh {
background-position: calc(-224px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-register {
background-position: calc(-256px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-reload {
background-position: calc(-288px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-remove_foe {
background-position: calc(-320px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-remove_friend {
background-position: calc(-352px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-security {
background-position: calc(-384px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-selectfolder {
background-position: calc(-416px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-send_complaint {
background-position: calc(-448px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-server_green {
background-position: calc(-480px * 1.5) calc(-256px * 1.5);
}
.icon_x24.client-server_log {
background-position: calc(0px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-server_query {
background-position: calc(-32px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-settings {
background-position: calc(-64px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-sort_by_name {
background-position: calc(-96px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-soundpack {
background-position: calc(-128px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-sound-pack {
background-position: calc(-160px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-stop {
background-position: calc(-192px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-subscribe_mode {
background-position: calc(-224px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-subscribe_to_all_channels {
background-position: calc(-256px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-subscribe_to_channel {
background-position: calc(-288px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-subscribe_to_channel_family {
background-position: calc(-320px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-switch_advanced {
background-position: calc(-352px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-switch_standard {
background-position: calc(-384px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-sync-disable {
background-position: calc(-416px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-sync-enable {
background-position: calc(-448px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-sync-icon {
background-position: calc(-480px * 1.5) calc(-288px * 1.5);
}
.icon_x24.client-tab_close_button {
background-position: calc(0px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-talk_power_grant {
background-position: calc(-32px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-talk_power_grant_next {
background-position: calc(-64px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-talk_power_request {
background-position: calc(-96px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-talk_power_request_cancel {
background-position: calc(-128px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-talk_power_revoke {
background-position: calc(-160px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-talk_power_revoke_all_grant_next {
background-position: calc(-192px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-temp_server_password {
background-position: calc(-224px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-temp_server_password_add {
background-position: calc(-256px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-textformat {
background-position: calc(-288px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-textformat_bold {
background-position: calc(-320px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-textformat_foreground {
background-position: calc(-352px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-textformat_italic {
background-position: calc(-384px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-textformat_underline {
background-position: calc(-416px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-theme {
background-position: calc(-448px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-toggle_server_query_clients {
background-position: calc(-480px * 1.5) calc(-320px * 1.5);
}
.icon_x24.client-toggle_whisper {
background-position: calc(0px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-token {
background-position: calc(-32px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-token_use {
background-position: calc(-64px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-translation {
background-position: calc(-96px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-unsubscribe_from_all_channels {
background-position: calc(-128px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-unsubscribe_from_channel_family {
background-position: calc(-160px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-unsubscribe_mode {
background-position: calc(-192px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-up {
background-position: calc(-224px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-upload {
background-position: calc(-256px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-upload_avatar {
background-position: calc(-288px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-urlcatcher {
background-position: calc(-320px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-user-account {
background-position: calc(-352px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-virtualserver_edit {
background-position: calc(-384px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-volume {
background-position: calc(-416px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-warning {
background-position: calc(-448px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-warning_external_link {
background-position: calc(-480px * 1.5) calc(-352px * 1.5);
}
.icon_x24.client-warning_info {
background-position: calc(0px * 1.5) calc(-384px * 1.5);
}
.icon_x24.client-warning_question {
background-position: calc(-32px * 1.5) calc(-384px * 1.5);
}
.icon_x24.client-weblist {
background-position: calc(-64px * 1.5) calc(-384px * 1.5);
}
.icon_x24.client-whisper {
background-position: calc(-96px * 1.5) calc(-384px * 1.5);
}
.icon_x24.client-whisperlists {
background-position: calc(-128px * 1.5) calc(-384px * 1.5);
}
.icon_x24.client-channel_green_subscribed2 {
background-position: calc(-160px * 1.5) calc(-384px * 1.5);
}
.icon_x24.client-home {
background-position: calc(-192px * 1.5) calc(-384px * 1.5);
}

View File

@ -170,9 +170,10 @@
</div>
<div id="spoiler-style" style="z-index: 1000000; position: absolute; display: block; background: white; right: 5px; left: 5px; top: 34px;">
<img src="img/style/default.png">
<!-- <img src="img/style/default.png"> -->
<img src="img/style/user-selected.png">
</div>
<button class="toggle-spoiler-style" style="height: 30px; width: 100px; z-index: 100000000; position: absolute; top: 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>
setTimeout(() => {
$("#spoiler-style").hide();

View File

@ -13,13 +13,13 @@
<div class="container-control-bar">
<div id="control_bar" class="control_bar">
<div class="button btn_connect container-connect" title="{{tr 'Connect to a server' /}}">
<div class="icon_x32 client-connect"></div>
<div class="icon_x24 client-connect"></div>
</div>
<div class="button-dropdown container-disconnect" title="{{tr 'Disconnect from server' /}}" style="display: none">
<div class="buttons">
<div class="button icon_x32 client-disconnect btn_disconnect"></div>
<div class="button-dropdown">
<div class="button icon_x24 client-disconnect btn_disconnect"></div>
<div class="dropdown-arrow">
<div class="arrow down"></div>
</div>
</div>
@ -28,16 +28,13 @@
<div class="btn_connect"><div class="icon client-connect"></div><a>{{tr "Connect to a server" /}}</a></div>
</div>
</div>
<!--
<div class="button btn_disconnect" title="{{tr 'Disconnect from server' /}}" style="display: none">
<div class="icon_x32 client-disconnect"></div>
</div>
-->
<div class="button-dropdown btn_bookmark" title="{{tr 'Bookmarks' /}}">
<div class="buttons">
<div class="button icon_x32 client-bookmark_manager btn_bookmark_list"></div>
<div class="button-dropdown">
<div class="button btn_bookmark_list">
<div class="icon_x24 client-bookmark_manager"></div>
</div>
<div class="dropdown-arrow">
<div class="arrow down"></div>
</div>
</div>
@ -54,8 +51,10 @@
<div class="hide-small button-dropdown btn_away" title="{{tr 'Toggle away status' /}}">
<div class="buttons">
<div class="button icon_x32 client-away btn_away_toggle"></div>
<div class="button-dropdown">
<div class="button btn_away_toggle">
<div class="icon_x24 client-away"></div>
</div>
<div class="dropdown-arrow">
<div class="arrow down"></div>
</div>
</div>
@ -69,17 +68,19 @@
<div class="btn_away_disable_global"><div class="icon client-present"></div><a>{{tr "Go online for all servers" /}}</a></div>
</div>
</div>
<div class="hide-small button btn_mute_input">
<div class="icon_x32 client-input_muted" title="{{tr 'Mute/unmute microphone' /}}"></div>
<div class="hide-small button button-red btn_mute_input">
<div class="icon_x24 client-input_muted" title="{{tr 'Mute/unmute microphone' /}}"></div>
</div>
<div class="hide-small button btn_mute_output">
<div class="icon_x32 client-output_muted" title="{{tr 'Mute/unmute headphones' /}}"></div>
<div class="hide-small button button-red btn_mute_output">
<div class="icon_x24 client-output_muted" title="{{tr 'Mute/unmute headphones' /}}"></div>
</div>
<div class="show-small button-dropdown dropdown-audio" title="{{tr 'Audio settings' /}}">
<div class="buttons">
<div class="button button-display icon_x32 client-music"></div>
<div class="button-dropdown">
<div class="button button-display">
<div class="icon_x24 client-music"></div>
</div>
<div class="dropdown-arrow">
<div class="arrow down"></div>
</div>
</div>
@ -96,12 +97,14 @@
</div>
<div class="divider"></div>
<div class="button button-subscribe-mode">
<div class="icon_x32" title="{{tr 'Toggle channel subscribe mode' /}}"></div>
<div class="icon_x24" title="{{tr 'Toggle channel subscribe mode' /}}"></div>
</div>
<div class="hide-small button-dropdown btn_token" title="{{tr 'Use token' /}}">
<div class="buttons">
<div class="button icon_x32 client-token btn_token_use"></div>
<div class="button-dropdown">
<div class="button btn_token_use">
<div class="icon_x24 client-token"></div>
</div>
<div class="dropdown-arrow">
<div class="arrow down"></div>
</div>
</div>
@ -115,8 +118,10 @@
<div class="show-small button-dropdown dropdown-servertools" title="{{tr 'Server tools' /}}">
<div class="buttons">
<div class="button button-display icon_x32 client-virtualserver_edit"></div>
<div class="button-dropdown">
<div class="button button-display">
<div class="icon_x24 client-virtualserver_edit"></div>
</div>
<div class="dropdown-arrow">
<div class="arrow down"></div>
</div>
</div>
@ -145,20 +150,22 @@
</div>
<div class="hide-small button button-playlist-manage" title="{{tr 'Playlists' /}}">
<div class="icon_x32 icon-playlist-manage"></div>
<div class="icon_x24 icon-playlist-manage"></div>
</div>
<div class="hide-small button btn_banlist" title="{{tr 'Banlist' /}}">
<div class="icon_x32 client-ban_list"></div>
<div class="icon_x24 client-ban_list"></div>
</div>
<div class="hide-small button btn_permissions" title="{{tr 'View/edit permissions' /}}">
<div class="icon_x32 client-permission_overview"></div>
<div class="icon_x24 client-permission_overview"></div>
</div>
<!-- the query button -->
<div class="hide-small button-dropdown btn_query" title="{{tr 'Show/hide server queries' /}}">
<div class="buttons">
<div class="button icon_x32 client-server_query btn_query_toggle"></div>
<div class="button-dropdown">
<div class="button btn_query_toggle">
<div class="icon_x24 client-server_query"></div>
</div>
<div class="dropdown-arrow">
<div class="arrow down"></div>
</div>
</div>
@ -170,7 +177,7 @@
</div>
<div class="divider"></div>
<div class="button btn_open_settings" title="{{tr 'Edit global client settings' /}}">
<div class="icon_x32 client-settings"></div>
<div class="icon_x24 client-settings"></div>
</div>
</div>
</div>
@ -189,22 +196,102 @@
<div class="container-app-main">
<div class="container-channel-chat">
<!-- Channel tree -->
<div class="container-channel-tree main_container">
<div class="container-channel-tree">
<div class="channel-tree" id="channelTree"></div>
</div>
<div class="container-seperator horizontal" seperator-id="seperator-channel-chat"></div>
<div class="container-seperator vertical" seperator-id="seperator-channel-chat"></div>
<!-- Chat window -->
<div class="main_container container-chat" id="chat"> </div>
<div class="container-chat" id="chat"> </div>
</div>
<div class="container-seperator vertical" seperator-id="seperator-main-info"></div>
<div id="select_info" class="main_container container-info"> </div> <!-- Selection info -->
<div class="container-seperator horizontal" seperator-id="seperator-main-log"></div>
<div id="server-log" class="container-server-log"> </div> <!-- Selection info -->
</div>
</div>
</div>
<div id="contextMenu" class="context-menu"></div>
</script>
<script class="jsrender-template" id="tmpl_frame_chat" type="text/html">
<div class="container-chat-frame">
<div class="container-info"></div>
<div class="container-chat"></div>
</div>
</script>
<script class="jsrender-template" id="tmpl_frame_chat_info" type="text/html">
<div class="lane">
<div class="block left">
<div class="title">{{tr "You're talking in Channel" /}}</div>
<div class="value value-voice-channel"></div>
<div class="small-value value-voice-limit"></div>
</div>
<div class="block right">
<div class="title">{{tr "Your ping" /}}</div>
<div class="value value-ping">11 ms</div>
</div>
</div>
<div class="lane">
<div class="block left">
<div class="title">{{tr "You're chatting in Channel" /}}</div>
<div class="value value-text-channel">XXXXXY</div>
<div class="small-value value-limit">0 / Unlimited</div>
</div>
<div class="block right">
<div class="title">{{tr "Private chats" /}} <div class="container-indicator"><div class="chat-counter">2</div></div></div>
<div class="value value-ping">12 conversations</div>
</div>
</div>
</script>
<script class="jsrender-template" id="tmpl_frame_chat_private" type="text/html">
<div class="container-private-conversations">
<div class="conversation-list">
<div class="no-chats">
<div>{{tr "You&nbsp;dont&nbsp;have any&nbsp;chats&nbsp;yet!" /}}</div>
</div>
</div>
<div class="container-seperator vertical" seperator-id="seperator-conversation-list-messages"></div>
<div class="conversation">
<div class="messages"></div>
<div class="chatbox"></div>
</div>
</div>
</script>
<script class="jsrender-template" id="tmpl_frame_chat_private_entry" type="text/html">
<div class="conversation-entry">
<div class="container-avatar">
<div class="avatar" style="display: flex">
<img style="width: 100%; height: 100%" src="img/style/avatar.png">
</div>
<div class="chat-unread"></div>
</div>
<div class="info">
<a class="client-name">{{>client_name}}</a>
<a class="last-message">Today at 12:59 AM</a>
</div>
</div>
</script>
<script class="jsrender-template" id="tmpl_frame_chat_chatbox" type="text/html">
<div class="container-chatbox">
{{if emojy_support}}
<div class="container-emojis">
<div class="button-emoji">
<div class="container-icon">
<img src="img/smiley-smile.svg">
</div>
</div>
</div>
{{/if}}
<div class="container-input">
<textarea placeholder="{{tr 'Type your message here...' /}}"></textarea>
</div>
</div>
</script>
<script class="jsrender-template" id="tmpl_select_info" type="text/html">
<div class="select_info" style="width: 100%; max-width: 100%">
<button type="button" class="close button-modal-close" aria-label="Close">
@ -216,26 +303,6 @@
</div>
</script>
<script class="jsrender-template" id="tmpl_frame_chat" type="text/html">
<div class="container-frame-chat">
<div class="messages">
<div class="message_box"></div>
</div>
<div class="chats"></div>
<div class="input">
<!--<div contentEditable="true" class="input_box"></div>-->
<div class="form-group">
<textarea id="input-chat-input"
type="text"
class="form-control input-message client-chat-box-field"
placeholder="{{tr 'enter a chat message...' /}}"
autocomplete="off"></textarea>
</div>
<button class="btn btn-primary button-send">{{tr "Send" /}}</button>
</div>
</div>
</script>
<script class="jsrender-template" id="tmpl_modal" type="text/html">
<div class="modal fade" tabindex="-1" role="dialog" style="padding-right: 8%; padding-left: 8%;" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document" style="max-width: unset;">
@ -1334,7 +1401,7 @@
</div>
<div class="property">
<div class="key muted-sounds">
Mute sounds when output is muted:
{{tr "Mute sounds when output is muted:" /}}
</div>
<div class="value muted-sounds">
<div class="switch">

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300">
<!-- https://dennism.de/wp-content/uploads/smiley-smile.svg -->
<path fill="#262626" d="M0,149.993333 C0,67.2933333 67.2866667,2.84217094e-14 149.993333,2.84217094e-14 C232.696667,2.84217094e-14 299.986667,67.2933333 299.986667,149.993333 C299.986667,232.703333 232.696667,300 149.993333,300 C67.2866667,300 0,232.703333 0,149.993333 Z M284.986667,149.993333 C284.986667,75.56 224.426667,15 149.993333,15 C75.5466667,15 15,75.56 15,149.993333 C15,224.44 75.5466667,285 149.993333,285 C224.426667,285 284.986667,224.44 284.986667,149.993333 Z M194.993333,121.866667 C185.666667,121.866667 178.12,114.306667 178.12,104.993333 C178.12,95.6866667 185.67,88.12 194.993333,88.12 C204.3,88.1233333 211.866667,95.6866667 211.866667,104.993333 C211.866667,114.31 204.3,121.866667 194.993333,121.866667 Z M104.993333,121.866667 C95.67,121.866667 88.12,114.306667 88.12,104.993333 C88.12,95.6866667 95.67,88.12 104.993333,88.12 C114.303333,88.1233333 121.866667,95.6866667 121.866667,104.993333 C121.866667,114.31 114.3,121.866667 104.993333,121.866667 Z M79.189449,191.903171 C77.0341864,188.693693 77.8887964,184.344712 81.098274,182.189449 C84.3077516,180.034186 88.6567329,180.888796 90.8119956,184.098274 C105.18258,205.498018 124.671916,216.000722 150.000722,216.000722 C175.329705,216.000722 194.820479,205.497839 209.193591,184.097834 C211.349096,180.88852 215.698142,180.034239 218.907457,182.189745 C222.116771,184.34525 222.971052,188.694296 220.815546,191.903611 C203.845419,217.17029 180.000273,230.000722 150.000722,230.000722 C120.000994,230.000722 96.1569752,217.170112 79.189449,191.903171 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

View File

@ -88,6 +88,7 @@ class ConnectionHandler {
permissions: PermissionManager;
groups: GroupManager;
chat_frame: chat.Frame;
select_info: InfoBar;
chat: ChatBox;
@ -117,13 +118,16 @@ class ConnectionHandler {
};
invoke_resized_on_activate: boolean = false;
log: log.ServerLog;
constructor() {
this.settings = new ServerSettings();
this.log = new log.ServerLog(this);
this.select_info = new InfoBar(this);
this.channelTree = new ChannelTree(this);
this.chat = new ChatBox(this);
this.chat_frame = new chat.Frame(this);
this.sound = new sound.SoundManager(this);
this.serverConnection = connection.spawn_server_connection(this);
@ -178,7 +182,13 @@ class ConnectionHandler {
}
}
console.log(tr("Start connection to %s:%d"), server_address.host, server_address.port);
this.chat.serverChat().appendMessage(tr("Initializing connection to {0}{1}"), true, server_address.host, server_address.port == 9987 ? "" : ":" + server_address.port);
this.log.log(log.server.Type.CONNECTION_BEGIN, {
address: {
server_hostname: server_address.host,
server_port: server_address.port
},
client_nickname: parameters.nickname
});
this.channelTree.initialiseHead(addr, server_address);
if(parameters.password && !parameters.password.hashed){
@ -196,7 +206,7 @@ class ConnectionHandler {
if(dns.supported() && !server_address.host.match(Modals.Regex.IP_V4) && !server_address.host.match(Modals.Regex.IP_V6)) {
const id = ++this._connect_initialize_id;
this.chat.serverChat().appendMessage(tr("Resolving hostname..."));
this.log.log(log.server.Type.CONNECTION_HOSTNAME_RESOLVE, {});
try {
const resolved = await dns.resolve_address(server_address.host, { timeout: 5000 }) || {} as any;
if(id != this._connect_initialize_id)
@ -204,7 +214,12 @@ class ConnectionHandler {
server_address.port = resolved.target_port || server_address.port;
server_address.host = resolved.target_ip || server_address.host;
this.chat.serverChat().appendMessage(tr("Hostname successfully resolved to {0}{1}"), true, server_address.host, server_address.port);
this.log.log(log.server.Type.CONNECTION_HOSTNAME_RESOLVED, {
address: {
server_port: server_address.port,
server_hostname: server_address.host
}
});
} catch(error) {
if(id != this._connect_initialize_id)
return; /* we're old */
@ -375,13 +390,15 @@ class ConnectionHandler {
break;
case DisconnectReason.DNS_FAILED:
console.error(tr("Failed to resolve hostname: %o"), data);
this.chat.serverChat().appendError(tr("Failed to resolve hostname: {0}"), data);
this.log.log(log.server.Type.CONNECTION_HOSTNAME_RESOLVE_ERROR, {
message: data as any
});
this.sound.play(Sound.CONNECTION_REFUSED);
break;
case DisconnectReason.CONNECT_FAILURE:
if(this._reconnect_attempt) {
auto_reconnect = true;
this.chat.serverChat().appendError(tr("Connect failed"));
this.log.log(log.server.Type.CONNECTION_FAILED, {});
break;
}
console.error(tr("Could not connect to remote host! Error: %o"), data);
@ -514,7 +531,7 @@ class ConnectionHandler {
console.log(tr("Allowed to auto reconnect but cant reconnect because we dont have any information left..."));
return;
}
this.chat.serverChat().appendMessage(tr("Reconnecting in 5 seconds"));
this.log.log(log.server.Type.RECONNECT_SCHEDULED, {timeout: 50000});
console.log(tr("Allowed to auto reconnect. Reconnecting in 5000ms"));
const server_address = this.serverConnection.remote_address();
@ -522,7 +539,7 @@ class ConnectionHandler {
this._reconnect_timer = setTimeout(() => {
this._reconnect_timer = undefined;
this.chat.serverChat().appendMessage(tr("Reconnecting..."));
this.log.log(log.server.Type.RECONNECT_CANCELED, {});
log.info(LogCategory.NETWORKING, tr("Reconnecting..."));
this.startConnection(server_address.host + ":" + server_address.port, profile, this.reconnect_properties(profile));
@ -533,7 +550,7 @@ class ConnectionHandler {
cancel_reconnect() {
if(this._reconnect_timer) {
this.chat.serverChat().appendMessage(tr("Reconnect canceled"));
this.log.log(log.server.Type.RECONNECT_CANCELED, {});
clearTimeout(this._reconnect_timer);
this._reconnect_timer = undefined;
}
@ -581,7 +598,7 @@ class ConnectionHandler {
if(Object.keys(property_update).length > 0) {
this.serverConnection.send_command("clientupdate", property_update).catch(error => {
log.warn(LogCategory.GENERAL, tr("Failed to update client audio hardware properties. Error: %o"), error);
this.chat.serverChat().appendError(tr("Failed to update audio hardware properties."));
this.log.log(log.server.Type.ERROR_CUSTOM, {message: tr("Failed to update audio hardware properties.")});
/* Update these properties anyways (for case the server fails to handle the command) */
const updates = [];
@ -640,7 +657,7 @@ class ConnectionHandler {
client_output_hardware: this.client_status.sound_playback_supported
}).catch(error => {
log.warn(LogCategory.GENERAL, tr("Failed to sync handler state with server. Error: %o"), error);
this.chat.serverChat().appendError(tr("Failed to sync handler state with server."));
this.log.log(log.server.Type.ERROR_CUSTOM, {message: tr("Failed to sync handler state with server.")});
});
}
@ -654,7 +671,7 @@ class ConnectionHandler {
client_away_message: typeof(this.client_status.away) === "string" ? this.client_status.away : "",
}).catch(error => {
log.warn(LogCategory.GENERAL, tr("Failed to update away status. Error: %o"), error);
this.chat.serverChat().appendError(tr("Failed to update away status."));
this.log.log(log.server.Type.ERROR_CUSTOM, {message: tr("Failed to update away status.")});
});
control_bar.update_button_away();

View File

@ -58,10 +58,14 @@ namespace connection {
if(!res.success) {
if(res.id == 2568) { //Permission error
res.message = tr("Insufficient client permissions. Failed on permission ") + this.connection_handler.permissions.resolveInfo(res.json["failed_permid"] as number).name;
this.connection_handler.chat.serverChat().appendError(tr("Insufficient client permissions. Failed on permission {}"), this.connection_handler.permissions.resolveInfo(res.json["failed_permid"] as number).name);
this.connection_handler.log.log(log.server.Type.ERROR_PERMISSION, {
permission: this.connection_handler.permissions.resolveInfo(res.json["failed_permid"] as number)
});
this.connection_handler.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
} else {
this.connection_handler.chat.serverChat().appendError(res.extra_message.length == 0 ? res.message : res.extra_message);
this.connection_handler.log.log(log.server.Type.ERROR_CUSTOM, {
message: res.extra_message.length == 0 ? res.message : res.extra_message
});
}
}
} else if(typeof(ex) === "string") {
@ -144,7 +148,9 @@ namespace connection {
this.connection_handler.chat.serverChat().name = this.connection.client.channelTree.server.properties["virtualserver_name"];
this.connection_handler.chat.serverChat().appendMessage(tr("Connected as {0}"), true, this.connection.client.getClient().createChatTag(true));
this.connection_handler.log.log(log.server.Type.CONNECTION_CONNECTED, {
own_client: this.connection_handler.getClient().log_data()
});
this.connection_handler.sound.play(Sound.CONNECTION_CONNECTED);
this.connection.client.onConnected();
}
@ -283,38 +289,32 @@ namespace connection {
if(this.connection_handler.client_status.queries_visible || client.properties.client_type != ClientType.CLIENT_QUERY) {
const own_channel = this.connection.client.getClient().currentChannel();
this.connection_handler.log.log(log.server.Type.CLIENT_VIEW_ENTER, {
channel_from: old_channel ? old_channel.log_data() : undefined,
channel_to: channel ? channel.log_data() : undefined,
client: client.log_data(),
invoker: invokeruid ? {
client_id: invokerid,
client_name: invokername,
client_unique_id: invokeruid
} : undefined,
message:reason_msg,
reason: parseInt(reason_id),
own_channel: channel == own_channel
});
if(reason_id == ViewReasonId.VREASON_USER_ACTION) {
if(own_channel == channel)
if(old_channel)
this.connection_handler.sound.play(Sound.USER_ENTERED);
else
this.connection_handler.sound.play(Sound.USER_ENTERED_CONNECT);
if(old_channel) {
this.connection_handler.chat.serverChat().appendMessage(tr("{0} appeared from {1} to {2}"), true, client.createChatTag(true), old_channel.generate_tag(true), channel.generate_tag(true));
} else {
this.connection_handler.chat.serverChat().appendMessage(tr("{0} connected to channel {1}"), true, client.createChatTag(true), channel.generate_tag(true));
}
} else if(reason_id == ViewReasonId.VREASON_MOVED) {
if(own_channel == channel)
this.connection_handler.sound.play(Sound.USER_ENTERED_MOVED);
this.connection_handler.chat.serverChat().appendMessage(tr("{0} appeared from {1} to {2}, moved by {3}"), true,
client.createChatTag(true),
old_channel ? old_channel.generate_tag(true) : undefined,
channel.generate_tag(true),
ClientEntry.chatTag(invokerid, invokername, invokeruid),
);
} else if(reason_id == ViewReasonId.VREASON_CHANNEL_KICK) {
if(own_channel == channel)
this.connection_handler.sound.play(Sound.USER_ENTERED_KICKED);
this.connection_handler.chat.serverChat().appendMessage(tr("{0} appeared from {1} to {2}, kicked by {3}{4}"), true,
client.createChatTag(true),
old_channel ? old_channel.generate_tag(true) : undefined,
channel.generate_tag(true),
ClientEntry.chatTag(invokerid, invokername, invokeruid),
reason_msg.length > 0 ? " (" + entry["msg"] + ")" : ""
);
} else {
console.warn(tr("Unknown reasonid for %o"), reason_id);
}
@ -363,6 +363,7 @@ namespace connection {
if(client instanceof LocalClientEntry) {
this.connection_handler.update_voice_status();
this.connection_handler.chat_frame.info_frame().update_channel_talk();
}
}
}
@ -389,6 +390,7 @@ namespace connection {
this.connection.client.handleDisconnect(DisconnectReason.SERVER_CLOSED, entry);
} else
this.connection.client.handleDisconnect(DisconnectReason.UNKNOWN, entry);
this.connection_handler.chat_frame.info_frame().update_channel_talk();
return;
}
@ -398,57 +400,36 @@ namespace connection {
let channel_from = tree.findChannel(entry["cfid"]);
let channel_to = tree.findChannel(entry["ctid"]);
this.connection_handler.log.log(log.server.Type.CLIENT_VIEW_LEAVE, {
channel_from: channel_from ? channel_from.log_data() : undefined,
channel_to: channel_to ? channel_to.log_data() : undefined,
client: client.log_data(),
invoker: entry["invokeruid"] ? {
client_id: entry["invokerid"],
client_name: entry["invokername"],
client_unique_id: entry["invokeruid"]
} : undefined,
message: entry["reasonmsg"],
reason: parseInt(entry["reasonid"]),
ban_time: parseInt(entry["bantime"]),
own_channel: channel_from == own_channel
});
if(reason_id == ViewReasonId.VREASON_USER_ACTION) {
this.connection_handler.chat.serverChat().appendMessage(tr("{0} disappeared from {1} to {2}"), true, client.createChatTag(true), channel_from.generate_tag(true), channel_to.generate_tag(true));
if(channel_from == own_channel)
this.connection_handler.sound.play(Sound.USER_LEFT);
} else if(reason_id == ViewReasonId.VREASON_SERVER_LEFT) {
this.connection_handler.chat.serverChat().appendMessage(tr("{0} left the server{1}"), true,
client.createChatTag(true),
entry["reasonmsg"] ? " (" + entry["reasonmsg"] + ")" : ""
);
if(channel_from == own_channel)
this.connection_handler.sound.play(Sound.USER_LEFT_DISCONNECT);
} else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_KICK) {
this.connection_handler.chat.serverChat().appendError(tr("{0} was kicked from the server by {1}.{2}"),
client.createChatTag(true),
ClientEntry.chatTag(entry["invokerid"], entry["invokername"], entry["invokeruid"]),
entry["reasonmsg"] ? " (" + entry["reasonmsg"] + ")" : ""
);
if(channel_from == own_channel)
this.connection_handler.sound.play(Sound.USER_LEFT_KICKED_SERVER);
} else if(reason_id == ViewReasonId.VREASON_CHANNEL_KICK) {
this.connection_handler.chat.serverChat().appendError(tr("{0} was kicked from your channel by {1}.{2}"),
client.createChatTag(true),
ClientEntry.chatTag(entry["invokerid"], entry["invokername"], entry["invokeruid"]),
entry["reasonmsg"] ? " (" + entry["reasonmsg"] + ")" : ""
);
if(channel_from == own_channel)
this.connection_handler.sound.play(Sound.USER_LEFT_KICKED_CHANNEL);
} else if(reason_id == ViewReasonId.VREASON_BAN) {
//"Mulus" was banned for 1 second from the server by "WolverinDEV" (Sry brauchte kurz ein opfer :P <3 (Nohomo))
let duration = "permanently";
if(entry["bantime"])
duration = "for " + formatDate(Number.parseInt(entry["bantime"]));
this.connection_handler.chat.serverChat().appendError(tr("{0} was banned {1} by {2}.{3}"),
client.createChatTag(true),
duration,
ClientEntry.chatTag(entry["invokerid"], entry["invokername"], entry["invokeruid"]),
entry["reasonmsg"] ? " (" + entry["reasonmsg"] + ")" : ""
);
if(channel_from == own_channel)
this.connection_handler.sound.play(Sound.USER_LEFT_BANNED);
} else if(reason_id == ViewReasonId.VREASON_TIMEOUT) {
this.connection_handler.chat.serverChat().appendError(tr("{0} timed out ({1})"),
client.createChatTag(true),
entry["reasonmsg"] ? " (" + entry["reasonmsg"] + ")" : ""
);
if(channel_from == own_channel)
this.connection_handler.sound.play(Sound.USER_LEFT_TIMEOUT);
} else {
@ -507,14 +488,40 @@ namespace connection {
if(entry !== client && entry.get_audio_handle())
entry.get_audio_handle().abort_replay();
if(self)
this.connection_handler.chat_frame.info_frame().update_channel_talk();
const own_channel = this.connection.client.getClient().currentChannel();
this.connection_handler.log.log(log.server.Type.CLIENT_VIEW_MOVE, {
channel_from: channel_from ? {
channel_id: channel_from.channelId,
channel_name: channel_from.channelName()
} : undefined,
channel_from_own: channel_from == own_channel,
channel_to: channel_to ? {
channel_id: channel_to.channelId,
channel_name: channel_to.channelName()
} : undefined,
channel_to_own: channel_to == own_channel,
client: {
client_id: client.clientId(),
client_name: client.clientNickName(),
client_unique_id: client.properties.client_unique_identifier
},
client_own: self,
invoker: json["invokeruid"] ? {
client_id: parseInt(json["invokerid"]),
client_name: json["invokername"],
client_unique_id: json["invokeruid"]
} : undefined,
message: json["reasonmsg"],
reason: parseInt(json["reasonid"]),
});
if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
this.connection_handler.chat.serverChat().appendMessage(self ? tr("You was moved by {3} from channel {1} to {2}") : tr("{0} was moved from channel {1} to {2} by {3}"), true,
client.createChatTag(true),
channel_from ? channel_from.generate_tag(true) : undefined,
channel_to.generate_tag(true),
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"])
);
if(self)
this.connection_handler.sound.play(Sound.USER_MOVED_SELF);
else if(own_channel == channel_to)
@ -522,24 +529,12 @@ namespace connection {
else if(own_channel == channel_from)
this.connection_handler.sound.play(Sound.USER_LEFT_MOVED);
} else if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
this.connection_handler.chat.serverChat().appendMessage(self ? tr("You switched from channel {1} to {2}") : tr("{0} switched from channel {1} to {2}"), true,
client.createChatTag(true),
channel_from ? channel_from.generate_tag(true) : undefined,
channel_to.generate_tag(true)
);
if(self) {} //If we do an action we wait for the error response
else if(own_channel == channel_to)
this.connection_handler.sound.play(Sound.USER_ENTERED);
else if(own_channel == channel_from)
this.connection_handler.sound.play(Sound.USER_LEFT);
} else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
this.connection_handler.chat.serverChat().appendMessage(self ? tr("You got kicked out of the channel {1} to channel {2} by {3}{4}") : tr("{0} got kicked from channel {1} to {2} by {3}{4}"), true,
client.createChatTag(true),
channel_from ? channel_from.generate_tag(true) : undefined,
channel_to.generate_tag(true),
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
);
if(self) {
this.connection_handler.sound.play(Sound.CHANNEL_KICKED);
} else if(own_channel == channel_to)
@ -632,7 +627,14 @@ namespace connection {
this.connection_handler.sound.play(Sound.MESSAGE_RECEIVED, {default_volume: .5});
this.connection_handler.chat.channelChat().appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), MessageHelper.bbcode_chat(json["msg"]))
} else if(mode == 3) {
this.connection_handler.chat.serverChat().appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), MessageHelper.bbcode_chat(json["msg"]));
this.connection_handler.log.log(log.server.Type.GLOBAL_MESSAGE, {
message: json["msg"],
sender: {
client_unique_id: json["invokeruid"],
client_name: json["invokername"],
client_id: parseInt(json["invokerid"])
}
});
}
}

View File

@ -587,7 +587,9 @@ const loader_javascript = {
"js/ui/frames/SelectedItemInfo.js",
"js/ui/frames/ControlBar.js",
"js/ui/frames/chat.js",
"js/ui/frames/chat_frame.js",
"js/ui/frames/connection_handlers.js",
"js/ui/frames/server_log.js",
//Load permissions
"js/permission/PermissionManager.js",
@ -697,6 +699,7 @@ const loader_style = {
load_style_debug: async () => {
await loader.load_styles([
"css/static/main.css",
"css/static/main-layout.css",
"css/static/helptag.css",
"css/static/scroll.css",
"css/static/channel-tree.css",
@ -724,7 +727,9 @@ const loader_style = {
"css/static/frame/SelectInfo.css",
"css/static/control_bar.css",
"css/static/context_menu.css",
"css/static/frame-chat.css",
"css/static/connection_handlers.css",
"css/static/server-log.css",
"css/static/htmltags.css"
]);
},

View File

@ -366,6 +366,16 @@ function main() {
};
server_connections.set_active_connection_handler(server_connections.server_connection_handlers()[0]);
const convs = server_connections.active_connection_handler().chat_frame.private_conversations();
let conv = convs.create_conversation("xxxx0", "WolverinDEV");
conv = convs.create_conversation("xxxx1", "Darkatzu");
conv = convs.create_conversation("xxxx2", "ZameXxX");
conv.set_unread_flag(true);
conv = convs.create_conversation("xxxx3", "Vagur");
//for(let i = 0; i < 100; i++)
// convs.create_conversation('xx' + i, "WolverinDEV #" + i);
if(settings.static(Settings.KEY_FLAG_CONNECT_DEFAULT, false) && settings.static(Settings.KEY_CONNECT_ADDRESS, "")) {
const profile_uuid = settings.static(Settings.KEY_CONNECT_PROFILE, (profiles.default_profile() || {id: 'default'}).id);

View File

@ -685,6 +685,7 @@ class ChannelEntry {
log.table("Clannel update properties", entries);
}
let info_update = false;
for(let variable of variables) {
let key = variable.key;
let value = variable.value;
@ -692,6 +693,7 @@ class ChannelEntry {
if(key == "channel_name") {
this.__updateChannelName();
info_update = true;
} else if(key == "channel_order") {
let order = this.channelTree.findChannel(this.properties.channel_order);
this.channelTree.moveChannel(this, order, this.parent);
@ -707,6 +709,7 @@ class ChannelEntry {
tag.children().detach();
this.channelTree.client.fileManager.icons.generateTag(this.properties.channel_icon_id).appendTo(tag);
}
info_update = true;
} else if(key == "channel_codec") {
(this.properties.channel_codec == 5 || this.properties.channel_codec == 3 ? $.fn.show : $.fn.hide).apply(this.channelTag().find(".icons .icon_music"));
this.channelTag().find(".icons .icon_no_sound").toggle(!(
@ -727,10 +730,19 @@ class ChannelEntry {
this._cached_channel_description_promise_resolve = undefined;
this._cached_channel_description_promise_reject = undefined;
}
if(key == "channel_maxclients" || key == "channel_maxfamilyclients" || key == "channel_flag_private" || key == "channel_flag_password")
if(key == "channel_maxclients" || key == "channel_maxfamilyclients" || key == "channel_flag_private" || key == "channel_flag_password") {
this.updateChannelTypeIcon();
info_update = true;
}
}
group.end();
if(info_update) {
const _client = this.channelTree.client.getClient();
if(_client.currentChannel() === this)
this.channelTree.client.chat_frame.info_frame().update_channel_talk();
//TODO chat channel!
}
}
updateChannelTypeIcon() {
@ -862,4 +874,11 @@ class ChannelEntry {
this._subscribe_mode = mode;
this.channelTree.client.settings.changeServer(Settings.FN_SERVER_CHANNEL_SUBSCRIBE_MODE(this), mode);
}
log_data() : log.server.base.Channel {
return {
channel_name: this.channelName(),
channel_id: this.channelId
}
}
}

View File

@ -817,7 +817,15 @@ class ClientEntry {
if(!this._channel) return;
const index = this._channel.calculate_family_index();
this.tag.css('padding-left', (index + 2) * 16 + "px");
this.tag.css('padding-left', (5 + (index + 2) * 16) + "px");
}
log_data() : log.server.base.Client {
return {
client_unique_id: this.properties.client_unique_identifier,
client_name: this.clientNickName(),
client_id: this._clientId
}
}
}
@ -912,9 +920,14 @@ class LocalClientEntry extends ClientEntry {
elm.text(_self.clientNickName());
_self.handle.serverConnection.command_helper.updateClient("client_nickname", text).then((e) => {
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, text);
this.channelTree.client.chat.serverChat().appendMessage(tr("Nickname successfully changed"));
this.channelTree.client.log.log(log.server.Type.CLIENT_NICKNAME_CHANGED, {
client: this.log_data(),
own_action: true
});
}).catch((e: CommandResult) => {
this.channelTree.client.chat.serverChat().appendError(tr("Could not change nickname ({})"), e.extra_message);
this.channelTree.client.log.log(log.server.Type.CLIENT_NICKNAME_CHANGE_FAILED, {
reason: e.extra_message
});
_self.openRename();
});
});

View File

@ -80,7 +80,7 @@ class ControlBar {
initialise() {
let dropdownify = (tag: JQuery) => {
tag.find(".button-dropdown").on('click', () => {
tag.find(".dropdown-arrow").on('click', () => {
tag.addClass("displayed");
}).hover(() => {
tag.addClass("displayed");
@ -553,11 +553,11 @@ class ControlBar {
callback: () => bookmark_connect(true)
}, contextmenu.Entry.CLOSE(() => {
setTimeout(() => {
this.htmlTag.find(".btn_bookmark.button-dropdown").removeClass("force-show")
this.htmlTag.find(".btn_bookmark.dropdown-arrow").removeClass("force-show")
}, 250);
}));
this.htmlTag.find(".btn_bookmark.button-dropdown").addClass("force-show");
this.htmlTag.find(".btn_bookmark.dropdown-arrow").addClass("force-show");
})
)
} else {

View File

@ -408,7 +408,7 @@ class ChatBox {
this.createChat("chat_server", ChatType.SERVER).onMessageSend = (text: string) => {
if(!this.connection_handler.serverConnection) {
this.serverChat().appendError(tr("Could not send chant message (Not connected)"));
this.serverChat().appendError(tr("Could not send chat message (Not connected)"));
return;
}

View File

@ -0,0 +1,292 @@
/* the bar on the right with the chats (Channel & Client) */
namespace chat {
export class InfoFrame {
private readonly handle: Frame;
private _html_tag: JQuery;
constructor(handle: Frame) {
this.handle = handle;
this._build_html_tag();
this.update_channel_talk();
}
html_tag() : JQuery { return this._html_tag; }
private _build_html_tag() {
this._html_tag = $("#tmpl_frame_chat_info").renderTag();
}
update_channel_talk() {
const client = this.handle.handle.connected ? this.handle.handle.getClient() : undefined;
const channel = client ? client.currentChannel() : undefined;
const html_tag = this._html_tag.find(".value-voice-channel");
const html_limit_tag = this._html_tag.find(".value-voice-limit");
html_limit_tag.text("");
html_tag.children().detach();
if(channel) {
if(channel.properties.channel_icon_id != 0)
client.handle.fileManager.icons.generateTag(channel.properties.channel_icon_id).appendTo(html_tag);
$.spawn("div").text(channel.channelName()).appendTo(html_tag);
//channel.properties.channel_maxclients
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 {
$.spawn("div").text("Not connected").appendTo(html_tag);
}
}
update_chat_counter() {
const count = this.handle.private_conversations().conversations().filter(e => e.is_unread()).length;
const count_container = this._html_tag.find(".container-indicator");
const count_tag = count_container.find(".chat-counter");
count_container.toggle(count > 0);
count_tag.text(count);
}
}
export enum ConversationState {
ACTIVE,
CLOSED,
OFFLINE
}
class ChatBox {
private _html_tag: JQuery;
private _html_input: JQuery<HTMLTextAreaElement>;
private __callback_text_changed;
private __callback_key_down;
constructor() {
this.__callback_key_down = this._callback_key_down.bind(this);
this.__callback_text_changed = this._callback_text_changed.bind(this);
this._build_html_tag();
this._initialize_listener();
}
html_tag() : JQuery {
return this._html_tag;
}
private _initialize_listener() {
this._html_input.on("cut paste drop keydown", (event) => setTimeout(() => this.__callback_text_changed(event), 0));
this._html_input.on("change", this.__callback_text_changed);
this._html_input.on("keydown", this.__callback_key_down);
}
private _build_html_tag() {
this._html_tag = $("#tmpl_frame_chat_chatbox").renderTag({
emojy_support: true
});
this._html_input = this._html_tag.find("textarea") as any;
}
private _callback_text_changed(event) {
if(event && event.isDefaultPrevented())
return;
/* Auto resize */
const text = this._html_input[0];
text.style.height = "1em";
text.style.height = text.scrollHeight + 'px';
}
private _callback_key_down(event: KeyboardEvent) {
if(event.shiftKey)
return;
if(event.key.toLowerCase() === "enter") {
event.preventDefault();
//TODO Notify text!
console.log("Sending text: %s", this._html_input.val());
this._html_input.val(undefined);
setTimeout(() => this.__callback_text_changed());
}
}
}
export class PrivateConveration {
readonly handle: PrivateConverations;
private _flag_unread: boolean;
private _html_entry_tag: JQuery;
private _html_messages_tag: JQuery; /* TODO: Consider to create them every time on the fly? */
private _message_history: {
message: string;
sender: "self" | "partner";
}[] = [];
client_unique_id: string;
client_id: string;
client_name: string;
state: ConversationState = ConversationState.ACTIVE;
constructor(handle: PrivateConverations, client_unique_id: string, client_name: string) {
this.handle = handle;
this.client_name = client_name;
this.client_unique_id = client_unique_id;
this._flag_unread = false;
this._build_entry_tag();
this.set_unread_flag(false);
}
entry_tag() : JQuery {
return this._html_entry_tag;
}
append_message(message: string, sender: "self" | "partner") {
this._message_history.push({
message: message,
sender: sender
});
}
private _build_entry_tag() {
this._html_entry_tag = $("#tmpl_frame_chat_private_entry").renderTag({
client_name: this.client_name
});
this._html_entry_tag.on('click', event => this.handle.set_selected_conversation(this));
}
set_client_name(name: string) {
if(this.client_name === name)
return;
this.client_name = name;
this._html_entry_tag.find(".client-name").text(name);
}
set_unread_flag(flag: boolean, update_chat_counter?: boolean) {
if(flag === this._flag_unread)
return;
this._flag_unread = flag;
this._html_entry_tag.toggleClass("unread", flag);
if(typeof(update_chat_counter) !== "boolean" || update_chat_counter)
this.handle.handle.info_frame().update_chat_counter();
}
is_unread() : boolean { return this._flag_unread; }
}
export class PrivateConverations {
readonly handle: Frame;
private _chat_box: ChatBox;
private _html_tag: JQuery;
private _container_conversation: JQuery;
private _container_conversation_messages: JQuery;
private _container_conversation_list: JQuery;
private _html_no_chats: JQuery;
private _conversations: PrivateConveration[] = [];
private _current_conversation: PrivateConveration = undefined;
constructor(handle: Frame) {
this.handle = handle;
this._chat_box = new ChatBox();
this._build_html_tag();
}
html_tag() : JQuery { return this._html_tag; }
conversations() : PrivateConveration[] { return this._conversations; }
create_conversation(client_uid: string, client_name: string) : PrivateConveration {
const conv = new PrivateConveration(this, client_uid, client_name);
this._conversations.push(conv);
this._html_no_chats.hide();
this._container_conversation_list.append(conv.entry_tag());
return conv;
}
delete_conversation(conv: PrivateConveration) {
if(!this._conversations.remove(conv))
return;
//TODO: May animate?
conv.entry_tag().detach();
this._html_no_chats.toggle(this._conversations.length == 0);
if(conv === this._current_conversation)
this.set_selected_conversation(undefined);
}
clear_conversations() {
while(this._conversations.length > 0)
this.delete_conversation(this._conversations[0]);
this.handle.info_frame().update_chat_counter();
}
set_selected_conversation(conv: PrivateConveration | undefined) {
if(conv === this._current_conversation)
return;
this._container_conversation_list.find(".selected").removeClass("selected");
this._container_conversation_messages.children().detach();
this._current_conversation = conv;
if(!this._current_conversation)
return;
this._current_conversation.entry_tag().addClass("selected");
}
private _build_html_tag() {
this._html_tag = $("#tmpl_frame_chat_private").renderTag().dividerfy();
this._container_conversation = this._html_tag.find(".conversation");
this._container_conversation_messages = this._container_conversation.find(".messages");
this._container_conversation.find(".chatbox").append(this._chat_box.html_tag());
this._container_conversation_list = this._html_tag.find(".conversation-list");
this._html_no_chats = this._container_conversation_list.find(".no-chats");
}
}
export class Frame {
readonly handle: ConnectionHandler;
private _info_frame: InfoFrame;
private _html_tag: JQuery;
private _container_info: JQuery;
private _container_chat: JQuery;
private _conversations: PrivateConverations;
constructor(handle: ConnectionHandler) {
this.handle = handle;
this._info_frame = new InfoFrame(this);
this._conversations = new PrivateConverations(this);
this._build_html_tag();
this.show_private_conversations();
}
html_tag() : JQuery { return this._html_tag; }
info_frame() : InfoFrame { return this._info_frame; }
private _build_html_tag() {
this._html_tag = $("#tmpl_frame_chat").renderTag();
this._container_info =this._html_tag.find(".container-info");
this._container_chat =this._html_tag.find(".container-chat");
this._info_frame.html_tag().appendTo(this._container_info);
}
private_conversations() : PrivateConverations {
return this._conversations;
}
show_private_conversations() {
this._container_chat.children().detach();
this._container_chat.append(this._conversations.html_tag());
}
}
}

View File

@ -5,9 +5,10 @@ class ServerConnectionManager {
private connection_handlers: ConnectionHandler[] = [];
private active_handler: ConnectionHandler | undefined;
private _container_log_server: JQuery;
private _container_channel_tree: JQuery;
private _container_select_info: JQuery;
private _container_chat_box: JQuery;
private _container_chat: JQuery;
private _tag: JQuery;
private _tag_connection_entries: JQuery;
@ -29,9 +30,10 @@ class ServerConnectionManager {
this._tag_button_scoll_left.on('click', this._button_scroll_left_clicked.bind(this));
this._tag_button_scoll_right.on('click', this._button_scroll_right_clicked.bind(this));
this._container_log_server = $("#server-log");
this._container_channel_tree = $("#channelTree");
this._container_select_info = $("#select_info");
this._container_chat_box = $("#chat");
this._container_chat = $("#chat");
this.set_active_connection_handler(undefined);
}
@ -43,8 +45,8 @@ class ServerConnectionManager {
control_bar.initialize_connection_handler_state(handler);
handler.tag_connection_handler.appendTo(this._tag_connection_entries);
this._tag.toggleClass("shown", this.connection_handlers.length > 1);
this._update_scroll();
return handler;
}
@ -52,6 +54,7 @@ class ServerConnectionManager {
this.connection_handlers.remove(handler);
handler.tag_connection_handler.detach();
this._update_scroll();
this._tag.toggleClass("shown", this.connection_handlers.length > 1);
if(handler.serverConnection) {
const connected = handler.connected;
@ -72,7 +75,8 @@ class ServerConnectionManager {
this._tag_connection_entries.find(".active").removeClass("active");
this._container_channel_tree.children().detach();
this._container_select_info.children().detach();
this._container_chat_box.children().detach();
this._container_chat.children().detach();
this._container_log_server.children().detach();
control_bar.set_connection_handler(handler);
if(handler) {
@ -80,7 +84,8 @@ class ServerConnectionManager {
this._container_channel_tree.append(handler.channelTree.tag_tree());
this._container_select_info.append(handler.select_info.get_tag());
this._container_chat_box.append(handler.chat.htmlTag);
this._container_chat.append(handler.chat_frame.html_tag());
this._container_log_server.append(handler.log.html_tag());
if(handler.invoke_resized_on_activate)
handler.resize_elements();

View File

@ -0,0 +1,448 @@
namespace log {
export namespace server {
export enum Type {
CONNECTION_BEGIN = "connection_begin",
CONNECTION_HOSTNAME_RESOLVE = "connection_hostname_resolve",
CONNECTION_HOSTNAME_RESOLVE_ERROR = "connection_hostname_resolve_error",
CONNECTION_HOSTNAME_RESOLVED = "connection_hostname_resolved",
CONNECTION_LOGIN = "connection_login",
CONNECTION_CONNECTED = "connection_connected",
CONNECTION_FAILED = "connection_failed",
CONNECTION_VOICE_SETUP_FAILED = "connection_voice_setup_failed",
GLOBAL_MESSAGE = "global_message",
CLIENT_VIEW_ENTER = "client_view_enter",
CLIENT_VIEW_LEAVE = "client_view_leave",
CLIENT_VIEW_MOVE = "client_view_move",
CLIENT_NICKNAME_CHANGED = "client_nickname_changed",
CLIENT_NICKNAME_CHANGE_FAILED = "client_nickname_change_failed",
CLIENT_SERVER_GROUP_ADD = "client_server_group_add",
CLIENT_SERVER_GROUP_REMOVE = "client_server_group_remove",
CLIENT_CHANNEL_GROUP_CHANGE = "client_channel_group_change",
CHANNEL_CREATE = "channel_create",
CHANNEL_DELETE = "channel_delete",
ERROR_CUSTOM = "error_custom",
ERROR_PERMISSION = "error_permission",
RECONNECT_SCHEDULED = "reconnect_scheduled",
RECONNECT_EXECUTE = "reconnect_execute",
RECONNECT_CANCELED = "reconnect_canceled"
}
export namespace base {
export type Client = {
client_unique_id: string;
client_name: string;
client_id: number;
}
export type Channel = {
channel_id: number;
channel_name: string;
}
export type Server = {
server_name: string;
server_unique_id: string;
}
export type ServerAddress = {
server_hostname: string;
server_port: number;
}
}
export namespace event {
export type GlobalMessage = {
sender: base.Client;
message: string;
}
export type ConnectBegin = {
address: base.ServerAddress;
client_nickname: string;
}
export type ErrorCustom = {
message: string;
}
export type ReconnectScheduled = {
timeout: number;
}
export type ReconnectCanceled = { }
export type ReconnectExecute = { }
export type ErrorPermission = {
permission: PermissionInfo;
}
//tr("You was moved by {3} from channel {1} to {2}") : tr("{0} was moved from channel {1} to {2} by {3}")
//tr("You switched from channel {1} to {2}") : tr("{0} switched from channel {1} to {2}")
//tr("You got kicked out of the channel {1} to channel {2} by {3}{4}") : tr("{0} got kicked from channel {1} to {2} by {3}{4}")
export type ClientMove = {
channel_from?: base.Channel;
channel_from_own: boolean;
channel_to?: base.Channel;
channel_to_own: boolean;
client: base.Client;
client_own: boolean;
invoker?: base.Client;
message?: string;
reason: ViewReasonId;
}
export type ClientEnter = {
channel_from?: base.Channel;
channel_to?: base.Channel;
client: base.Client;
invoker?: base.Client;
message?: string;
own_channel: boolean;
reason: ViewReasonId;
ban_time?: number;
}
export type ClientLeave = {
channel_from?: base.Channel;
channel_to?: base.Channel;
client: base.Client;
invoker?: base.Client;
message?: string;
own_channel: boolean;
reason: ViewReasonId;
ban_time?: number;
}
export type ChannelCreate = {
creator: base.Client;
channel: base.Channel;
own_action: boolean;
}
export type ChannelDelete = {
deleter: base.Client;
channel: base.Channel;
own_action: boolean;
}
export type ConnectionConnected = {
own_client: base.Client;
}
export type ConnectionFailed = {};
export type ConnectionLogin = {}
export type ConnectionHostnameResolve = {};
export type ConnectionHostnameResolved = {
address: base.ServerAddress;
}
export type ConnectionHostnameResolveError = {
message: string;
}
export type ConnectionVoiceSetupFailed = {
reason: string;
reconnect_delay: number; /* if less or equal to 0 reconnect is prohibited */
}
export type ClientNicknameChanged = {
own_action: boolean;
client: base.Client;
}
export type ClientNicknameChangeFailed = {
reason: string;
}
}
export type LogMessage = {
type: Type;
timestamp: number;
data: any;
}
export interface TypeInfo {
"connection_begin" : event.ConnectBegin;
"global_message": event.GlobalMessage;
"error_custom": event.ErrorCustom;
"error_permission": event.ErrorPermission;
"connection_hostname_resolved": event.ConnectionHostnameResolved;
"connection_hostname_resolve": event.ConnectionHostnameResolve;
"connection_hostname_resolve_error": event.ConnectionHostnameResolveError;
"connection_failed": event.ConnectionFailed;
"connection_login": event.ConnectionLogin;
"connection_connected": event.ConnectionConnected;
"connection_voice_setup_failed": event.ConnectionVoiceSetupFailed;
"reconnect_scheduled": event.ReconnectScheduled;
"reconnect_canceled": event.ReconnectCanceled;
"reconnect_execute": event.ReconnectExecute;
"client_view_enter": event.ClientEnter;
"client_view_move": event.ClientMove;
"client_view_leave": event.ClientLeave;
"client_nickname_change_failed": event.ClientNicknameChangeFailed,
"client_nickname_changed": event.ClientNicknameChanged,
"channel_create": event.ChannelCreate;
"channel_delete": event.ChannelDelete;
}
type MessageBuilderOptions = {};
type MessageBuilder<T extends keyof server.TypeInfo> = (data: TypeInfo[T], options: MessageBuilderOptions) => JQuery[] | undefined;
export const MessageBuilders: {[key: string]: MessageBuilder<any>} = {
"global_message": (data: event.GlobalMessage, options) => {
return [];
},
"error_custom": (data: event.ErrorCustom, options) => {
return [$.spawn("div").addClass("log-error").text(data.message)]
}
};
}
export class ServerLog {
private readonly handle: ConnectionHandler;
private history_length: number = 100;
private _log: server.LogMessage[] = [];
private _html_tag: JQuery;
private _log_container: JQuery;
private auto_follow: boolean; /* automatic scroll to bottom */
private _ignore_event: number; /* after auto scroll we've triggered the scroll event. We want to prevent this so we capture the next event */
constructor(handle: ConnectionHandler) {
this.handle = handle;
this.auto_follow = true;
this._html_tag = $.spawn("div").addClass("container-log");
this._log_container = $.spawn("div").addClass("container-messages");
this._log_container.appendTo(this._html_tag);
this._html_tag.on('scroll', event => {
if(Date.now() - this._ignore_event < 100) {
this._ignore_event = 0;
return;
}
this.auto_follow = (this._html_tag[0].scrollTop + this._html_tag[0].clientHeight + this._html_tag[0].clientHeight * .125) > this._html_tag[0].scrollHeight;
})
}
log<T extends keyof server.TypeInfo>(type: T, data: server.TypeInfo[T]) {
const event = {
data: data,
timestamp: Date.now(),
type: type as any
};
this._log.push(event);
while(this._log.length > this.history_length)
this._log.pop_front();
this.append_log(event);
}
html_tag() : JQuery {
return this._html_tag;
}
private append_log(message: server.LogMessage) {
let container = $.spawn("div").addClass("log-message");
/* build timestamp */
{
const num = number => ('00' + number).substr(-2);
const date = new Date(message.timestamp);
$.spawn("div")
.addClass("timestamp")
.text("<" + num(date.getHours()) + ":" + num(date.getMinutes()) + ":" + num(date.getSeconds()) + ">")
.appendTo(container);
}
/* build message data */
{
const builder = server.MessageBuilders[message.type];
if(!builder) {
MessageHelper.formatMessage(tr("missing log message builder {0}!"), message.type).forEach(e => e.addClass("log-error").appendTo(container));
} else {
const elements = builder(message.data, {});
if(!elements)
return; /* discard message */
container.append(...elements);
}
}
this._ignore_event = Date.now();
this._log_container.append(container);
/* max history messages! */
const messages = this._log_container.children();
let index = 0;
while(messages.length - index > this.history_length)
index++;
const hide_elements = messages.filter(idx => idx < index);
hide_elements.hide(250, () => hide_elements.detach());
if(this.auto_follow)
this._html_tag.scrollTop(this._html_tag[0].scrollHeight);
}
}
}
/* impl of the parsers */
namespace log {
export namespace server {
namespace impl {
const client_tag = (client: base.Client, braces?: boolean) => htmltags.generate_client_object({
client_unique_id: client.client_unique_id,
client_id: client.client_id,
client_name: client.client_name,
add_braces: braces
});
const channel_tag = (channel: base.Channel, braces?: boolean) => htmltags.generate_channel_object({
channel_display_name: channel.channel_name,
channel_name: channel.channel_name,
channel_id: channel.channel_id,
add_braces: braces
});
MessageBuilders["connection_begin"] = (data: event.ConnectBegin, options) => {
return MessageHelper.formatMessage(tr("Connecting to {0}{1}"), data.address.server_hostname, data.address.server_port == 9987 ? "" : (":" + data.address.server_port));
};
MessageBuilders["connection_hostname_resolve"] = (data: event.ConnectionHostnameResolve, options) => MessageHelper.formatMessage(tr("Resolving hostname"));
MessageBuilders["connection_hostname_resolved"] = (data: event.ConnectionHostnameResolved, options) => MessageHelper.formatMessage(tr("Hostname resolved successfully to {0}:{1}"), data.address.server_hostname, data.address.server_port);
MessageBuilders["connection_hostname_resolve_error"] = (data: event.ConnectionHostnameResolveError, options) => MessageHelper.formatMessage(tr("Failed to resolve hostname. Connecting to given hostname. Error: {0}"), data.message);
MessageBuilders["connection_login"] = (data: event.ConnectionLogin, options) => MessageHelper.formatMessage(tr("Logging in..."));
MessageBuilders["connection_failed"] = (data: event.ConnectionFailed, options) => MessageHelper.formatMessage(tr("Connect failed."));
MessageBuilders["connection_connected"] = (data: event.ConnectionConnected, options) => MessageHelper.formatMessage(tr("Connected as {0}"), client_tag(data.own_client, true));
MessageBuilders["connection_voice_setup_failed"] = (data: event.ConnectionVoiceSetupFailed, options) => {
return MessageHelper.formatMessage(tr("Failed to setup voice bridge: {0}. Allow reconnect: {1}"), data.reason, data.reconnect_delay > 0 ? tr("yes") : tr("no"));
};
MessageBuilders["error_permission"] = (data: event.ErrorPermission, options) => {
return MessageHelper.formatMessage(tr("Insufficient client permissions. Failed on permission {0}"), data.permission.name).map(e => e.addClass("log-error"));
};
MessageBuilders["client_view_enter"] = (data: event.ClientEnter, options) => {
if(data.reason == ViewReasonId.VREASON_SYSTEM) {
return undefined;
} if(data.reason == ViewReasonId.VREASON_USER_ACTION) {
/* client appeared */
if(data.channel_from) {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} appeared from {1} to your {2}") : tr("{0} appeared from {1} to {2}"), client_tag(data.client), channel_tag(data.channel_from), channel_tag(data.channel_to));
} else {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} connected to your channel {1}") : tr("{0} connected to channel {1}"), client_tag(data.client), channel_tag(data.channel_to));
}
} else if(data.reason == ViewReasonId.VREASON_MOVED) {
if(data.channel_from) {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} appeared from {1} to your channel {2}, moved by {3}") : tr("{0} appeared from {1} to {2}, moved by {3}"),
client_tag(data.client),
channel_tag(data.channel_from),
channel_tag(data.channel_to),
client_tag(data.invoker)
);
} else {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} appeared to your channel {1}, moved by {2}") : tr("{0} appeared to {1}, moved by {2}"),
client_tag(data.client),
channel_tag(data.channel_to),
client_tag(data.invoker)
);
}
} else if(data.reason == ViewReasonId.VREASON_CHANNEL_KICK) {
if(data.channel_from) {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} appeared from {1} to your channel {2}, kicked by {3}{4}") : tr("{0} appeared from {1} to {2}, kicked by {3}{4}"),
client_tag(data.client),
channel_tag(data.channel_from),
channel_tag(data.channel_to),
client_tag(data.invoker),
data.message ? (" (" + data.message + ")") : ""
);
} else {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} appeared to your channel {1}, kicked by {2}{3}") : tr("{0} appeared to {1}, kicked by {2}{3}"),
client_tag(data.client),
channel_tag(data.channel_to),
client_tag(data.invoker),
data.message ? (" (" + data.message + ")") : ""
);
}
}
return [$.spawn("div").addClass("log-error").text("Invalid view enter reason id (" + data.message + ")")];
};
MessageBuilders["client_view_move"] = (data: event.ClientMove, options) => {
if(data.reason == ViewReasonId.VREASON_MOVED) {
return MessageHelper.formatMessage(data.client_own ? tr("You was moved by {3} from channel {1} to {2}") : tr("{0} was moved from channel {1} to {2} by {3}"),
client_tag(data.client),
channel_tag(data.channel_from),
channel_tag(data.channel_to),
client_tag(data.invoker)
);
} else if(data.reason == ViewReasonId.VREASON_USER_ACTION) {
return MessageHelper.formatMessage(data.client_own ? tr("You switched from channel {1} to {2}") : tr("{0} switched from channel {1} to {2}"),
client_tag(data.client),
channel_tag(data.channel_from),
channel_tag(data.channel_to)
);
} else if(data.reason == ViewReasonId.VREASON_CHANNEL_KICK) {
return MessageHelper.formatMessage(data.client_own ? tr("You got kicked out of the channel {1} to channel {2} by {3}{4}") : tr("{0} got kicked from channel {1} to {2} by {3}{4}"),
client_tag(data.client),
channel_tag(data.channel_from),
channel_tag(data.channel_to),
client_tag(data.invoker),
data.message ? (" (" + data.message + ")") : ""
);
}
return [$.spawn("div").addClass("log-error").text("Invalid view move reason id (" + data.reason + ")")];
};
MessageBuilders["client_view_leave"] = (data: event.ClientLeave, options) => {
if(data.reason == ViewReasonId.VREASON_USER_ACTION) {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} disappeared from your channel {1} to {2}") : tr("{0} disappeared from {1} to {2}"), client_tag(data.client), channel_tag(data.channel_from), channel_tag(data.channel_to));
} else if(data.reason == ViewReasonId.VREASON_SERVER_LEFT) {
return MessageHelper.formatMessage(tr("{0} left the server{1}"), client_tag(data.client), data.message ? (" (" + data.message + ")") : "");
} else if(data.reason == ViewReasonId.VREASON_SERVER_KICK) {
return MessageHelper.formatMessage(tr("{0} was kicked from the server by {1}.{2}"), client_tag(data.client), client_tag(data.invoker), data.message ? (" (" + data.message + ")") : "");
} else if(data.reason == ViewReasonId.VREASON_CHANNEL_KICK) {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} was kicked from your channel by {2}.{3}") : tr("{0} was kicked from channel {1} by {2}.{3}"),
client_tag(data.client),
channel_tag(data.channel_from),
client_tag(data.invoker),
data.message ? (" (" + data.message + ")") : ""
);
} else if(data.reason == ViewReasonId.VREASON_BAN) {
let duration = "permanently";
if(data.ban_time)
duration = "for " + formatDate(data.ban_time);
return MessageHelper.formatMessage(tr("{0} was banned {1} by {2}.{3}"),
client_tag(data.client),
duration,
client_tag(data.invoker),
data.message ? (" (" + data.message + ")") : ""
);
} else if(data.reason == ViewReasonId.VREASON_TIMEOUT) {
return MessageHelper.formatMessage(tr("{0} timed out{1}"), client_tag(data.client), data.message ? (" (" + data.message + ")") : "");
}
return [$.spawn("div").addClass("log-error").text("Invalid view leave reason id (" + data.message + ")")];
}
}
}
}

View File

@ -65,6 +65,10 @@ namespace htmltags {
return result;
}
export function generate_client_object(properties: ClientProperties) : JQuery {
return $(this.generate_client(properties));
}
/* required for the bbcodes */
function generate_channel_open(properties: ChannelProperties) : string {
let result = "";
@ -103,6 +107,10 @@ namespace htmltags {
return result;
}
export function generate_channel_object(properties: ChannelProperties) : JQuery {
return $(this.generate_channel(properties));
}
export namespace callbacks {
export function callback_context_client(element: JQuery) {

View File

@ -280,17 +280,19 @@ namespace Modals {
icon.state = "error";
};
if(image.naturalWidth > 32 && image.naturalHeight > 32) {
width_error("width and height (max 32px)");
return;
}
if(image.naturalWidth > 32) {
width_error("width (max 32px)");
return;
}
if(image.naturalHeight > 32) {
width_error("height (max 32px)");
return;
if(!result.startsWith("data:image/svg+xml")) {
if(image.naturalWidth > 32 && image.naturalHeight > 32) {
width_error("width and height (max 32px). Given: " + image.naturalWidth + "x" + image.naturalHeight);
return;
}
if(image.naturalWidth > 32) {
width_error("width (max 32px)");
return;
}
if(image.naturalHeight > 32) {
width_error("height (max 32px)");
return;
}
}
console.log("Image loaded (%dx%d) %s (%s)", image.naturalWidth, image.naturalHeight, image.name, icon.icon_id);
icon.image_element = () => {

View File

@ -299,6 +299,11 @@ namespace Modals {
const permission = entry.permission();
const value: PermissionValue = this.permission_value_map[permission.id];
if(permission.name === "i_icon_id") {
entry.set_icon_id_image(undefined);
entry.on_icon_select = this.icon_selector;
}
if(value && value.hasValue()) {
entry.value = value.value;
entry.flag_skip = value.flag_skip;
@ -311,13 +316,11 @@ namespace Modals {
}).catch(error => {
console.warn(tr("Failed to load icon for permission editor: %o"), error);
});
entry.on_icon_select = this.icon_selector;
}
} else {
entry.value = undefined;
entry.flag_skip = false;
entry.flag_negate = false;
entry.set_icon_id_image(undefined);
}
if(value && value.hasGrant()) {

View File

@ -643,7 +643,11 @@ class ChannelTree {
return new Promise<ChannelEntry>(resolve => { resolve(channel); })
}).then(channel => {
this.client.chat.serverChat().appendMessage(tr("Channel {} successfully created!"), true, channel.generate_tag(true));
this.client.log.log(log.server.Type.CHANNEL_CREATE, {
channel: channel.log_data(),
creator: this.client.getClient().log_data(),
own_action: true
});
this.client.sound.play(Sound.CHANNEL_CREATED);
});
});

View File

@ -45,7 +45,7 @@ namespace connection {
on_connect: () => void = () => {
console.log(tr("Socket connected"));
this.client.chat.serverChat().appendMessage(tr("Logging in..."));
this.client.log.log(log.server.Type.CONNECTION_LOGIN, {});
this._handshakeHandler.initialize();
this._handshakeHandler.startHandshake();
};

View File

@ -393,7 +393,10 @@ namespace audio {
} else if(json["request"] == "status") {
if(json["state"] == "failed") {
const chandler = this.connection.client;
chandler.chat.serverChat().appendError(tr("Failed to setup voice bridge ({}). Allow reconnect: {}"), json["reason"], json["allow_reconnect"]);
chandler.log.log(log.server.Type.CONNECTION_VOICE_SETUP_FAILED, {
reason: json["reason"],
reconnect_delay: json["allow_reconnect"] ? 1 : 0
});
log.error(LogCategory.NETWORKING, tr("Failed to setup voice bridge (%s). Allow reconnect: %s"), json["reason"], json["allow_reconnect"]);
if(json["allow_reconnect"] == true) {
this.createSession();