Added a lot of new stuff
parent
efb4bad182
commit
6d537bac15
|
@ -1,4 +1,12 @@
|
|||
# Changelog:
|
||||
* **04.04.19**
|
||||
- Fixed issue with client icons not updating correctly (Showing invalid microphone state)
|
||||
- Added multi server mode
|
||||
- Connect URL not disconnecting from all other servers
|
||||
- Open certificate accept URL in another tab
|
||||
- Improved the host banner experience
|
||||
- Hiding server group types in permission editor which are not editable
|
||||
|
||||
* **28.03.19**
|
||||
- Improved icon and avatar cache handling
|
||||
- Added an icon manager
|
||||
|
|
|
@ -13,7 +13,7 @@ html, body {
|
|||
height: 100%;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
justify-content: stretch;
|
||||
|
||||
.app {
|
||||
width: 100%;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import "properties";
|
||||
|
||||
/* the channel tree */
|
||||
.channel-tree {
|
||||
-webkit-touch-callout: none;
|
||||
|
@ -60,7 +62,11 @@
|
|||
}
|
||||
|
||||
&.selected {
|
||||
background-color: blue;
|
||||
background-color: $channel_tree_entry_selected;
|
||||
|
||||
.name {
|
||||
color: whitesmoke;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,10 +85,6 @@
|
|||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
&.selected {
|
||||
background-color: blue;
|
||||
}
|
||||
|
||||
.channel-type {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
@ -128,6 +130,13 @@
|
|||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: $channel_tree_entry_selected;
|
||||
.channel-name {
|
||||
color: whitesmoke;
|
||||
}
|
||||
}
|
||||
|
||||
.show-channel-normal-only {
|
||||
display: none;
|
||||
|
||||
|
@ -191,7 +200,11 @@
|
|||
}
|
||||
|
||||
&.selected {
|
||||
background-color: blue;
|
||||
background-color: $channel_tree_entry_selected;
|
||||
|
||||
.client-name {
|
||||
color: whitesmoke;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
|
||||
.container-connection-handlers {
|
||||
height: 35px;
|
||||
background-color: lightgray;
|
||||
|
||||
border: 4px solid lightgray;
|
||||
border-top: 1px dotted gray;
|
||||
border-bottom-width: 0;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
position: relative;
|
||||
|
||||
.connection-handlers {
|
||||
height: 100%;
|
||||
width: fit-content;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: left;
|
||||
|
||||
.connection-container {
|
||||
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;
|
||||
|
||||
.server-icon {
|
||||
align-self: center;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.server-name {
|
||||
align-self: center;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.button-close {
|
||||
align-self: center;
|
||||
|
||||
&:hover {
|
||||
background-color: #e7e7e7;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #FFFFFF33;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
overflow-x: auto;
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
.container-scroll {
|
||||
margin-top: 5px;
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
display: none;
|
||||
flex-direction: row;
|
||||
|
||||
&.enabled {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.button-scroll {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
|
||||
border: 1px #2222223b solid;
|
||||
border-radius: 2px;
|
||||
background: #e7e7e7;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
|
||||
&:hover {
|
||||
background: #eeeeee;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background: #9e9e9e;
|
||||
&:hover {
|
||||
background: #9e9e9e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.scrollbar {
|
||||
.connection-handlers {
|
||||
width: calc(100% - 45px);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -6,6 +6,11 @@ $background:lightgray;
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
/* tmp fix for ultra small devices */
|
||||
overflow-y: visible;
|
||||
|
||||
|
@ -99,8 +104,6 @@ $background:lightgray;
|
|||
border: 2px solid $border_color_activated;
|
||||
width: 230px;
|
||||
|
||||
user-select: none;
|
||||
|
||||
z-index: 1000;
|
||||
/*box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);*/
|
||||
|
||||
|
@ -136,7 +139,7 @@ $background:lightgray;
|
|||
}
|
||||
}
|
||||
|
||||
&:hover.displayed {
|
||||
&:hover.displayed, &.force-show {
|
||||
.dropdown {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -66,10 +66,12 @@
|
|||
|
||||
flex-grow: 1;
|
||||
flex-shrink: 2;
|
||||
|
||||
max-height: 25%;
|
||||
min-height: 0;
|
||||
|
||||
display: flex;
|
||||
justify-content: stretch;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
|
@ -81,6 +83,12 @@
|
|||
.hostbanner {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
.meta-image {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.image-container {
|
||||
display: flex;
|
||||
|
|
|
@ -238,6 +238,7 @@ $animation_length: .5s;
|
|||
|
||||
.app {
|
||||
min-width: 350px;
|
||||
min-height: 330px;
|
||||
|
||||
.container-app-main {
|
||||
position: relative;
|
||||
|
@ -245,6 +246,7 @@ $animation_length: .5s;
|
|||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
|
@ -254,9 +256,11 @@ $animation_length: .5s;
|
|||
}
|
||||
|
||||
.container-control-bar {
|
||||
flex-shrink: 0;
|
||||
|
||||
height: 45px;
|
||||
width: 100%;
|
||||
border-radius: 2px 0 0 0;
|
||||
border-radius: 2px 2px 0 0;
|
||||
border-bottom-width: 0;
|
||||
background-color: lightgrey;
|
||||
|
||||
|
@ -327,6 +331,12 @@ $animation_length: .5s;
|
|||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
|
@ -334,11 +344,12 @@ $animation_length: .5s;
|
|||
top: 0;
|
||||
|
||||
transition: all $animation_length linear;
|
||||
overflow: auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.app {
|
||||
.container-app-main {
|
||||
|
||||
.container-info {
|
||||
display: none;
|
||||
position: absolute;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
$channel_tree_entry_selected: blue;
|
|
@ -1,4 +1,4 @@
|
|||
#chat {
|
||||
.container-frame-chat {
|
||||
font-family: Arial, serif;
|
||||
font-size: 12px;
|
||||
/*white-space: pre;*/
|
||||
|
|
|
@ -12,12 +12,27 @@
|
|||
<!-- navigation bar -->
|
||||
<div class="container-control-bar">
|
||||
<div id="control_bar" class="control_bar">
|
||||
<div class="button btn_connect" title="{{tr 'Connect to a server' /}}">
|
||||
<div class="button btn_connect container-connect" title="{{tr 'Connect to a server' /}}">
|
||||
<div class="icon_x32 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="arrow down"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown" style="width: 350px">
|
||||
<div class="btn_disconnect"><div class="icon client-disconnect"></div><a>{{tr "Disconnect from current server" /}}</a></div>
|
||||
<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">
|
||||
|
@ -44,9 +59,14 @@
|
|||
<div class="arrow down"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<div class="btn_away_toggle"><div class="icon client-away"></div><a>{{tr "Toggle away status" /}}</a></div>
|
||||
<div class="btn_away_message"><div class="icon client-away"></div><a>{{tr "Set away message" /}}</a></div>
|
||||
<div class="dropdown" style="width: 350px">
|
||||
<div class="btn_away_disable"><div class="icon client-present"></div><a>{{tr "Go online" /}}</a></div>
|
||||
<div class="btn_away_enable"><div class="icon client-away"></div><a>{{tr "Set away on this server" /}}</a></div>
|
||||
<div class="btn_away_message"><div class="icon client-away"></div><a>{{tr "Set away message on this server" /}}</a></div>
|
||||
<hr class="btn_away_message_global"> <!-- applied to this HR this class because it needs to get hidden as well if we dont have global settings -->
|
||||
<div class="btn_away_enable_global"><div class="icon client-away"></div><a>{{tr "Set away for all servers" /}}</a></div>
|
||||
<div class="btn_away_message_global"><div class="icon client-away"></div><a>{{tr "Set away message for all servers" /}}</a></div>
|
||||
<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">
|
||||
|
@ -154,6 +174,17 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-connection-handlers scrollbar" id="connection-handlers">
|
||||
<div class="connection-handlers"> </div>
|
||||
<div class="container-scroll">
|
||||
<div class="button-scroll button-scroll-left">
|
||||
<div class="icon client-arrow_left"></div>
|
||||
</div>
|
||||
<div class="button-scroll button-scroll-right disabled">
|
||||
<div class="icon client-arrow_right"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container-app-main">
|
||||
<div class="container-channel-chat">
|
||||
|
@ -164,43 +195,47 @@
|
|||
|
||||
<div class="container-seperator horizontal" seperator-id="seperator-channel-chat"></div>
|
||||
<!-- Chat window -->
|
||||
<div class="main_container container-chat">
|
||||
<div id="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"
|
||||
placeholder="{{tr 'enter a chat message...' /}}"
|
||||
autocomplete="off"></textarea>
|
||||
</div>
|
||||
<button class="btn btn-primary button-send">{{tr "Send" /}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main_container 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 class="select_info" style="width: 100%; max-width: 100%">
|
||||
<button type="button" class="close" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<div class="container-banner"></div>
|
||||
<!-- <div class="container-seperator horizontal" seperator-id="seperator-hostbanner-info"></div> -->
|
||||
<div class="container-select-info"></div>
|
||||
</div>
|
||||
</div> <!-- Selection info -->
|
||||
<div id="select_info" class="main_container container-info"> </div> <!-- Selection info -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="contextMenu" class="context-menu"></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" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<div class="container-banner"></div>
|
||||
<!-- <div class="container-seperator horizontal" seperator-id="seperator-hostbanner-info"></div> -->
|
||||
<div class="container-select-info"></div>
|
||||
</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"
|
||||
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;">
|
||||
|
@ -330,7 +365,10 @@
|
|||
</div>
|
||||
</modal-body>
|
||||
<modal-footer>
|
||||
<button type="button" class="btn btn-raised btn-success button-connect">Connect</button>
|
||||
<button type="button" class="btn btn-raised btn-success button-connect">{{tr "Connect" /}}</button>
|
||||
{{if multi_tab}}
|
||||
<button type="button" class="btn btn-raised btn-success button-connect-new-tab">{{tr "Connect in a new tab" /}}</button>
|
||||
{{/if}}
|
||||
</modal-footer>
|
||||
</modal>
|
||||
</script>
|
||||
|
@ -2313,8 +2351,13 @@
|
|||
</div>
|
||||
</script>
|
||||
<script class="jsrender-template" id="tmpl_selected_hostbanner" type="text/html">
|
||||
<div class="hostbanner">
|
||||
<a class="image-container" href="{{:property_virtualserver_hostbanner_url}}" target="_blank">
|
||||
<div class="hostbanner" x-divider-require-resize="1">
|
||||
<img class="meta-image hostbanner-image" src="{{:hostbanner_gfx_url}}"/>
|
||||
<a class="image-container"
|
||||
{{if property_virtualserver_hostbanner_url}}
|
||||
href="{{:property_virtualserver_hostbanner_url}}"
|
||||
{{/if}}
|
||||
target="_blank">
|
||||
<div
|
||||
style="background: center no-repeat url({{:hostbanner_gfx_url}})"
|
||||
alt="{{tr 'Host banner'/}}"
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
interface Window {
|
||||
BroadcastChannel: BroadcastChannel;
|
||||
}
|
||||
|
||||
namespace bipc {
|
||||
export interface BroadcastMessage {
|
||||
timestamp: number;
|
||||
receiver: string;
|
||||
sender: string;
|
||||
|
||||
type: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
function uuidv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
interface ProcessQuery {
|
||||
timestamp: number
|
||||
query_id: string;
|
||||
}
|
||||
|
||||
export interface ProcessQueryResponse {
|
||||
request_timestamp: number
|
||||
request_query_id: string;
|
||||
|
||||
device_id: string;
|
||||
protocol: number;
|
||||
}
|
||||
|
||||
export interface CertificateAcceptCallback {
|
||||
request_id: string;
|
||||
}
|
||||
export interface CertificateAcceptSucceeded {
|
||||
|
||||
}
|
||||
|
||||
export abstract class BasicIPCHandler {
|
||||
protected static readonly BROADCAST_UNIQUE_ID = "00000000-0000-4000-0000-000000000000";
|
||||
protected static readonly PROTOCOL_VERSION = 1;
|
||||
|
||||
protected unique_id;
|
||||
|
||||
protected constructor() { }
|
||||
|
||||
setup() {
|
||||
this.unique_id = uuidv4(); /* lets get an unique identifier */
|
||||
}
|
||||
|
||||
abstract send_message(type: string, data: any, target?: string);
|
||||
|
||||
protected handle_message(message: BroadcastMessage) {
|
||||
console.log("Received: %o", message);
|
||||
|
||||
if(message.receiver === BasicIPCHandler.BROADCAST_UNIQUE_ID) {
|
||||
if(message.type == "process-query") {
|
||||
log.debug(LogCategory.IPC, tr("Received a device query from %s."), message.sender);
|
||||
this.send_message("process-query-response", {
|
||||
request_query_id: (<ProcessQuery>message.data).query_id,
|
||||
request_timestamp: (<ProcessQuery>message.data).timestamp,
|
||||
|
||||
device_id: this.unique_id,
|
||||
protocol: BasicIPCHandler.PROTOCOL_VERSION
|
||||
} as ProcessQueryResponse, message.sender);
|
||||
return;
|
||||
}
|
||||
} else if(message.receiver === this.unique_id) {
|
||||
if(message.type == "process-query-response") {
|
||||
const response: ProcessQueryResponse = message.data;
|
||||
if(this._query_results[response.request_query_id])
|
||||
this._query_results[response.request_query_id].push(response);
|
||||
else {
|
||||
log.warn(LogCategory.IPC, tr("Received a query response for an unknown request."));
|
||||
}
|
||||
return;
|
||||
} else if(message.type == "certificate-accept-callback") {
|
||||
const data: CertificateAcceptCallback = message.data;
|
||||
if(!this._cert_accept_callbacks[data.request_id]) {
|
||||
log.warn(LogCategory.IPC, tr("Received certificate accept callback for an unknown request ID."));
|
||||
return;
|
||||
}
|
||||
this._cert_accept_callbacks[data.request_id]();
|
||||
delete this._cert_accept_callbacks[data.request_id];
|
||||
|
||||
this.send_message("certificate-accept-succeeded", {
|
||||
|
||||
} as CertificateAcceptSucceeded, message.sender);
|
||||
return;
|
||||
} else if(message.type == "certificate-accept-succeeded") {
|
||||
if(!this._cert_accept_succeeded[message.sender]) {
|
||||
log.warn(LogCategory.IPC, tr("Received certificate accept succeeded, but haven't a callback."));
|
||||
return;
|
||||
}
|
||||
this._cert_accept_succeeded[message.sender]();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _query_results: {[key: string]:ProcessQueryResponse[]} = {};
|
||||
async query_processes(timeout?: number) : Promise<ProcessQueryResponse[]> {
|
||||
const query_id = uuidv4();
|
||||
this._query_results[query_id] = [];
|
||||
|
||||
this.send_message("process-query", {
|
||||
query_id: query_id,
|
||||
timestamp: Date.now()
|
||||
} as ProcessQuery);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, timeout || 250));
|
||||
const result = this._query_results[query_id];
|
||||
delete this._query_results[query_id];
|
||||
return result;
|
||||
}
|
||||
|
||||
private _cert_accept_callbacks: {[key: string]:(() => any)} = {};
|
||||
register_certificate_accept_callback(callback: () => any) : string {
|
||||
const id = uuidv4();
|
||||
this._cert_accept_callbacks[id] = callback;
|
||||
return this.unique_id + ":" + id;
|
||||
}
|
||||
|
||||
private _cert_accept_succeeded: {[sender: string]:(() => any)} = {};
|
||||
post_certificate_accpected(id: string, timeout?: number) : Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = id.split(":");
|
||||
const timeout_id = setTimeout(() => {
|
||||
delete this._cert_accept_succeeded[data[0]];
|
||||
clearTimeout(timeout_id);
|
||||
reject("timeout");
|
||||
}, timeout || 250);
|
||||
this._cert_accept_succeeded[data[0]] = () => {
|
||||
delete this._cert_accept_succeeded[data[0]];
|
||||
clearTimeout(timeout_id);
|
||||
resolve();
|
||||
};
|
||||
this.send_message("certificate-accept-callback", {
|
||||
request_id: data[1]
|
||||
} as CertificateAcceptCallback, data[0]);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class BroadcastChannelIPC extends BasicIPCHandler {
|
||||
private static readonly CHANNEL_NAME = "TeaSpeak-Web";
|
||||
|
||||
private channel: BroadcastChannel;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
this.channel = new BroadcastChannel(BroadcastChannelIPC.CHANNEL_NAME);
|
||||
this.channel.onmessage = this.on_message.bind(this);
|
||||
this.channel.onmessageerror = this.on_error.bind(this);
|
||||
}
|
||||
|
||||
private on_message(event: MessageEvent) {
|
||||
if(typeof(event.data) !== "string") {
|
||||
log.warn(LogCategory.IPC, tr("Received message with an invalid type (%s): %o"), typeof(event.data), event.data);
|
||||
return;
|
||||
}
|
||||
|
||||
let message: BroadcastMessage;
|
||||
try {
|
||||
message = JSON.parse(event.data);
|
||||
} catch(error) {
|
||||
log.error(LogCategory.IPC, tr("Received an invalid encoded message: %o"), event.data);
|
||||
return;
|
||||
}
|
||||
super.handle_message(message);
|
||||
}
|
||||
|
||||
private on_error(event: MessageEvent) {
|
||||
log.warn(LogCategory.IPC, tr("Received error: %o"), event);
|
||||
}
|
||||
|
||||
send_message(type: string, data: any, target?: string) {
|
||||
const message: BroadcastMessage = {} as any;
|
||||
|
||||
message.sender = this.unique_id;
|
||||
message.receiver = target ? target : BasicIPCHandler.BROADCAST_UNIQUE_ID;
|
||||
message.timestamp = Date.now();
|
||||
message.type = type;
|
||||
message.data = data;
|
||||
|
||||
this.channel.postMessage(JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
let handler: BasicIPCHandler;
|
||||
export function setup() {
|
||||
if(!supported())
|
||||
return;
|
||||
/* TODO: test for support */
|
||||
handler = new BroadcastChannelIPC();
|
||||
handler.setup();
|
||||
}
|
||||
|
||||
export function get_handler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
export function supported() {
|
||||
/* ios does not support this */
|
||||
return typeof(window.BroadcastChannel) !== "undefined";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,596 @@
|
|||
/// <reference path="log.ts" />
|
||||
/// <reference path="voice/VoiceClient.ts" />
|
||||
/// <reference path="proto.ts" />
|
||||
/// <reference path="ui/view.ts" />
|
||||
/// <reference path="settings.ts" />
|
||||
/// <reference path="ui/frames/SelectedItemInfo.ts" />
|
||||
/// <reference path="FileManager.ts" />
|
||||
/// <reference path="permission/PermissionManager.ts" />
|
||||
/// <reference path="permission/GroupManager.ts" />
|
||||
/// <reference path="ui/frames/ControlBar.ts" />
|
||||
/// <reference path="connection/ConnectionBase.ts" />
|
||||
|
||||
enum DisconnectReason {
|
||||
HANDLER_DESTROYED,
|
||||
REQUESTED,
|
||||
CONNECT_FAILURE,
|
||||
CONNECTION_CLOSED,
|
||||
CONNECTION_FATAL_ERROR,
|
||||
CONNECTION_PING_TIMEOUT,
|
||||
CLIENT_KICKED,
|
||||
CLIENT_BANNED,
|
||||
HANDSHAKE_FAILED,
|
||||
SERVER_CLOSED,
|
||||
SERVER_REQUIRES_PASSWORD,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
enum ConnectionState {
|
||||
UNCONNECTED,
|
||||
CONNECTING,
|
||||
INITIALISING,
|
||||
CONNECTED,
|
||||
DISCONNECTING
|
||||
}
|
||||
|
||||
enum ViewReasonId {
|
||||
VREASON_USER_ACTION = 0,
|
||||
VREASON_MOVED = 1,
|
||||
VREASON_SYSTEM = 2,
|
||||
VREASON_TIMEOUT = 3,
|
||||
VREASON_CHANNEL_KICK = 4,
|
||||
VREASON_SERVER_KICK = 5,
|
||||
VREASON_BAN = 6,
|
||||
VREASON_SERVER_STOPPED = 7,
|
||||
VREASON_SERVER_LEFT = 8,
|
||||
VREASON_CHANNEL_UPDATED = 9,
|
||||
VREASON_EDITED = 10,
|
||||
VREASON_SERVER_SHUTDOWN = 11
|
||||
}
|
||||
|
||||
interface VoiceStatus {
|
||||
input_hardware: boolean;
|
||||
input_muted: boolean;
|
||||
output_muted: boolean;
|
||||
|
||||
channel_codec_encoding_supported: boolean;
|
||||
channel_codec_decoding_supported: boolean;
|
||||
sound_playback_supported: boolean;
|
||||
|
||||
sound_record_supported;
|
||||
|
||||
away: boolean | string;
|
||||
|
||||
channel_subscribe_all: boolean;
|
||||
queries_visible: boolean;
|
||||
}
|
||||
|
||||
class ConnectionHandler {
|
||||
channelTree: ChannelTree;
|
||||
|
||||
serverConnection: connection.ServerConnection;
|
||||
|
||||
fileManager: FileManager;
|
||||
|
||||
permissions: PermissionManager;
|
||||
groups: GroupManager;
|
||||
|
||||
select_info: InfoBar;
|
||||
chat: ChatBox;
|
||||
|
||||
settings: ServerSettings;
|
||||
sound: sound.SoundManager;
|
||||
|
||||
readonly tag_connection_handler: JQuery;
|
||||
|
||||
private _clientId: number = 0;
|
||||
private _local_client: LocalClientEntry;
|
||||
private _reconnect_timer: NodeJS.Timer;
|
||||
private _reconnect_attempt: boolean = false;
|
||||
|
||||
client_status: VoiceStatus = {
|
||||
input_hardware: false,
|
||||
input_muted: false,
|
||||
output_muted: false,
|
||||
away: false,
|
||||
channel_subscribe_all: true,
|
||||
queries_visible: false,
|
||||
|
||||
sound_playback_supported: undefined,
|
||||
sound_record_supported: undefined,
|
||||
channel_codec_encoding_supported: undefined,
|
||||
channel_codec_decoding_supported: undefined
|
||||
};
|
||||
|
||||
invoke_resized_on_activate: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this.settings = new ServerSettings();
|
||||
|
||||
this.select_info = new InfoBar(this);
|
||||
this.channelTree = new ChannelTree(this);
|
||||
this.chat = new ChatBox(this);
|
||||
this.sound = new sound.SoundManager(this);
|
||||
|
||||
this.serverConnection = new connection.ServerConnection(this);
|
||||
this.serverConnection.onconnectionstatechanged = this.on_connection_state_changed.bind(this);
|
||||
|
||||
this.fileManager = new FileManager(this);
|
||||
this.permissions = new PermissionManager(this);
|
||||
this.groups = new GroupManager(this);
|
||||
this._local_client = new LocalClientEntry(this);
|
||||
this.channelTree.registerClient(this._local_client);
|
||||
|
||||
//settings.static_global(Settings.KEY_DISABLE_VOICE, false)
|
||||
this.chat.initialize();
|
||||
this.tag_connection_handler = $.spawn("div").addClass("connection-container");
|
||||
$.spawn("div").addClass("server-icon icon client-server_green").appendTo(this.tag_connection_handler);
|
||||
$.spawn("div").addClass("server-name").text(tr("Not connected")).appendTo(this.tag_connection_handler);
|
||||
$.spawn("div").addClass("button-close icon client-tab_close_button").appendTo(this.tag_connection_handler);
|
||||
this.tag_connection_handler.on('click', event => {
|
||||
if(event.isDefaultPrevented())
|
||||
return;
|
||||
|
||||
server_connections.set_active_connection_handler(this);
|
||||
});
|
||||
this.tag_connection_handler.find(".button-close").on('click', event => {
|
||||
server_connections.destroy_server_connection_handler(this);
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
setup() { }
|
||||
|
||||
startConnection(addr: string, profile: profiles.ConnectionProfile, name?: string, password?: {password: string, hashed: boolean}) {
|
||||
this.tag_connection_handler.find(".server-name").text(tr("Connecting"));
|
||||
this.cancel_reconnect();
|
||||
this._reconnect_attempt = false;
|
||||
if(this.serverConnection)
|
||||
this.handleDisconnect(DisconnectReason.REQUESTED);
|
||||
|
||||
let idx = addr.lastIndexOf(':');
|
||||
|
||||
let port: number;
|
||||
let host: string;
|
||||
if(idx != -1) {
|
||||
port = parseInt(addr.substr(idx + 1));
|
||||
host = addr.substr(0, idx);
|
||||
} else {
|
||||
host = addr;
|
||||
port = 9987;
|
||||
}
|
||||
console.log(tr("Start connection to %s:%d"), host, port);
|
||||
this.channelTree.initialiseHead(addr, {host, port});
|
||||
|
||||
if(password && !password.hashed) {
|
||||
helpers.hashPassword(password.password).then(password => {
|
||||
/* errors will be already handled via the handle disconnect thing */
|
||||
this.serverConnection.connect({host, port}, new connection.HandshakeHandler(profile, name, password));
|
||||
}).catch(error => {
|
||||
createErrorModal(tr("Error while hashing password"), tr("Failed to hash server password!<br>") + error).open();
|
||||
})
|
||||
} else {
|
||||
/* errors will be already handled via the handle disconnect thing */
|
||||
this.serverConnection.connect({host, port}, new connection.HandshakeHandler(profile, name, password ? password.password : undefined));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getClient() : LocalClientEntry { return this._local_client; }
|
||||
getClientId() { return this._clientId; }
|
||||
|
||||
set clientId(id: number) {
|
||||
this._clientId = id;
|
||||
this._local_client["_clientId"] = id;
|
||||
}
|
||||
|
||||
get clientId() {
|
||||
return this._clientId;
|
||||
}
|
||||
|
||||
getServerConnection() : connection.ServerConnection { return this.serverConnection; }
|
||||
|
||||
|
||||
/**
|
||||
* LISTENER
|
||||
*/
|
||||
onConnected() {
|
||||
console.log("Client connected!");
|
||||
this.channelTree.registerClient(this._local_client);
|
||||
this.permissions.requestPermissionList();
|
||||
if(this.groups.serverGroups.length == 0)
|
||||
this.groups.requestGroups();
|
||||
|
||||
this.initialize_server_settings();
|
||||
|
||||
/* apply the server settings */
|
||||
if(this.client_status.channel_subscribe_all)
|
||||
this.channelTree.subscribe_all_channels();
|
||||
else
|
||||
this.channelTree.unsubscribe_all_channels();
|
||||
this.channelTree.toggle_server_queries(this.client_status.queries_visible);
|
||||
|
||||
this.sync_status_with_server();
|
||||
/*
|
||||
No need to update the voice stuff because as soon we see ourself we're doing it
|
||||
this.update_voice_status();
|
||||
if(control_bar.current_connection_handler() === this)
|
||||
control_bar.apply_server_voice_state();
|
||||
*/
|
||||
}
|
||||
|
||||
private initialize_server_settings() {
|
||||
let update_control = false;
|
||||
this.settings.setServer(this.channelTree.server);
|
||||
{
|
||||
const flag_subscribe = this.settings.server(Settings.KEY_CONTROL_CHANNEL_SUBSCRIBE_ALL, true);
|
||||
if(this.client_status.channel_subscribe_all != flag_subscribe) {
|
||||
this.client_status.channel_subscribe_all = flag_subscribe;
|
||||
update_control = true;
|
||||
}
|
||||
}
|
||||
{
|
||||
const flag_query = this.settings.server(Settings.KEY_CONTROL_SHOW_QUERIES, false);
|
||||
if(this.client_status.queries_visible != flag_query) {
|
||||
this.client_status.queries_visible = flag_query;
|
||||
update_control = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(update_control && server_connections.active_connection_handler() === this) {
|
||||
control_bar.apply_server_state();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
get connected() : boolean {
|
||||
return this.serverConnection && this.serverConnection.connected();
|
||||
}
|
||||
|
||||
private generate_ssl_certificate_accept() : JQuery {
|
||||
const properties = {
|
||||
connect_default: true,
|
||||
connect_profile: this.serverConnection._handshakeHandler.profile.id,
|
||||
connect_address: this.serverConnection._remote_address.host + (this.serverConnection._remote_address.port !== 9987 ? ":" + this.serverConnection._remote_address.port : "")
|
||||
};
|
||||
|
||||
const build_url = props => {
|
||||
const parameters: string[] = [];
|
||||
for(const key of Object.keys(props))
|
||||
parameters.push(key + "=" + encodeURIComponent(props[key]));
|
||||
|
||||
let callback = document.location.origin + document.location.pathname + document.location.search; /* don't use document.URL because it may contains a #! */
|
||||
if(document.location.search.length == 0)
|
||||
callback += "?" + parameters.join("&");
|
||||
else
|
||||
callback += "&" + parameters.join("&");
|
||||
|
||||
return "https://" + this.serverConnection._remote_address.host + ":" + this.serverConnection._remote_address.port + "/?forward_url=" + encodeURIComponent(callback);
|
||||
};
|
||||
|
||||
/* generate the tag */
|
||||
const tag = $.spawn("a").text(tr("here"));
|
||||
|
||||
if(bipc.supported()) {
|
||||
tag.attr('href', "#");
|
||||
let popup: Window;
|
||||
tag.on('click', event => {
|
||||
const features = {
|
||||
status: "no",
|
||||
location: "no",
|
||||
toolbar: "no",
|
||||
menubar: "no",
|
||||
width: 600,
|
||||
height: 400
|
||||
};
|
||||
|
||||
if(popup)
|
||||
popup.close();
|
||||
|
||||
properties["certificate_callback"] = bipc.get_handler().register_certificate_accept_callback(() => {
|
||||
log.info(LogCategory.GENERAL, tr("Received notification that the certificate has been accepted! Attempting reconnect!"));
|
||||
if(this._certificate_modal)
|
||||
this._certificate_modal.close();
|
||||
|
||||
popup.close(); /* no need, but nicer */
|
||||
this.startConnection(properties.connect_address, profiles.find_profile(properties.connect_profile) || profiles.default_profile());
|
||||
});
|
||||
|
||||
const url = build_url(properties);
|
||||
const features_string = [...Object.keys(features)].map(e => e + "=" + features[e]).reduce((a, b) => a + "," + b);
|
||||
popup = window.open(url, "TeaWeb certificate accept", features_string);
|
||||
try {
|
||||
popup.focus();
|
||||
} catch(e) {
|
||||
log.warn(LogCategory.GENERAL, tr("Certificate accept popup has been blocked. Trying a blank page and replacing href"));
|
||||
|
||||
window.open(url, "TeaWeb certificate accept"); /* trying without features */
|
||||
tag.attr("target", "_blank");
|
||||
tag.attr("href", url);
|
||||
tag.unbind('click');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
tag.attr('href', build_url(properties));
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
private _certificate_modal: Modal;
|
||||
handleDisconnect(type: DisconnectReason, data: any = {}) {
|
||||
this.tag_connection_handler.find(".server-name").text(tr("Not connected"));
|
||||
let auto_reconnect = false;
|
||||
switch (type) {
|
||||
case DisconnectReason.REQUESTED:
|
||||
break;
|
||||
case DisconnectReason.HANDLER_DESTROYED:
|
||||
if(data)
|
||||
this.sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||
break;
|
||||
case DisconnectReason.CONNECT_FAILURE:
|
||||
if(this._reconnect_attempt) {
|
||||
auto_reconnect = true;
|
||||
this.chat.serverChat().appendError(tr("Connect failed"));
|
||||
break;
|
||||
}
|
||||
console.error(tr("Could not connect to remote host! Exception: %o"), data);
|
||||
|
||||
if(native_client) {
|
||||
createErrorModal(
|
||||
tr("Could not connect"),
|
||||
tr("Could not connect to remote host (Connection refused)")
|
||||
).open();
|
||||
} else {
|
||||
const error_message_format =
|
||||
"Could not connect to remote host (Connection refused)\n" +
|
||||
"If you're sure that the remote host is up, than you may not allow unsigned certificates.\n" +
|
||||
"Click {0} to accept the remote certificate";
|
||||
|
||||
this._certificate_modal = createErrorModal(
|
||||
tr("Could not connect"),
|
||||
MessageHelper.formatMessage(tr(error_message_format), this.generate_ssl_certificate_accept())
|
||||
);
|
||||
this._certificate_modal.close_listener.push(() => this._certificate_modal = undefined);
|
||||
this._certificate_modal.open();
|
||||
}
|
||||
this.sound.play(Sound.CONNECTION_REFUSED);
|
||||
break;
|
||||
case DisconnectReason.HANDSHAKE_FAILED:
|
||||
//TODO sound
|
||||
console.error(tr("Failed to process handshake: %o"), data);
|
||||
createErrorModal(
|
||||
tr("Could not connect"),
|
||||
tr("Failed to process handshake: ") + data as string
|
||||
).open();
|
||||
break;
|
||||
case DisconnectReason.CONNECTION_CLOSED:
|
||||
console.error(tr("Lost connection to remote server!"));
|
||||
createErrorModal(
|
||||
tr("Connection closed"),
|
||||
tr("The connection was closed by remote host")
|
||||
).open();
|
||||
this.sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||
|
||||
auto_reconnect = true;
|
||||
break;
|
||||
case DisconnectReason.CONNECTION_PING_TIMEOUT:
|
||||
console.error(tr("Connection ping timeout"));
|
||||
this.sound.play(Sound.CONNECTION_DISCONNECTED_TIMEOUT);
|
||||
createErrorModal(
|
||||
tr("Connection lost"),
|
||||
tr("Lost connection to remote host (Ping timeout)<br>Even possible?")
|
||||
).open();
|
||||
|
||||
break;
|
||||
case DisconnectReason.SERVER_CLOSED:
|
||||
this.chat.serverChat().appendError(tr("Server closed ({0})"), data.reasonmsg);
|
||||
createErrorModal(
|
||||
tr("Server closed"),
|
||||
"The server is closed.<br>" + //TODO tr
|
||||
"Reason: " + data.reasonmsg
|
||||
).open();
|
||||
this.sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||
|
||||
auto_reconnect = true;
|
||||
break;
|
||||
case DisconnectReason.SERVER_REQUIRES_PASSWORD:
|
||||
this.chat.serverChat().appendError(tr("Server requires password"));
|
||||
createInputModal(tr("Server password"), tr("Enter server password:"), password => password.length != 0, password => {
|
||||
if(!(typeof password === "string")) return;
|
||||
this.startConnection(this.serverConnection._remote_address.host + ":" + this.serverConnection._remote_address.port,
|
||||
this.serverConnection._handshakeHandler.profile,
|
||||
this.serverConnection._handshakeHandler.name,
|
||||
{password: password as string, hashed: false});
|
||||
}).open();
|
||||
break;
|
||||
case DisconnectReason.CLIENT_KICKED:
|
||||
this.chat.serverChat().appendError(tr("You got kicked from the server by {0}{1}"),
|
||||
ClientEntry.chatTag(data["invokerid"], data["invokername"], data["invokeruid"]),
|
||||
data["reasonmsg"] ? " (" + data["reasonmsg"] + ")" : "");
|
||||
this.sound.play(Sound.SERVER_KICKED);
|
||||
auto_reconnect = true;
|
||||
break;
|
||||
case DisconnectReason.CLIENT_BANNED:
|
||||
this.chat.serverChat().appendError(tr("You got banned from the server by {0}{1}"),
|
||||
ClientEntry.chatTag(data["invokerid"], data["invokername"], data["invokeruid"]),
|
||||
data["reasonmsg"] ? " (" + data["reasonmsg"] + ")" : "");
|
||||
this.sound.play(Sound.CONNECTION_BANNED); //TODO findout if it was a disconnect or a connect refuse
|
||||
break;
|
||||
default:
|
||||
console.error(tr("Got uncaught disconnect!"));
|
||||
console.error(tr("Type: %o Data:"), type);
|
||||
console.error(data);
|
||||
break;
|
||||
}
|
||||
|
||||
this.channelTree.reset();
|
||||
if(this.serverConnection)
|
||||
this.serverConnection.disconnect();
|
||||
|
||||
if(control_bar.current_connection_handler() == this)
|
||||
control_bar.update_connection_state();
|
||||
this.select_info.setCurrentSelected(null);
|
||||
this.select_info.update_banner();
|
||||
|
||||
if(auto_reconnect) {
|
||||
if(!this.serverConnection) {
|
||||
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"));
|
||||
|
||||
console.log(tr("Allowed to auto reconnect. Reconnecting in 5000ms"));
|
||||
const server_address = this.serverConnection._remote_address;
|
||||
const profile = this.serverConnection._handshakeHandler.profile;
|
||||
const name = this.serverConnection._handshakeHandler.name;
|
||||
const password = this.serverConnection._handshakeHandler.server_password;
|
||||
|
||||
this._reconnect_timer = setTimeout(() => {
|
||||
this._reconnect_timer = undefined;
|
||||
this.chat.serverChat().appendMessage(tr("Reconnecting..."));
|
||||
log.info(LogCategory.NETWORKING, tr("Reconnecting..."))
|
||||
this.startConnection(server_address.host + ":" + server_address.port, profile, name, password ? { password: password, hashed: true} : undefined);
|
||||
this._reconnect_attempt = true;
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
cancel_reconnect() {
|
||||
if(this._reconnect_timer) {
|
||||
this.chat.serverChat().appendMessage(tr("Reconnect canceled"));
|
||||
clearTimeout(this._reconnect_timer);
|
||||
this._reconnect_timer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private on_connection_state_changed() {
|
||||
if(control_bar.current_connection_handler() == this)
|
||||
control_bar.update_connection_state();
|
||||
}
|
||||
|
||||
update_voice_status(targetChannel?: ChannelEntry) {
|
||||
targetChannel = targetChannel || this.getClient().currentChannel();
|
||||
|
||||
const vconnection = this.serverConnection.voice_connection();
|
||||
const basic_voice_support = this.serverConnection.support_voice() && vconnection.connected();
|
||||
const support_record = basic_voice_support && (!targetChannel || vconnection.encoding_supported(targetChannel.properties.channel_codec));
|
||||
const support_playback = basic_voice_support && (!targetChannel || vconnection.decoding_supported(targetChannel.properties.channel_codec));
|
||||
|
||||
const property_update = {
|
||||
client_input_muted: this.client_status.input_muted,
|
||||
client_output_muted: this.client_status.output_muted
|
||||
};
|
||||
|
||||
if(!this.serverConnection.support_voice() || !vconnection.connected()) {
|
||||
property_update["client_input_hardware"] = false;
|
||||
property_update["client_output_hardware"] = false;
|
||||
this.client_status.input_hardware = true; /* IDK if we have input hardware or not, but it dosn't matter at all so */
|
||||
} else {
|
||||
const audio_source = vconnection.voice_recorder();
|
||||
const recording_supported = typeof(audio_source) !== "undefined" && audio_source.is_recording_supported() && (!targetChannel || vconnection.encoding_supported(targetChannel.properties.channel_codec));
|
||||
const playback_supported = !targetChannel || vconnection.decoding_supported(targetChannel.properties.channel_codec);
|
||||
|
||||
property_update["client_input_hardware"] = recording_supported;
|
||||
property_update["client_output_hardware"] = playback_supported;
|
||||
this.client_status.input_hardware = recording_supported;
|
||||
}
|
||||
|
||||
if(this.serverConnection && this.serverConnection.connected()) {
|
||||
const client_properties = this.getClient().properties;
|
||||
for(const key of Object.keys(property_update)) {
|
||||
if(client_properties[key] === property_update[key])
|
||||
delete property_update[key];
|
||||
}
|
||||
|
||||
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."));
|
||||
|
||||
/* Update these properties anyways (for case the server fails to handle the command) */
|
||||
const updates = [];
|
||||
for(const key of Object.keys(property_update))
|
||||
updates.push({key: key, value: (property_update[key]) + ""});
|
||||
this.getClient().updateVariables(...updates);
|
||||
});
|
||||
}
|
||||
} else { /* no icons are shown so no update at all */ }
|
||||
|
||||
|
||||
if(targetChannel && (!vconnection || vconnection.connected())) {
|
||||
const encoding_supported = vconnection.encoding_supported(targetChannel.properties.channel_codec);
|
||||
const decoding_supported = vconnection.decoding_supported(targetChannel.properties.channel_codec);
|
||||
|
||||
if(this.client_status.channel_codec_decoding_supported !== decoding_supported || this.client_status.channel_codec_encoding_supported !== encoding_supported) {
|
||||
this.client_status.channel_codec_decoding_supported = decoding_supported;
|
||||
this.client_status.channel_codec_encoding_supported = encoding_supported;
|
||||
|
||||
let message;
|
||||
if(!encoding_supported && !decoding_supported)
|
||||
message = tr("This channel has an unsupported codec.<br>You cant speak or listen to anybody within this channel!");
|
||||
else if(!encoding_supported)
|
||||
message = tr("This channel has an unsupported codec.<br>You cant speak within this channel!");
|
||||
else if(!decoding_supported)
|
||||
message = tr("This channel has an unsupported codec.<br>You listen to anybody within this channel!"); /* implies speaking does not work as well */
|
||||
if(message)
|
||||
createErrorModal(tr("Channel codec unsupported"), message).open();
|
||||
}
|
||||
}
|
||||
|
||||
this.client_status = this.client_status || {} as any;
|
||||
this.client_status.sound_record_supported = support_record;
|
||||
this.client_status.sound_playback_supported = support_playback;
|
||||
|
||||
if(vconnection && vconnection.voice_recorder() && vconnection.voice_recorder().is_recording_supported())
|
||||
vconnection.voice_recorder().set_recording(!this.client_status.input_muted && !this.client_status.output_muted);
|
||||
|
||||
if(control_bar.current_connection_handler() === this)
|
||||
control_bar.apply_server_voice_state();
|
||||
}
|
||||
|
||||
sync_status_with_server() {
|
||||
if(this.serverConnection.connected())
|
||||
this.serverConnection.send_command("clientupdate", {
|
||||
client_input_muted: this.client_status.input_muted,
|
||||
client_output_muted: this.client_status.output_muted,
|
||||
client_away: typeof(this.client_status.away) === "string" || this.client_status.away,
|
||||
client_away_message: typeof(this.client_status.away) === "string" ? this.client_status.away : "",
|
||||
client_input_hardware: this.client_status.sound_record_supported && this.client_status.input_hardware,
|
||||
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."));
|
||||
});
|
||||
}
|
||||
|
||||
set_away_status(state: boolean | string) {
|
||||
if(this.client_status.away === state)
|
||||
return;
|
||||
|
||||
this.client_status.away = state;
|
||||
this.serverConnection.send_command("clientupdate", {
|
||||
client_away: typeof(this.client_status.away) === "string" || this.client_status.away,
|
||||
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."));
|
||||
});
|
||||
|
||||
control_bar.update_button_away();
|
||||
}
|
||||
|
||||
resize_elements() {
|
||||
this.channelTree.handle_resized();
|
||||
this.select_info.handle_resize();
|
||||
this.invoke_resized_on_activate = false;
|
||||
}
|
||||
|
||||
acquire_recorder(voice_recoder: VoiceRecorder, update_control_bar: boolean) {
|
||||
const vconnection = this.serverConnection.voice_connection();
|
||||
if(vconnection)
|
||||
vconnection.acquire_voice_recorder(voice_recoder);
|
||||
if(voice_recoder)
|
||||
voice_recoder.clean_recording_supported();
|
||||
this.update_voice_status(undefined);
|
||||
}
|
||||
}
|
|
@ -1,11 +1,6 @@
|
|||
/// <reference path="client.ts" />
|
||||
/// <reference path="connection/CommandHandler.ts" />
|
||||
/// <reference path="connection/ConnectionBase.ts" />
|
||||
|
||||
/*
|
||||
FIXME: Dont use item storage with base64! Use the larger cache API and drop IE support!
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage#Browser_compatibility
|
||||
*/
|
||||
|
||||
class FileEntry {
|
||||
name: string;
|
||||
datetime: number;
|
||||
|
@ -218,7 +213,7 @@ class RequestFileUpload {
|
|||
}
|
||||
|
||||
class FileManager extends connection.AbstractCommandHandler {
|
||||
handle: TSClient;
|
||||
handle: ConnectionHandler;
|
||||
icons: IconManager;
|
||||
avatars: AvatarManager;
|
||||
|
||||
|
@ -228,7 +223,7 @@ class FileManager extends connection.AbstractCommandHandler {
|
|||
|
||||
private transfer_counter : number = 0;
|
||||
|
||||
constructor(client: TSClient) {
|
||||
constructor(client: ConnectionHandler) {
|
||||
super(client.serverConnection);
|
||||
|
||||
this.handle = client;
|
||||
|
@ -533,14 +528,17 @@ class CacheManager {
|
|||
}
|
||||
|
||||
class IconManager {
|
||||
private static cache: CacheManager;
|
||||
|
||||
handle: FileManager;
|
||||
private cache: CacheManager;
|
||||
private _id_urls: {[id:number]:string} = {};
|
||||
private _loading_promises: {[id:number]:Promise<Icon>} = {};
|
||||
|
||||
constructor(handle: FileManager) {
|
||||
this.handle = handle;
|
||||
this.cache = new CacheManager("icons");
|
||||
|
||||
if(!IconManager.cache)
|
||||
IconManager.cache = new CacheManager("icons");
|
||||
}
|
||||
|
||||
iconList() : Promise<FileEntry[]> {
|
||||
|
@ -572,10 +570,10 @@ class IconManager {
|
|||
url: this._id_urls[id]
|
||||
};
|
||||
|
||||
if(!this.cache.setupped())
|
||||
await this.cache.setup();
|
||||
if(!IconManager.cache.setupped())
|
||||
await IconManager.cache.setup();
|
||||
|
||||
const response = await this.cache.resolve_cached('icon_' + id); //TODO age!
|
||||
const response = await IconManager.cache.resolve_cached('icon_' + id); //TODO age!
|
||||
if(response)
|
||||
return {
|
||||
id: id,
|
||||
|
@ -606,7 +604,7 @@ class IconManager {
|
|||
const type = image_type(response.headers.get('X-media-bytes'));
|
||||
const media = media_image_type(type);
|
||||
|
||||
await this.cache.put_cache('icon_' + id, response.clone(), "image/" + media);
|
||||
await IconManager.cache.put_cache('icon_' + id, response.clone(), "image/" + media);
|
||||
const url = (this._id_urls[id] = await this._response_url(response.clone()));
|
||||
|
||||
this._loading_promises[id] = undefined;
|
||||
|
@ -694,14 +692,15 @@ class Avatar {
|
|||
class AvatarManager {
|
||||
handle: FileManager;
|
||||
|
||||
private cache: CacheManager;
|
||||
private static cache: CacheManager;
|
||||
private _cached_avatars: {[response_avatar_id:number]:Avatar} = {};
|
||||
private _loading_promises: {[response_avatar_id:number]:Promise<Icon>} = {};
|
||||
|
||||
constructor(handle: FileManager) {
|
||||
this.handle = handle;
|
||||
|
||||
this.cache = new CacheManager("avatars");
|
||||
if(!AvatarManager.cache)
|
||||
AvatarManager.cache = new CacheManager("avatars");
|
||||
}
|
||||
|
||||
private async _response_url(response: Response, type: ImageType) : Promise<string> {
|
||||
|
@ -724,10 +723,10 @@ class AvatarManager {
|
|||
this._cached_avatars[avatar_id] = (avatar = undefined);
|
||||
}
|
||||
|
||||
if(!this.cache.setupped())
|
||||
await this.cache.setup();
|
||||
if(!AvatarManager.cache.setupped())
|
||||
await AvatarManager.cache.setup();
|
||||
|
||||
const response = await this.cache.resolve_cached('avatar_' + client_avatar_id); //TODO age!
|
||||
const response = await AvatarManager.cache.resolve_cached('avatar_' + client_avatar_id); //TODO age!
|
||||
if(!response)
|
||||
return undefined;
|
||||
|
||||
|
@ -770,7 +769,7 @@ class AvatarManager {
|
|||
const type = image_type(response.headers.get('X-media-bytes'));
|
||||
const media = media_image_type(type);
|
||||
|
||||
await this.cache.put_cache('avatar_' + client_avatar_id, response.clone(), "image/" + media, {
|
||||
await AvatarManager.cache.put_cache('avatar_' + client_avatar_id, response.clone(), "image/" + media, {
|
||||
"X-avatar-id": avatar_id
|
||||
});
|
||||
const url = await this._response_url(response.clone(), type);
|
||||
|
|
|
@ -1,315 +0,0 @@
|
|||
/// <reference path="log.ts" />
|
||||
/// <reference path="voice/AudioController.ts" />
|
||||
/// <reference path="proto.ts" />
|
||||
/// <reference path="ui/view.ts" />
|
||||
/// <reference path="settings.ts" />
|
||||
/// <reference path="ui/frames/SelectedItemInfo.ts" />
|
||||
/// <reference path="FileManager.ts" />
|
||||
/// <reference path="permission/PermissionManager.ts" />
|
||||
/// <reference path="permission/GroupManager.ts" />
|
||||
/// <reference path="ui/frames/ControlBar.ts" />
|
||||
/// <reference path="connection/ConnectionBase.ts" />
|
||||
|
||||
enum DisconnectReason {
|
||||
REQUESTED,
|
||||
CONNECT_FAILURE,
|
||||
CONNECTION_CLOSED,
|
||||
CONNECTION_FATAL_ERROR,
|
||||
CONNECTION_PING_TIMEOUT,
|
||||
CLIENT_KICKED,
|
||||
CLIENT_BANNED,
|
||||
HANDSHAKE_FAILED,
|
||||
SERVER_CLOSED,
|
||||
SERVER_REQUIRES_PASSWORD,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
enum ConnectionState {
|
||||
UNCONNECTED,
|
||||
CONNECTING,
|
||||
INITIALISING,
|
||||
CONNECTED,
|
||||
DISCONNECTING
|
||||
}
|
||||
|
||||
enum ViewReasonId {
|
||||
VREASON_USER_ACTION = 0,
|
||||
VREASON_MOVED = 1,
|
||||
VREASON_SYSTEM = 2,
|
||||
VREASON_TIMEOUT = 3,
|
||||
VREASON_CHANNEL_KICK = 4,
|
||||
VREASON_SERVER_KICK = 5,
|
||||
VREASON_BAN = 6,
|
||||
VREASON_SERVER_STOPPED = 7,
|
||||
VREASON_SERVER_LEFT = 8,
|
||||
VREASON_CHANNEL_UPDATED = 9,
|
||||
VREASON_EDITED = 10,
|
||||
VREASON_SERVER_SHUTDOWN = 11
|
||||
}
|
||||
|
||||
class TSClient {
|
||||
channelTree: ChannelTree;
|
||||
serverConnection: connection.ServerConnection;
|
||||
voiceConnection: VoiceConnection | undefined;
|
||||
fileManager: FileManager;
|
||||
selectInfo: InfoBar;
|
||||
permissions: PermissionManager;
|
||||
groups: GroupManager;
|
||||
controlBar: ControlBar;
|
||||
|
||||
private _clientId: number = 0;
|
||||
private _ownEntry: LocalClientEntry;
|
||||
private _reconnect_timer: NodeJS.Timer;
|
||||
private _reconnect_attempt: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this.selectInfo = new InfoBar(this, $("#select_info"));
|
||||
this.channelTree = new ChannelTree(this, $("#channelTree"));
|
||||
this.serverConnection = new connection.ServerConnection(this);
|
||||
this.fileManager = new FileManager(this);
|
||||
this.permissions = new PermissionManager(this);
|
||||
this.groups = new GroupManager(this);
|
||||
this._ownEntry = new LocalClientEntry(this);
|
||||
this.controlBar = new ControlBar(this, $("#control_bar"));
|
||||
this.channelTree.registerClient(this._ownEntry);
|
||||
|
||||
if(!settings.static_global(Settings.KEY_DISABLE_VOICE, false))
|
||||
this.voiceConnection = new VoiceConnection(this);
|
||||
}
|
||||
|
||||
setup() {
|
||||
this.controlBar.initialise();
|
||||
}
|
||||
|
||||
startConnection(addr: string, profile: profiles.ConnectionProfile, name?: string, password?: {password: string, hashed: boolean}) {
|
||||
this.cancel_reconnect();
|
||||
this._reconnect_attempt = false;
|
||||
if(this.serverConnection)
|
||||
this.handleDisconnect(DisconnectReason.REQUESTED);
|
||||
|
||||
let idx = addr.lastIndexOf(':');
|
||||
|
||||
let port: number;
|
||||
let host: string;
|
||||
if(idx != -1) {
|
||||
port = parseInt(addr.substr(idx + 1));
|
||||
host = addr.substr(0, idx);
|
||||
} else {
|
||||
host = addr;
|
||||
port = 9987;
|
||||
}
|
||||
console.log(tr("Start connection to %s:%d"), host, port);
|
||||
this.channelTree.initialiseHead(addr, {host, port});
|
||||
|
||||
if(password && !password.hashed) {
|
||||
helpers.hashPassword(password.password).then(password => {
|
||||
/* errors will be already handled via the handle disconnect thing */
|
||||
this.serverConnection.connect({host, port}, new connection.HandshakeHandler(profile, name, password));
|
||||
}).catch(error => {
|
||||
createErrorModal(tr("Error while hashing password"), tr("Failed to hash server password!<br>") + error).open();
|
||||
})
|
||||
} else {
|
||||
/* errors will be already handled via the handle disconnect thing */
|
||||
this.serverConnection.connect({host, port}, new connection.HandshakeHandler(profile, name, password ? password.password : undefined));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getClient() : LocalClientEntry { return this._ownEntry; }
|
||||
getClientId() { return this._clientId; }
|
||||
|
||||
set clientId(id: number) {
|
||||
this._clientId = id;
|
||||
this._ownEntry["_clientId"] = id;
|
||||
}
|
||||
|
||||
get clientId() {
|
||||
return this._clientId;
|
||||
}
|
||||
|
||||
getServerConnection() : connection.ServerConnection { return this.serverConnection; }
|
||||
|
||||
|
||||
/**
|
||||
* LISTENER
|
||||
*/
|
||||
onConnected() {
|
||||
console.log("Client connected!");
|
||||
this.channelTree.registerClient(this._ownEntry);
|
||||
settings.setServer(this.channelTree.server);
|
||||
this.permissions.requestPermissionList();
|
||||
if(this.groups.serverGroups.length == 0)
|
||||
this.groups.requestGroups();
|
||||
this.controlBar.updateProperties();
|
||||
if(this.controlBar.channel_subscribe_all)
|
||||
this.channelTree.subscribe_all_channels();
|
||||
else
|
||||
this.channelTree.unsubscribe_all_channels();
|
||||
if(this.voiceConnection && !this.voiceConnection.current_encoding_supported())
|
||||
createErrorModal(tr("Codec encode type not supported!"), tr("Codec encode type " + VoiceConnectionType[this.voiceConnection.type] + " not supported by this browser!<br>Choose another one!")).open(); //TODO tr
|
||||
}
|
||||
|
||||
get connected() : boolean {
|
||||
return this.serverConnection && this.serverConnection.connected();
|
||||
}
|
||||
|
||||
private certAcceptUrl() {
|
||||
const properties = {
|
||||
connect_default: true,
|
||||
connect_profile: this.serverConnection._handshakeHandler.profile.id,
|
||||
connect_address: this.serverConnection._remote_address.host + ":" + this.serverConnection._remote_address.port
|
||||
};
|
||||
|
||||
|
||||
const parameters: string[] = [];
|
||||
for(const key in properties)
|
||||
parameters.push(key + "=" + encodeURIComponent(properties[key]));
|
||||
|
||||
|
||||
|
||||
// document.URL
|
||||
let callback = document.URL;
|
||||
if(document.location.search.length == 0)
|
||||
callback += "?" + parameters.join("&");
|
||||
else
|
||||
callback += "&" + parameters.join("&");
|
||||
|
||||
return "https://" + this.serverConnection._remote_address.host + ":" + this.serverConnection._remote_address.port + "/?forward_url=" + encodeURIComponent(callback);
|
||||
}
|
||||
|
||||
handleDisconnect(type: DisconnectReason, data: any = {}) {
|
||||
let auto_reconnect = false;
|
||||
switch (type) {
|
||||
case DisconnectReason.REQUESTED:
|
||||
break;
|
||||
case DisconnectReason.CONNECT_FAILURE:
|
||||
if(this._reconnect_attempt) {
|
||||
auto_reconnect = true;
|
||||
chat.serverChat().appendError(tr("Connect failed"));
|
||||
break;
|
||||
}
|
||||
console.error(tr("Could not connect to remote host! Exception: %o"), data);
|
||||
|
||||
if(native_client) {
|
||||
createErrorModal(
|
||||
tr("Could not connect"),
|
||||
tr("Could not connect to remote host (Connection refused)")
|
||||
).open();
|
||||
} else {
|
||||
//TODO tr
|
||||
createErrorModal(
|
||||
tr("Could not connect"),
|
||||
"Could not connect to remote host (Connection refused)<br>" +
|
||||
"If you're sure that the remote host is up, than you may not allow unsigned certificates.<br>" +
|
||||
"Click <a href='" + this.certAcceptUrl() + "'>here</a> to accept the remote certificate"
|
||||
).open();
|
||||
}
|
||||
sound.play(Sound.CONNECTION_REFUSED);
|
||||
break;
|
||||
case DisconnectReason.HANDSHAKE_FAILED:
|
||||
//TODO sound
|
||||
console.error(tr("Failed to process handshake: %o"), data);
|
||||
createErrorModal(
|
||||
tr("Could not connect"),
|
||||
tr("Failed to process handshake: ") + data as string
|
||||
).open();
|
||||
break;
|
||||
case DisconnectReason.CONNECTION_CLOSED:
|
||||
console.error(tr("Lost connection to remote server!"));
|
||||
createErrorModal(
|
||||
tr("Connection closed"),
|
||||
tr("The connection was closed by remote host")
|
||||
).open();
|
||||
sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||
|
||||
auto_reconnect = true;
|
||||
break;
|
||||
case DisconnectReason.CONNECTION_PING_TIMEOUT:
|
||||
console.error(tr("Connection ping timeout"));
|
||||
sound.play(Sound.CONNECTION_DISCONNECTED_TIMEOUT);
|
||||
createErrorModal(
|
||||
tr("Connection lost"),
|
||||
tr("Lost connection to remote host (Ping timeout)<br>Even possible?")
|
||||
).open();
|
||||
|
||||
break;
|
||||
case DisconnectReason.SERVER_CLOSED:
|
||||
chat.serverChat().appendError(tr("Server closed ({0})"), data.reasonmsg);
|
||||
createErrorModal(
|
||||
tr("Server closed"),
|
||||
"The server is closed.<br>" + //TODO tr
|
||||
"Reason: " + data.reasonmsg
|
||||
).open();
|
||||
sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||
|
||||
auto_reconnect = true;
|
||||
break;
|
||||
case DisconnectReason.SERVER_REQUIRES_PASSWORD:
|
||||
chat.serverChat().appendError(tr("Server requires password"));
|
||||
createInputModal(tr("Server password"), tr("Enter server password:"), password => password.length != 0, password => {
|
||||
if(!(typeof password === "string")) return;
|
||||
this.startConnection(this.serverConnection._remote_address.host + ":" + this.serverConnection._remote_address.port,
|
||||
this.serverConnection._handshakeHandler.profile,
|
||||
this.serverConnection._handshakeHandler.name,
|
||||
{password: password as string, hashed: false});
|
||||
}).open();
|
||||
break;
|
||||
case DisconnectReason.CLIENT_KICKED:
|
||||
chat.serverChat().appendError(tr("You got kicked from the server by {0}{1}"),
|
||||
ClientEntry.chatTag(data["invokerid"], data["invokername"], data["invokeruid"]),
|
||||
data["reasonmsg"] ? " (" + data["reasonmsg"] + ")" : "");
|
||||
sound.play(Sound.SERVER_KICKED);
|
||||
auto_reconnect = true;
|
||||
break;
|
||||
case DisconnectReason.CLIENT_BANNED:
|
||||
chat.serverChat().appendError(tr("You got banned from the server by {0}{1}"),
|
||||
ClientEntry.chatTag(data["invokerid"], data["invokername"], data["invokeruid"]),
|
||||
data["reasonmsg"] ? " (" + data["reasonmsg"] + ")" : "");
|
||||
sound.play(Sound.CONNECTION_BANNED); //TODO findout if it was a disconnect or a connect refuse
|
||||
break;
|
||||
default:
|
||||
console.error(tr("Got uncaught disconnect!"));
|
||||
console.error(tr("Type: %o Data:"), type);
|
||||
console.error(data);
|
||||
break;
|
||||
}
|
||||
|
||||
this.channelTree.reset();
|
||||
if(this.voiceConnection)
|
||||
this.voiceConnection.dropSession();
|
||||
if(this.serverConnection) this.serverConnection.disconnect();
|
||||
this.controlBar.update_connection_state();
|
||||
this.selectInfo.setCurrentSelected(null);
|
||||
this.selectInfo.update_banner();
|
||||
|
||||
if(auto_reconnect) {
|
||||
if(!this.serverConnection) {
|
||||
console.log(tr("Allowed to auto reconnect but cant reconnect because we dont have any information left..."));
|
||||
return;
|
||||
}
|
||||
chat.serverChat().appendMessage(tr("Reconnecting in 5 seconds"));
|
||||
|
||||
console.log(tr("Allowed to auto reconnect. Reconnecting in 5000ms"));
|
||||
const server_address = this.serverConnection._remote_address;
|
||||
const profile = this.serverConnection._handshakeHandler.profile;
|
||||
const name = this.serverConnection._handshakeHandler.name;
|
||||
const password = this.serverConnection._handshakeHandler.server_password;
|
||||
|
||||
this._reconnect_timer = setTimeout(() => {
|
||||
this._reconnect_timer = undefined;
|
||||
chat.serverChat().appendMessage(tr("Reconnecting..."));
|
||||
log.info(LogCategory.NETWORKING, tr("Reconnecting..."))
|
||||
this.startConnection(server_address.host + ":" + server_address.port, profile, name, password ? { password: password, hashed: true} : undefined);
|
||||
this._reconnect_attempt = true;
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
cancel_reconnect() {
|
||||
if(this._reconnect_timer) {
|
||||
chat.serverChat().appendMessage(tr("Reconnect canceled"));
|
||||
clearTimeout(this._reconnect_timer);
|
||||
this._reconnect_timer = undefined;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@ class BufferChunk {
|
|||
}
|
||||
|
||||
class CodecClientCache {
|
||||
_last_access: number;
|
||||
_chunks: BufferChunk[] = [];
|
||||
|
||||
bufferedSamples(max: number = 0) : number {
|
||||
|
|
|
@ -173,7 +173,7 @@ class CodecWrapperWorker extends BasicCodec {
|
|||
this._workerCallbackResolve = undefined;
|
||||
return;
|
||||
} else if(message["type"] == "chatmessage_server") {
|
||||
chat.serverChat().appendMessage(message["message"]);
|
||||
//FIXME?
|
||||
return;
|
||||
}
|
||||
console.log(tr("Costume callback! (%o)"), message);
|
||||
|
|
|
@ -8,9 +8,11 @@ namespace connection {
|
|||
|
||||
export class ConnectionCommandHandler extends AbstractCommandHandler {
|
||||
readonly connection: ServerConnection;
|
||||
readonly connection_handler: ConnectionHandler;
|
||||
|
||||
constructor(connection: ServerConnection) {
|
||||
super(connection);
|
||||
this.connection_handler = connection.client;
|
||||
|
||||
this["error"] = this.handleCommandResult;
|
||||
this["channellist"] = this.handleCommandChannelList;
|
||||
|
@ -86,9 +88,14 @@ namespace connection {
|
|||
|
||||
handleCommandServerInit(json){
|
||||
//We could setup the voice channel
|
||||
if( this.connection.client.voiceConnection) {
|
||||
if(this.connection.support_voice()) {
|
||||
console.log(tr("Setting up voice"));
|
||||
this.connection.client.voiceConnection.createSession();
|
||||
|
||||
const connection = this.connection.voice_connection();
|
||||
if(connection instanceof audio.js.VoiceConnection)
|
||||
connection.createSession();
|
||||
else
|
||||
throw "unknown voice connection";
|
||||
} else {
|
||||
console.log(tr("Skipping voice setup (No voice bridge available)"));
|
||||
}
|
||||
|
@ -112,10 +119,10 @@ namespace connection {
|
|||
this.connection.client.channelTree.server.updateVariables(false, ...updates);
|
||||
|
||||
|
||||
chat.serverChat().name = this.connection.client.channelTree.server.properties["virtualserver_name"];
|
||||
chat.serverChat().appendMessage(tr("Connected as {0}"), true, this.connection.client.getClient().createChatTag(true));
|
||||
sound.play(Sound.CONNECTION_CONNECTED);
|
||||
globalClient.onConnected();
|
||||
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.sound.play(Sound.CONNECTION_CONNECTED);
|
||||
this.connection.client.onConnected();
|
||||
}
|
||||
|
||||
private createChannelFromJson(json, ignoreOrder: boolean = false) {
|
||||
|
@ -233,28 +240,28 @@ namespace connection {
|
|||
client = tree.insertClient(client, channel);
|
||||
} else {
|
||||
if(client == this.connection.client.getClient())
|
||||
chat.channelChat().name = channel.channelName();
|
||||
this.connection_handler.chat.channelChat().name = channel.channelName();
|
||||
tree.moveClient(client, channel);
|
||||
}
|
||||
|
||||
if(this.connection.client.controlBar.query_visible || client.properties.client_type != ClientType.CLIENT_QUERY) {
|
||||
if(this.connection_handler.client_status.queries_visible || client.properties.client_type != ClientType.CLIENT_QUERY) {
|
||||
const own_channel = this.connection.client.getClient().currentChannel();
|
||||
if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
|
||||
if(own_channel == channel)
|
||||
if(old_channel)
|
||||
sound.play(Sound.USER_ENTERED);
|
||||
this.connection_handler.sound.play(Sound.USER_ENTERED);
|
||||
else
|
||||
sound.play(Sound.USER_ENTERED_CONNECT);
|
||||
this.connection_handler.sound.play(Sound.USER_ENTERED_CONNECT);
|
||||
if(old_channel) {
|
||||
chat.serverChat().appendMessage(tr("{0} appeared from {1} to {2}"), true, client.createChatTag(true), old_channel.generate_tag(true), channel.generate_tag(true));
|
||||
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 {
|
||||
chat.serverChat().appendMessage(tr("{0} connected to channel {1}"), true, client.createChatTag(true), channel.generate_tag(true));
|
||||
this.connection_handler.chat.serverChat().appendMessage(tr("{0} connected to channel {1}"), true, client.createChatTag(true), channel.generate_tag(true));
|
||||
}
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
|
||||
if(own_channel == channel)
|
||||
sound.play(Sound.USER_ENTERED_MOVED);
|
||||
this.connection_handler.sound.play(Sound.USER_ENTERED_MOVED);
|
||||
|
||||
chat.serverChat().appendMessage(tr("{0} appeared from {1} to {2}, moved by {3}"), true,
|
||||
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),
|
||||
|
@ -262,9 +269,9 @@ namespace connection {
|
|||
);
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
|
||||
if(own_channel == channel)
|
||||
sound.play(Sound.USER_ENTERED_KICKED);
|
||||
this.connection_handler.sound.play(Sound.USER_ENTERED_KICKED);
|
||||
|
||||
chat.serverChat().appendMessage(tr("{0} appeared from {1} to {2}, kicked by {3}{4}"), true,
|
||||
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),
|
||||
|
@ -297,7 +304,7 @@ namespace connection {
|
|||
{
|
||||
let client_chat = client.chat(false);
|
||||
if(!client_chat) {
|
||||
for(const c of chat.open_chats()) {
|
||||
for(const c of this.connection_handler.chat.open_chats()) {
|
||||
if(c.owner_unique_id == client.properties.client_unique_identifier && c.flag_offline) {
|
||||
client_chat = c;
|
||||
break;
|
||||
|
@ -314,8 +321,10 @@ namespace connection {
|
|||
client_chat.flag_offline = false;
|
||||
}
|
||||
}
|
||||
if(client instanceof LocalClientEntry)
|
||||
this.connection.client.controlBar.updateVoice();
|
||||
|
||||
if(client instanceof LocalClientEntry) {
|
||||
this.connection_handler.update_voice_status();
|
||||
}
|
||||
}
|
||||
|
||||
handleCommandClientLeftView(json) {
|
||||
|
@ -340,48 +349,49 @@ namespace connection {
|
|||
return;
|
||||
}
|
||||
|
||||
if(this.connection.client.controlBar.query_visible || client.properties.client_type != ClientType.CLIENT_QUERY) {
|
||||
|
||||
if(this.connection_handler.client_status.queries_visible || client.properties.client_type != ClientType.CLIENT_QUERY) {
|
||||
const own_channel = this.connection.client.getClient().currentChannel();
|
||||
let channel_from = tree.findChannel(json["cfid"]);
|
||||
let channel_to = tree.findChannel(json["ctid"]);
|
||||
|
||||
if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
|
||||
chat.serverChat().appendMessage(tr("{0} disappeared from {1} to {2}"), true, client.createChatTag(true), channel_from.generate_tag(true), channel_to.generate_tag(true));
|
||||
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)
|
||||
sound.play(Sound.USER_LEFT);
|
||||
this.connection_handler.sound.play(Sound.USER_LEFT);
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_LEFT) {
|
||||
chat.serverChat().appendMessage(tr("{0} left the server{1}"), true,
|
||||
this.connection_handler.chat.serverChat().appendMessage(tr("{0} left the server{1}"), true,
|
||||
client.createChatTag(true),
|
||||
json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
|
||||
);
|
||||
|
||||
if(channel_from == own_channel)
|
||||
sound.play(Sound.USER_LEFT_DISCONNECT);
|
||||
this.connection_handler.sound.play(Sound.USER_LEFT_DISCONNECT);
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_KICK) {
|
||||
chat.serverChat().appendError(tr("{0} was kicked from the server by {1}.{2}"),
|
||||
this.connection_handler.chat.serverChat().appendError(tr("{0} was kicked from the server by {1}.{2}"),
|
||||
client.createChatTag(true),
|
||||
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
|
||||
json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
|
||||
);
|
||||
if(channel_from == own_channel)
|
||||
sound.play(Sound.USER_LEFT_KICKED_SERVER);
|
||||
this.connection_handler.sound.play(Sound.USER_LEFT_KICKED_SERVER);
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
|
||||
chat.serverChat().appendError(tr("{0} was kicked from your channel by {1}.{2}"),
|
||||
this.connection_handler.chat.serverChat().appendError(tr("{0} was kicked from your channel by {1}.{2}"),
|
||||
client.createChatTag(true),
|
||||
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
|
||||
json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
|
||||
);
|
||||
|
||||
if(channel_from == own_channel)
|
||||
sound.play(Sound.USER_LEFT_KICKED_CHANNEL);
|
||||
this.connection_handler.sound.play(Sound.USER_LEFT_KICKED_CHANNEL);
|
||||
} else if(json["reasonid"] == 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(json["bantime"])
|
||||
duration = "for " + formatDate(Number.parseInt(json["bantime"]));
|
||||
|
||||
chat.serverChat().appendError(tr("{0} was banned {1} by {2}.{3}"),
|
||||
this.connection_handler.chat.serverChat().appendError(tr("{0} was banned {1} by {2}.{3}"),
|
||||
client.createChatTag(true),
|
||||
duration,
|
||||
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
|
||||
|
@ -389,7 +399,7 @@ namespace connection {
|
|||
);
|
||||
|
||||
if(channel_from == own_channel)
|
||||
sound.play(Sound.USER_LEFT_BANNED);
|
||||
this.connection_handler.sound.play(Sound.USER_LEFT_BANNED);
|
||||
} else {
|
||||
console.error(tr("Unknown client left reason!"));
|
||||
}
|
||||
|
@ -431,44 +441,45 @@ namespace connection {
|
|||
console.error(tr("Unknown client move (Channel from)!"));
|
||||
|
||||
let self = client instanceof LocalClientEntry;
|
||||
let current_clients;
|
||||
let current_clients: ClientEntry[];
|
||||
if(self) {
|
||||
chat.channelChat().name = channel_to.channelName();
|
||||
current_clients = client.channelTree.clientsByChannel(client.currentChannel())
|
||||
this.connection.client.controlBar.updateVoice(channel_to);
|
||||
this.connection_handler.chat.channelChat().name = channel_to.channelName();
|
||||
current_clients = client.channelTree.clientsByChannel(client.currentChannel());
|
||||
this.connection_handler.update_voice_status(channel_to);
|
||||
}
|
||||
|
||||
tree.moveClient(client, channel_to);
|
||||
for(const entry of current_clients || [])
|
||||
if(entry !== client) entry.getAudioController().stopAudio(true);
|
||||
if(entry !== client)
|
||||
entry.get_audio_handle().abort_replay();
|
||||
|
||||
const own_channel = this.connection.client.getClient().currentChannel();
|
||||
if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
|
||||
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,
|
||||
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)
|
||||
sound.play(Sound.USER_MOVED_SELF);
|
||||
this.connection_handler.sound.play(Sound.USER_MOVED_SELF);
|
||||
else if(own_channel == channel_to)
|
||||
sound.play(Sound.USER_ENTERED_MOVED);
|
||||
this.connection_handler.sound.play(Sound.USER_ENTERED_MOVED);
|
||||
else if(own_channel == channel_from)
|
||||
sound.play(Sound.USER_LEFT_MOVED);
|
||||
this.connection_handler.sound.play(Sound.USER_LEFT_MOVED);
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
|
||||
chat.serverChat().appendMessage(self ? tr("You switched from channel {1} to {2}") : tr("{0} switched from channel {1} to {2}"), true,
|
||||
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)
|
||||
sound.play(Sound.USER_ENTERED);
|
||||
this.connection_handler.sound.play(Sound.USER_ENTERED);
|
||||
else if(own_channel == channel_from)
|
||||
sound.play(Sound.USER_LEFT);
|
||||
this.connection_handler.sound.play(Sound.USER_LEFT);
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
|
||||
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,
|
||||
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),
|
||||
|
@ -476,11 +487,11 @@ namespace connection {
|
|||
json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
|
||||
);
|
||||
if(self) {
|
||||
sound.play(Sound.CHANNEL_KICKED);
|
||||
this.connection_handler.sound.play(Sound.CHANNEL_KICKED);
|
||||
} else if(own_channel == channel_to)
|
||||
sound.play(Sound.USER_ENTERED_KICKED);
|
||||
this.connection_handler.sound.play(Sound.USER_ENTERED_KICKED);
|
||||
else if(own_channel == channel_from)
|
||||
sound.play(Sound.USER_LEFT_KICKED_CHANNEL);
|
||||
this.connection_handler.sound.play(Sound.USER_LEFT_KICKED_CHANNEL);
|
||||
} else {
|
||||
console.warn(tr("Unknown reason id %o"), json["reasonid"]);
|
||||
}
|
||||
|
@ -554,20 +565,20 @@ namespace connection {
|
|||
return;
|
||||
}
|
||||
if(invoker == this.connection.client.getClient()) {
|
||||
sound.play(Sound.MESSAGE_SEND, {default_volume: .5});
|
||||
this.connection_handler.sound.play(Sound.MESSAGE_SEND, {default_volume: .5});
|
||||
target.chat(true).appendMessage("{0}: {1}", true, this.connection.client.getClient().createChatTag(true), MessageHelper.bbcode_chat(json["msg"]));
|
||||
} else {
|
||||
sound.play(Sound.MESSAGE_RECEIVED, {default_volume: .5});
|
||||
this.connection_handler.sound.play(Sound.MESSAGE_RECEIVED, {default_volume: .5});
|
||||
invoker.chat(true).appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), MessageHelper.bbcode_chat(json["msg"]));
|
||||
}
|
||||
} else if(mode == 2) {
|
||||
if(json["invokerid"] == this.connection.client.clientId)
|
||||
sound.play(Sound.MESSAGE_SEND, {default_volume: .5});
|
||||
this.connection_handler.sound.play(Sound.MESSAGE_SEND, {default_volume: .5});
|
||||
else
|
||||
sound.play(Sound.MESSAGE_RECEIVED, {default_volume: .5});
|
||||
chat.channelChat().appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), MessageHelper.bbcode_chat(json["msg"]))
|
||||
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) {
|
||||
chat.serverChat().appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), MessageHelper.bbcode_chat(json["msg"]));
|
||||
this.connection_handler.chat.serverChat().appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), MessageHelper.bbcode_chat(json["msg"]));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -621,8 +632,8 @@ namespace connection {
|
|||
updates.push({key: key, value: json[key]});
|
||||
}
|
||||
client.updateVariables(...updates);
|
||||
if(this.connection.client.selectInfo.currentSelected == client)
|
||||
this.connection.client.selectInfo.update();
|
||||
if(this.connection.client.select_info.currentSelected == client)
|
||||
this.connection.client.select_info.update();
|
||||
}
|
||||
|
||||
handleNotifyServerEdited(json) {
|
||||
|
@ -641,8 +652,8 @@ namespace connection {
|
|||
updates.push({key: key, value: json[key]});
|
||||
}
|
||||
this.connection.client.channelTree.server.updateVariables(false, ...updates);
|
||||
if(this.connection.client.selectInfo.currentSelected == this.connection.client.channelTree.server)
|
||||
this.connection.client.selectInfo.update();
|
||||
if(this.connection.client.select_info.currentSelected == this.connection.client.channelTree.server)
|
||||
this.connection.client.select_info.update();
|
||||
}
|
||||
|
||||
handleNotifyServerUpdated(json) {
|
||||
|
@ -661,7 +672,7 @@ namespace connection {
|
|||
updates.push({key: key, value: json[key]});
|
||||
}
|
||||
this.connection.client.channelTree.server.updateVariables(true, ...updates);
|
||||
let info = this.connection.client.selectInfo;
|
||||
let info = this.connection.client.select_info;
|
||||
if(info.currentSelected instanceof ServerEntry)
|
||||
info.update();
|
||||
}
|
||||
|
@ -686,7 +697,7 @@ namespace connection {
|
|||
unique_id: json["invokeruid"]
|
||||
}, json["msg"]);
|
||||
|
||||
sound.play(Sound.USER_POKED_SELF);
|
||||
this.connection_handler.sound.play(Sound.USER_POKED_SELF);
|
||||
}
|
||||
|
||||
//TODO server chat message
|
||||
|
@ -695,7 +706,7 @@ namespace connection {
|
|||
|
||||
const self = this.connection.client.getClient();
|
||||
if(json["clid"] == self.clientId())
|
||||
sound.play(Sound.GROUP_SERVER_ASSIGNED_SELF);
|
||||
this.connection_handler.sound.play(Sound.GROUP_SERVER_ASSIGNED_SELF);
|
||||
}
|
||||
|
||||
//TODO server chat message
|
||||
|
@ -704,7 +715,7 @@ namespace connection {
|
|||
|
||||
const self = this.connection.client.getClient();
|
||||
if(json["clid"] == self.clientId()) {
|
||||
sound.play(Sound.GROUP_SERVER_REVOKED_SELF);
|
||||
this.connection_handler.sound.play(Sound.GROUP_SERVER_REVOKED_SELF);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
@ -715,7 +726,7 @@ namespace connection {
|
|||
|
||||
const self = this.connection.client.getClient();
|
||||
if(json["clid"] == self.clientId()) {
|
||||
sound.play(Sound.GROUP_CHANNEL_CHANGED_SELF);
|
||||
this.connection_handler.sound.play(Sound.GROUP_CHANNEL_CHANGED_SELF);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,11 +11,12 @@ namespace connection {
|
|||
timeout: 1000
|
||||
};
|
||||
|
||||
export type ConnectionStateListener = (old_state: ConnectionState, new_state: ConnectionState) => any;
|
||||
export abstract class AbstractServerConnection {
|
||||
readonly client: TSClient;
|
||||
readonly client: ConnectionHandler;
|
||||
readonly command_helper: CommandHelper;
|
||||
|
||||
protected constructor(client: TSClient) {
|
||||
protected constructor(client: ConnectionHandler) {
|
||||
this.client = client;
|
||||
|
||||
this.command_helper = new CommandHelper(this);
|
||||
|
@ -32,18 +33,34 @@ namespace connection {
|
|||
|
||||
abstract command_handler_boss() : AbstractCommandHandlerBoss;
|
||||
abstract send_command(command: string, data?: any | any[], options?: CommandOptions) : Promise<CommandResult>;
|
||||
|
||||
abstract get onconnectionstatechanged() : ConnectionStateListener;
|
||||
abstract set onconnectionstatechanged(listener: ConnectionStateListener);
|
||||
}
|
||||
|
||||
export namespace voice {
|
||||
export enum PlayerState {
|
||||
PREBUFFERING,
|
||||
PLAYING,
|
||||
BUFFERING,
|
||||
STOPPING,
|
||||
STOPPED
|
||||
}
|
||||
|
||||
export interface VoiceClient {
|
||||
client_id: number;
|
||||
|
||||
callback_playback: () => any;
|
||||
callback_timeout: () => any;
|
||||
callback_stopped: () => any;
|
||||
|
||||
callback_state_changed: (new_state: PlayerState) => any;
|
||||
|
||||
get_state() : PlayerState;
|
||||
|
||||
get_volume() : number;
|
||||
set_volume(volume: number) : Promise<void>;
|
||||
set_volume(volume: number) : void;
|
||||
|
||||
abort_replay();
|
||||
}
|
||||
|
||||
export abstract class AbstractVoiceConnection {
|
||||
|
@ -54,10 +71,15 @@ namespace connection {
|
|||
}
|
||||
|
||||
abstract connected() : boolean;
|
||||
abstract encoding_supported(codec: number) : boolean;
|
||||
abstract decoding_supported(codec: number) : boolean;
|
||||
|
||||
abstract register_client(client_id: number) : VoiceClient;
|
||||
abstract availible_clients() : VoiceClient[];
|
||||
abstract available_clients() : VoiceClient[];
|
||||
abstract unregister_client(client: VoiceClient) : Promise<void>;
|
||||
|
||||
abstract voice_recorder() : VoiceRecorder;
|
||||
abstract acquire_voice_recorder(recorder: VoiceRecorder | undefined);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -71,7 +71,12 @@ namespace connection {
|
|||
client_version: "TeaWeb " + git_version + " (" + navigator.userAgent + ")",
|
||||
|
||||
client_server_password: this.server_password,
|
||||
client_browser_engine: navigator.product
|
||||
client_browser_engine: navigator.product,
|
||||
|
||||
client_input_hardware: this.connection.client.client_status.input_hardware,
|
||||
client_output_hardware: false,
|
||||
client_input_muted: this.connection.client.client_status.input_muted,
|
||||
client_output_muted: this.connection.client.client_status.output_muted,
|
||||
};
|
||||
|
||||
if(version) {
|
||||
|
|
|
@ -48,7 +48,10 @@ namespace connection {
|
|||
private _retCodeIdx: number;
|
||||
private _retListener: ReturnListener<CommandResult>[];
|
||||
|
||||
constructor(client : TSClient) {
|
||||
private _connection_state_listener: connection.ConnectionStateListener;
|
||||
private _voice_connection: audio.js.VoiceConnection;
|
||||
|
||||
constructor(client : ConnectionHandler) {
|
||||
super(client);
|
||||
|
||||
this._socket = null;
|
||||
|
@ -60,11 +63,14 @@ namespace connection {
|
|||
|
||||
this._command_boss.register_handler(this._command_handler_default);
|
||||
this.command_helper.initialize();
|
||||
|
||||
if(!settings.static_global(Settings.KEY_DISABLE_VOICE, false))
|
||||
this._voice_connection = new audio.js.VoiceConnection(this);
|
||||
}
|
||||
|
||||
on_connect: () => void = () => {
|
||||
console.log(tr("Socket connected"));
|
||||
chat.serverChat().appendMessage(tr("Logging in..."));
|
||||
this.client.chat.serverChat().appendMessage(tr("Logging in..."));
|
||||
this._handshakeHandler.startHandshake();
|
||||
};
|
||||
|
||||
|
@ -90,12 +96,12 @@ namespace connection {
|
|||
this._handshakeHandler = handshake;
|
||||
this._handshakeHandler.setConnection(this);
|
||||
this._connected = false;
|
||||
chat.serverChat().appendMessage(tr("Connecting to {0}:{1}"), true, address.host, address.port);
|
||||
this.client.chat.serverChat().appendMessage(tr("Connecting to {0}:{1}"), true, address.host, address.port);
|
||||
|
||||
const self = this;
|
||||
let local_socket: WebSocket;
|
||||
let local_timeout_timer: NodeJS.Timer;
|
||||
try {
|
||||
let local_socket: WebSocket;
|
||||
let local_timeout_timer: NodeJS.Timer;
|
||||
|
||||
local_timeout_timer = setTimeout(async () => {
|
||||
console.log(tr("Connect timeout triggered!"));
|
||||
|
@ -144,6 +150,7 @@ namespace connection {
|
|||
};
|
||||
this.updateConnectionState(ConnectionState.INITIALISING);
|
||||
} catch (e) {
|
||||
clearTimeout(local_timeout_timer);
|
||||
try {
|
||||
await this.disconnect();
|
||||
} catch(error) {
|
||||
|
@ -154,8 +161,10 @@ namespace connection {
|
|||
}
|
||||
|
||||
updateConnectionState(state: ConnectionState) {
|
||||
const old_state = this._connectionState;
|
||||
this._connectionState = state;
|
||||
this.client.controlBar.update_connection_state();
|
||||
if(this._connection_state_listener)
|
||||
this._connection_state_listener(old_state, state);
|
||||
}
|
||||
|
||||
async disconnect(reason?: string) : Promise<void> {
|
||||
|
@ -176,6 +185,9 @@ namespace connection {
|
|||
this._retListener = [];
|
||||
this._retCodeIdx = 0;
|
||||
this._connected = false;
|
||||
|
||||
if(this._voice_connection)
|
||||
this._voice_connection.dropSession();
|
||||
}
|
||||
|
||||
private handle_socket_message(data) {
|
||||
|
@ -203,10 +215,10 @@ namespace connection {
|
|||
});
|
||||
group.end();
|
||||
} else if(json["type"] === "WebRTC") {
|
||||
if(this.client.voiceConnection)
|
||||
this.client.voiceConnection.handleControlPacket(json);
|
||||
if(this._voice_connection)
|
||||
this._voice_connection.handleControlPacket(json);
|
||||
else
|
||||
console.log(tr("Dropping WebRTC command packet, because we havent a bridge."))
|
||||
console.log(tr("Dropping WebRTC command packet, because we haven't a bridge."))
|
||||
}
|
||||
else {
|
||||
console.log(tr("Unknown command type %o"), json["type"]);
|
||||
|
@ -280,14 +292,14 @@ namespace connection {
|
|||
if(!res.success) {
|
||||
if(res.id == 2568) { //Permission error
|
||||
res.message = tr("Insufficient client permissions. Failed on permission ") + this.client.permissions.resolveInfo(res.json["failed_permid"] as number).name;
|
||||
chat.serverChat().appendError(tr("Insufficient client permissions. Failed on permission {}"), this.client.permissions.resolveInfo(res.json["failed_permid"] as number).name);
|
||||
sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
|
||||
this.client.chat.serverChat().appendError(tr("Insufficient client permissions. Failed on permission {}"), this.client.permissions.resolveInfo(res.json["failed_permid"] as number).name);
|
||||
this.client.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
|
||||
} else {
|
||||
chat.serverChat().appendError(res.extra_message.length == 0 ? res.message : res.extra_message);
|
||||
this.client.chat.serverChat().appendError(res.extra_message.length == 0 ? res.message : res.extra_message);
|
||||
}
|
||||
}
|
||||
} else if(typeof(ex) === "string") {
|
||||
chat.serverChat().appendError(tr("Command execution results in ") + ex);
|
||||
this.client.chat.serverChat().appendError(tr("Command execution results in ") + ex);
|
||||
} else {
|
||||
console.error(tr("Invalid promise result type: %o. Result:"), typeof (ex));
|
||||
console.error(ex);
|
||||
|
@ -303,16 +315,24 @@ namespace connection {
|
|||
}
|
||||
|
||||
support_voice(): boolean {
|
||||
return false;
|
||||
return this._voice_connection !== undefined;
|
||||
}
|
||||
|
||||
voice_connection(): connection.voice.AbstractVoiceConnection | undefined {
|
||||
return undefined;
|
||||
return this._voice_connection;
|
||||
}
|
||||
|
||||
command_handler_boss(): connection.AbstractCommandHandlerBoss {
|
||||
return this._command_boss;
|
||||
}
|
||||
|
||||
|
||||
get onconnectionstatechanged() : connection.ConnectionStateListener {
|
||||
return this._connection_state_listener;
|
||||
}
|
||||
set onconnectionstatechanged(listener: connection.ConnectionStateListener) {
|
||||
this._connection_state_listener = listener;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -295,4 +295,5 @@ namespace i18n {
|
|||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const tr: typeof i18n.tr = i18n.tr;
|
|
@ -7,10 +7,14 @@ namespace app {
|
|||
WEB_RELEASE
|
||||
}
|
||||
export let type: Type = Type.UNKNOWN;
|
||||
|
||||
export function is_web() {
|
||||
return type == Type.WEB_RELEASE || type == Type.WEB_DEBUG;
|
||||
}
|
||||
}
|
||||
|
||||
namespace loader {
|
||||
type Task = {
|
||||
export type Task = {
|
||||
name: string,
|
||||
priority: number, /* tasks with the same priority will be executed in sync */
|
||||
function: () => Promise<void>
|
||||
|
@ -518,8 +522,6 @@ const loader_javascript = {
|
|||
|
||||
"js/sound/Sounds.js",
|
||||
|
||||
"js/utils/modal.js",
|
||||
"js/utils/tab.js",
|
||||
"js/utils/helpers.js",
|
||||
|
||||
"js/crypto/sha.js",
|
||||
|
@ -530,6 +532,12 @@ const loader_javascript = {
|
|||
"js/profiles/ConnectionProfile.js",
|
||||
"js/profiles/Identity.js",
|
||||
|
||||
//Basic UI elements
|
||||
"js/ui/elements/context_divider.js",
|
||||
"js/ui/elements/context_menu.js",
|
||||
"js/ui/elements/modal.js",
|
||||
"js/ui/elements/tab.js",
|
||||
|
||||
//Load UI
|
||||
"js/ui/modal/ModalAvatarList.js",
|
||||
"js/ui/modal/ModalQuery.js",
|
||||
|
@ -556,11 +564,12 @@ const loader_javascript = {
|
|||
"js/ui/server.js",
|
||||
"js/ui/view.js",
|
||||
"js/ui/client_move.js",
|
||||
"js/ui/context_divider.js",
|
||||
"js/ui/htmltags.js",
|
||||
|
||||
"js/ui/frames/SelectedItemInfo.js",
|
||||
"js/ui/frames/ControlBar.js",
|
||||
"js/ui/frames/chat.js",
|
||||
"js/ui/frames/connection_handlers.js",
|
||||
|
||||
//Load permissions
|
||||
"js/permission/PermissionManager.js",
|
||||
|
@ -570,7 +579,7 @@ const loader_javascript = {
|
|||
"js/voice/VoiceHandler.js",
|
||||
"js/voice/VoiceRecorder.js",
|
||||
"js/voice/AudioResampler.js",
|
||||
"js/voice/AudioController.js",
|
||||
"js/voice/VoiceClient.js",
|
||||
|
||||
//Load codec
|
||||
"js/codec/Codec.js",
|
||||
|
@ -579,10 +588,9 @@ const loader_javascript = {
|
|||
//Load general stuff
|
||||
"js/settings.js",
|
||||
"js/bookmarks.js",
|
||||
"js/contextMenu.js",
|
||||
"js/FileManager.js",
|
||||
"js/client.js",
|
||||
"js/chat.js",
|
||||
"js/ConnectionHandler.js",
|
||||
"js/BrowserIPC.js",
|
||||
|
||||
//Connection
|
||||
"js/connection/CommandHandler.js",
|
||||
|
@ -591,7 +599,6 @@ const loader_javascript = {
|
|||
"js/connection/ServerConnection.js",
|
||||
|
||||
"js/stats.js",
|
||||
|
||||
"js/PPTListener.js",
|
||||
|
||||
|
||||
|
@ -687,6 +694,7 @@ const loader_style = {
|
|||
"css/static/frame/SelectInfo.css",
|
||||
"css/static/control_bar.css",
|
||||
"css/static/context_menu.css",
|
||||
"css/static/connection_handlers.css",
|
||||
"css/static/htmltags.css"
|
||||
]);
|
||||
},
|
||||
|
|
|
@ -8,6 +8,7 @@ enum LogCategory {
|
|||
NETWORKING,
|
||||
VOICE,
|
||||
I18N,
|
||||
IPC,
|
||||
IDENTITIES
|
||||
}
|
||||
|
||||
|
@ -30,7 +31,8 @@ namespace log {
|
|||
[LogCategory.NETWORKING, "Network "],
|
||||
[LogCategory.VOICE, "Voice "],
|
||||
[LogCategory.I18N, "I18N "],
|
||||
[LogCategory.IDENTITIES, "IDENTITIES "]
|
||||
[LogCategory.IDENTITIES, "IDENTITIES "],
|
||||
[LogCategory.IPC, "IPC "]
|
||||
]);
|
||||
|
||||
export let enabled_mapping = new Map<number, boolean>([
|
||||
|
@ -43,7 +45,8 @@ namespace log {
|
|||
[LogCategory.NETWORKING, true],
|
||||
[LogCategory.VOICE, true],
|
||||
[LogCategory.I18N, false],
|
||||
[LogCategory.IDENTITIES, true]
|
||||
[LogCategory.IDENTITIES, true],
|
||||
[LogCategory.IPC, true]
|
||||
]);
|
||||
|
||||
enum GroupMode {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
/// <reference path="chat.ts" />
|
||||
/// <reference path="client.ts" />
|
||||
/// <reference path="utils/modal.ts" />
|
||||
/// <reference path="ui/frames/chat.ts" />
|
||||
/// <reference path="ui/modal/ModalConnect.ts" />
|
||||
/// <reference path="ui/modal/ModalCreateChannel.ts" />
|
||||
/// <reference path="ui/modal/ModalBanCreate.ts" />
|
||||
|
@ -11,8 +9,6 @@
|
|||
/// <reference path="log.ts" />
|
||||
|
||||
let settings: Settings;
|
||||
let globalClient: TSClient;
|
||||
let chat: ChatBox;
|
||||
|
||||
const js_render = window.jsrender || $;
|
||||
const native_client = window.require !== undefined;
|
||||
|
@ -34,7 +30,8 @@ function setup_close() {
|
|||
profiles.save();
|
||||
|
||||
if(!settings.static(Settings.KEY_DISABLE_UNLOAD_DIALOG, false)) {
|
||||
if(!globalClient.serverConnection || !globalClient.serverConnection.connected) return;
|
||||
const active_connections = server_connections.server_connection_handlers().filter(e => e.connected);
|
||||
if(active_connections.length == 0) return;
|
||||
|
||||
if(!native_client) {
|
||||
event.returnValue = "Are you really sure?<br>You're still connected!";
|
||||
|
@ -92,13 +89,6 @@ function setup_jsrender() : boolean {
|
|||
}
|
||||
|
||||
async function initialize() {
|
||||
const display_load_error = message => {
|
||||
if(typeof(display_critical_load) !== "undefined")
|
||||
display_critical_load(message);
|
||||
else
|
||||
displayCriticalError(message);
|
||||
};
|
||||
|
||||
settings = new Settings();
|
||||
|
||||
try {
|
||||
|
@ -109,6 +99,18 @@ async function initialize() {
|
|||
return;
|
||||
}
|
||||
|
||||
bipc.setup();
|
||||
}
|
||||
|
||||
|
||||
async function initialize_app() {
|
||||
const display_load_error = message => {
|
||||
if(typeof(display_critical_load) !== "undefined")
|
||||
display_critical_load(message);
|
||||
else
|
||||
displayCriticalError(message);
|
||||
};
|
||||
|
||||
try {
|
||||
if(!setup_jsrender())
|
||||
throw "invalid load";
|
||||
|
@ -128,7 +130,15 @@ async function initialize() {
|
|||
return;
|
||||
}
|
||||
|
||||
AudioController.initializeAudioController();
|
||||
control_bar = new ControlBar($("#control_bar")); /* setup the control bar */
|
||||
|
||||
if(!audio.player.initialize())
|
||||
console.warn(tr("Failed to initialize audio controller!"));
|
||||
|
||||
sound.initialize().then(() => {
|
||||
console.log(tr("Sounds initialitzed"));
|
||||
});
|
||||
|
||||
await profiles.load();
|
||||
|
||||
try {
|
||||
|
@ -230,47 +240,31 @@ function Base64DecodeUrl(str: string, pad?: boolean){
|
|||
|
||||
function main() {
|
||||
//http://localhost:63343/Web-Client/index.php?_ijt=omcpmt8b9hnjlfguh8ajgrgolr&default_connect_url=true&default_connect_type=teamspeak&default_connect_url=localhost%3A9987&disableUnloadDialog=1&loader_ignore_age=1
|
||||
voice_recoder = new VoiceRecorder();
|
||||
voice_recoder.reinitialiseVAD();
|
||||
|
||||
globalClient = new TSClient();
|
||||
server_connections = new ServerConnectionManager($("#connection-handlers"));
|
||||
control_bar.initialise(); /* before connection handler to allow property apply */
|
||||
|
||||
const initial_handler = server_connections.spawn_server_connection_handler();
|
||||
initial_handler.acquire_recorder(voice_recoder, false);
|
||||
control_bar.set_connection_handler(initial_handler);
|
||||
/** Setup the XF forum identity **/
|
||||
profiles.identities.setup_forum();
|
||||
|
||||
chat = new ChatBox($("#chat"));
|
||||
globalClient.setup();
|
||||
|
||||
if(settings.static(Settings.KEY_FLAG_CONNECT_DEFAULT, false) && settings.static(Settings.KEY_CONNECT_ADDRESS, "")) {
|
||||
const profile_uuid = settings.static(Settings.KEY_CONNECT_PROFILE, (profiles.default_profile() || {id: 'default'}).id);
|
||||
console.log("UUID: %s", profile_uuid);
|
||||
const profile = profiles.find_profile(profile_uuid) || profiles.default_profile();
|
||||
const address = settings.static(Settings.KEY_CONNECT_ADDRESS, "");
|
||||
const username = settings.static(Settings.KEY_CONNECT_USERNAME, "Another TeaSpeak user");
|
||||
|
||||
const password = settings.static(Settings.KEY_CONNECT_PASSWORD, "");
|
||||
const password_hashed = settings.static(Settings.KEY_FLAG_CONNECT_PASSWORD, false);
|
||||
|
||||
if(profile && profile.valid()) {
|
||||
globalClient.startConnection(address, profile, username, password.length > 0 ? {
|
||||
password: password,
|
||||
hashed: password_hashed
|
||||
} : undefined);
|
||||
} else {
|
||||
Modals.spawnConnectModal({
|
||||
url: address,
|
||||
enforce: true
|
||||
}, {
|
||||
profile: profile,
|
||||
enforce: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let _resize_timeout: NodeJS.Timer;
|
||||
$(window).on('resize', () => {
|
||||
$(window).on('resize', event => {
|
||||
if(event.target !== window)
|
||||
return;
|
||||
|
||||
if(_resize_timeout)
|
||||
clearTimeout(_resize_timeout);
|
||||
_resize_timeout = setTimeout(() => {
|
||||
globalClient.channelTree.handle_resized();
|
||||
globalClient.selectInfo.handle_resize();
|
||||
for(const connection of server_connections.server_connection_handlers())
|
||||
connection.invoke_resized_on_activate = true;
|
||||
const active_connection = server_connections.active_connection_handler();
|
||||
if(active_connection)
|
||||
active_connection.resize_elements();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
|
@ -288,22 +282,24 @@ function main() {
|
|||
Modals.spawnAvatarList(globalClient);
|
||||
}, 1000);
|
||||
*/
|
||||
(<any>window).test_upload = () => {
|
||||
const data = "Hello World";
|
||||
globalClient.fileManager.upload_file({
|
||||
size: data.length,
|
||||
(<any>window).test_upload = (message?: string) => {
|
||||
message = message || "Hello World";
|
||||
|
||||
const connection = server_connections.active_connection_handler();
|
||||
connection.fileManager.upload_file({
|
||||
size: message.length,
|
||||
overwrite: true,
|
||||
channel: globalClient.getClient().currentChannel(),
|
||||
channel: connection.getClient().currentChannel(),
|
||||
name: '/HelloWorld.txt',
|
||||
path: ''
|
||||
}).then(key => {
|
||||
console.log("Got key: %o", key);
|
||||
const upload = new RequestFileUpload(key);
|
||||
|
||||
const buffer = new Uint8Array(data.length);
|
||||
const buffer = new Uint8Array(message.length);
|
||||
{
|
||||
for(let index = 0; index < data.length; index++)
|
||||
buffer[index] = data.charCodeAt(index);
|
||||
for(let index = 0; index < message.length; index++)
|
||||
buffer[index] = message.charCodeAt(index);
|
||||
}
|
||||
|
||||
upload.put_data(buffer).catch(error => {
|
||||
|
@ -311,13 +307,42 @@ function main() {
|
|||
});
|
||||
})
|
||||
};
|
||||
|
||||
server_connections.set_active_connection_handler(server_connections.server_connection_handlers()[0]);
|
||||
|
||||
if(settings.static(Settings.KEY_FLAG_CONNECT_DEFAULT, false) && settings.static(Settings.KEY_CONNECT_ADDRESS, "")) {
|
||||
const profile_uuid = settings.static(Settings.KEY_CONNECT_PROFILE, (profiles.default_profile() || {id: 'default'}).id);
|
||||
console.log("UUID: %s", profile_uuid);
|
||||
const profile = profiles.find_profile(profile_uuid) || profiles.default_profile();
|
||||
const address = settings.static(Settings.KEY_CONNECT_ADDRESS, "");
|
||||
const username = settings.static(Settings.KEY_CONNECT_USERNAME, "Another TeaSpeak user");
|
||||
|
||||
const password = settings.static(Settings.KEY_CONNECT_PASSWORD, "");
|
||||
const password_hashed = settings.static(Settings.KEY_FLAG_CONNECT_PASSWORD, false);
|
||||
|
||||
if(profile && profile.valid()) {
|
||||
const connection = server_connections.active_connection_handler() || server_connections.spawn_server_connection_handler();
|
||||
connection.startConnection(address, profile, username, password.length > 0 ? {
|
||||
password: password,
|
||||
hashed: password_hashed
|
||||
} : undefined);
|
||||
} else {
|
||||
Modals.spawnConnectModal({
|
||||
url: address,
|
||||
enforce: true
|
||||
}, {
|
||||
profile: profile,
|
||||
enforce: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loader.register_task(loader.Stage.LOADED, {
|
||||
name: "async main invoke",
|
||||
const task_teaweb_starter: loader.Task = {
|
||||
name: "voice app starter",
|
||||
function: async () => {
|
||||
try {
|
||||
await initialize();
|
||||
await initialize_app();
|
||||
main();
|
||||
if(!audio.player.initialized()) {
|
||||
log.info(LogCategory.VOICE, tr("Initialize audio controller later!"));
|
||||
|
@ -334,5 +359,77 @@ loader.register_task(loader.Stage.LOADED, {
|
|||
}
|
||||
},
|
||||
priority: 10
|
||||
};
|
||||
|
||||
const task_certificate_callback: loader.Task = {
|
||||
name: "certificate accept tester",
|
||||
function: async () => {
|
||||
const certificate_accept = settings.static_global(Settings.KEY_CERTIFICATE_CALLBACK, undefined);
|
||||
if(certificate_accept) {
|
||||
log.info(LogCategory.IPC, tr("Using this instance as certificate callback. ID: %s"), certificate_accept);
|
||||
try {
|
||||
try {
|
||||
await bipc.get_handler().post_certificate_accpected(certificate_accept);
|
||||
} catch(e) {} //FIXME remove!
|
||||
log.info(LogCategory.IPC, tr("Other instance has acknowledged out work. Closing this window."));
|
||||
|
||||
const seconds_tag = $.spawn("a");
|
||||
|
||||
let seconds = 5;
|
||||
let interval_id;
|
||||
interval_id = setInterval(() => {
|
||||
seconds--;
|
||||
seconds_tag.text(seconds.toString());
|
||||
|
||||
if(seconds <= 0) {
|
||||
clearTimeout(interval_id);
|
||||
log.info(LogCategory.GENERAL, tr("Closing window"));
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
const message =
|
||||
"You've successfully accepted the certificate.{:br:}" +
|
||||
"This page will close in {0} seconds.";
|
||||
createInfoModal(
|
||||
tr("Certificate acccepted successfully"),
|
||||
MessageHelper.formatMessage(tr(message), seconds_tag),
|
||||
{
|
||||
closeable: false,
|
||||
footer: undefined
|
||||
}
|
||||
).open();
|
||||
return;
|
||||
} catch(error) {
|
||||
log.warn(LogCategory.IPC, tr("Failed to successfully post certificate accept status: %o"), error);
|
||||
}
|
||||
} else {
|
||||
log.info(LogCategory.IPC, tr("We're not used to accept certificated. Booting app."));
|
||||
}
|
||||
|
||||
loader.register_task(loader.Stage.LOADED, task_teaweb_starter);
|
||||
},
|
||||
priority: 10
|
||||
};
|
||||
|
||||
loader.register_task(loader.Stage.LOADED, {
|
||||
name: "app starter",
|
||||
function: async () => {
|
||||
try {
|
||||
await initialize();
|
||||
|
||||
if(app.is_web()) {
|
||||
loader.register_task(loader.Stage.LOADED, task_certificate_callback);
|
||||
} else
|
||||
loader.register_task(loader.Stage.LOADED, task_teaweb_starter);
|
||||
} catch (ex) {
|
||||
console.error(ex.stack);
|
||||
if(ex instanceof ReferenceError || ex instanceof TypeError)
|
||||
ex = ex.name + ": " + ex.message;
|
||||
displayCriticalError("Failed to boot app function:<br>" + ex);
|
||||
}
|
||||
},
|
||||
priority: 10
|
||||
});
|
||||
|
||||
|
|
|
@ -60,13 +60,13 @@ class Group {
|
|||
}
|
||||
|
||||
class GroupManager extends connection.AbstractCommandHandler {
|
||||
readonly handle: TSClient;
|
||||
readonly handle: ConnectionHandler;
|
||||
|
||||
serverGroups: Group[] = [];
|
||||
channelGroups: Group[] = [];
|
||||
|
||||
private requests_group_permissions: GroupPermissionRequest[] = [];
|
||||
constructor(client: TSClient) {
|
||||
constructor(client: ConnectionHandler) {
|
||||
super(client.serverConnection);
|
||||
|
||||
client.serverConnection.command_handler_boss().register_handler(this);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/// <reference path="../client.ts" />
|
||||
/// <reference path="../ConnectionHandler.ts" />
|
||||
/// <reference path="../connection/ConnectionBase.ts" />
|
||||
|
||||
enum PermissionType {
|
||||
|
@ -418,7 +418,7 @@ class TeaPermissionRequest {
|
|||
}
|
||||
|
||||
class PermissionManager extends connection.AbstractCommandHandler {
|
||||
readonly handle: TSClient;
|
||||
readonly handle: ConnectionHandler;
|
||||
|
||||
permissionList: PermissionInfo[] = [];
|
||||
permissionGroups: PermissionGroup[] = [];
|
||||
|
@ -505,7 +505,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
|||
return permissions;
|
||||
}
|
||||
|
||||
constructor(client: TSClient) {
|
||||
constructor(client: ConnectionHandler) {
|
||||
super(client.serverConnection);
|
||||
|
||||
//FIXME? Dont register the handler like this?
|
||||
|
|
|
@ -10,10 +10,11 @@ interface JSON {
|
|||
map_field_to<T>(object: T, value: any, field: string) : T;
|
||||
}
|
||||
|
||||
type JQueryScrollType = "height" | "width";
|
||||
interface JQuery<TElement = HTMLElement> {
|
||||
render(values?: any) : string;
|
||||
renderTag(values?: any) : JQuery<TElement>;
|
||||
hasScrollBar() : boolean;
|
||||
hasScrollBar(direction?: JQueryScrollType) : boolean;
|
||||
|
||||
|
||||
visible_height() : number;
|
||||
|
@ -137,10 +138,21 @@ if(typeof ($) !== "undefined") {
|
|||
}
|
||||
}
|
||||
if(!$.fn.hasScrollBar)
|
||||
$.fn.hasScrollBar = function() {
|
||||
if(this.length <= 0) return false;
|
||||
return this.get(0).scrollHeight > this.height();
|
||||
}
|
||||
$.fn.hasScrollBar = function(direction?: "height" | "width") {
|
||||
if(this.length <= 0)
|
||||
return false;
|
||||
|
||||
const scroll_height = this.get(0).scrollHeight > this.height();
|
||||
const scroll_width = this.get(0).scrollWidth > this.width();
|
||||
|
||||
if(typeof(direction) === "string") {
|
||||
if(direction === "height")
|
||||
return scroll_height;
|
||||
if(direction === "width")
|
||||
return scroll_width;
|
||||
}
|
||||
return scroll_width || scroll_height;
|
||||
};
|
||||
|
||||
if(!$.fn.visible_height)
|
||||
$.fn.visible_height = function (this: JQuery<HTMLElement>) {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/// <reference path="client.ts" />
|
||||
|
||||
if(typeof(customElements) !== "undefined") {
|
||||
try {
|
||||
class X_Properties extends HTMLElement {}
|
||||
|
@ -19,15 +17,11 @@ interface SettingsKey<T> {
|
|||
fallback_keys?: string | string[];
|
||||
fallback_imports?: {[key: string]:(value: string) => T};
|
||||
description?: string;
|
||||
default_value?: T;
|
||||
}
|
||||
|
||||
class StaticSettings {
|
||||
private static _instance: StaticSettings;
|
||||
static get instance() : StaticSettings {
|
||||
if(!this._instance)
|
||||
this._instance = new StaticSettings(true);
|
||||
return this._instance;
|
||||
}
|
||||
class SettingsBase {
|
||||
protected static readonly UPDATE_DIRECT: boolean = true;
|
||||
|
||||
protected static transformStO?<T>(input?: string, _default?: T, default_type?: string) : T {
|
||||
default_type = default_type || typeof _default;
|
||||
|
@ -66,7 +60,7 @@ class StaticSettings {
|
|||
if(typeof(value) !== 'string')
|
||||
return _default;
|
||||
|
||||
return StaticSettings.transformStO(value as string, _default, default_type);
|
||||
return SettingsBase.transformStO(value as string, _default, default_type);
|
||||
}
|
||||
|
||||
protected static keyify<T>(key: string | SettingsKey<T>) : SettingsKey<T> {
|
||||
|
@ -76,11 +70,21 @@ class StaticSettings {
|
|||
return key;
|
||||
throw "key is not a key";
|
||||
}
|
||||
}
|
||||
|
||||
class StaticSettings extends SettingsBase {
|
||||
private static _instance: StaticSettings;
|
||||
static get instance() : StaticSettings {
|
||||
if(!this._instance)
|
||||
this._instance = new StaticSettings(true);
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
protected _handle: StaticSettings;
|
||||
protected _staticPropsTag: JQuery;
|
||||
|
||||
protected constructor(_reserved = undefined) {
|
||||
super();
|
||||
if(_reserved && !StaticSettings._instance) {
|
||||
this._staticPropsTag = $("#properties");
|
||||
this.initializeStatic();
|
||||
|
@ -90,7 +94,14 @@ class StaticSettings {
|
|||
}
|
||||
|
||||
private initializeStatic() {
|
||||
location.search.substr(1).split("&").forEach(part => {
|
||||
let search;
|
||||
if(window.opener && window.opener !== window) {
|
||||
search = new URL(window.location.href).search;
|
||||
} else {
|
||||
search = location.search;
|
||||
}
|
||||
|
||||
search.substr(1).split("&").forEach(part => {
|
||||
let item = part.split("=");
|
||||
$("<x-property></x-property>")
|
||||
.attr("key", item[0])
|
||||
|
@ -136,6 +147,9 @@ class Settings extends StaticSettings {
|
|||
key: 'disableVoice',
|
||||
description: 'Disables the voice bridge. If disabled, the audio and codec workers aren\'t required anymore'
|
||||
};
|
||||
static readonly KEY_DISABLE_MULTI_SESSION: SettingsKey<boolean> = {
|
||||
key: 'disableMultiSession',
|
||||
};
|
||||
|
||||
static readonly KEY_LOAD_DUMMY_ERROR: SettingsKey<boolean> = {
|
||||
key: 'dummy_load_error',
|
||||
|
@ -176,6 +190,10 @@ class Settings extends StaticSettings {
|
|||
key: 'connect_password_hashed'
|
||||
};
|
||||
|
||||
static readonly KEY_CERTIFICATE_CALLBACK: SettingsKey<string> = {
|
||||
key: 'certificate_callback'
|
||||
};
|
||||
|
||||
static readonly FN_SERVER_CHANNEL_SUBSCRIBE_MODE: (channel: ChannelEntry) => SettingsKey<ChannelSubscribeMode> = channel => {
|
||||
return {
|
||||
key: 'channel_subscribe_mode_' + channel.getChannelId()
|
||||
|
@ -197,10 +215,7 @@ class Settings extends StaticSettings {
|
|||
return result;
|
||||
})();
|
||||
|
||||
private static readonly UPDATE_DIRECT: boolean = true;
|
||||
private cacheGlobal = {};
|
||||
private cacheServer = {};
|
||||
private currentServer: ServerEntry;
|
||||
private saveWorker: NodeJS.Timer;
|
||||
private updated: boolean = false;
|
||||
|
||||
|
@ -225,10 +240,6 @@ class Settings extends StaticSettings {
|
|||
return StaticSettings.resolveKey(Settings.keyify(key), _default, key => this.cacheGlobal[key]);
|
||||
}
|
||||
|
||||
server?<T>(key: string | SettingsKey<T>, _default?: T) : T {
|
||||
return StaticSettings.resolveKey(Settings.keyify(key), _default, key => this.cacheServer[key]);
|
||||
}
|
||||
|
||||
changeGlobal<T>(key: string | SettingsKey<T>, value?: T){
|
||||
key = Settings.keyify(key);
|
||||
|
||||
|
@ -242,12 +253,37 @@ class Settings extends StaticSettings {
|
|||
this.save();
|
||||
}
|
||||
|
||||
save() {
|
||||
this.updated = false;
|
||||
let global = JSON.stringify(this.cacheGlobal);
|
||||
localStorage.setItem("settings.global", global);
|
||||
}
|
||||
}
|
||||
|
||||
class ServerSettings extends SettingsBase {
|
||||
private cacheServer = {};
|
||||
private currentServer: ServerEntry;
|
||||
private _server_save_worker: NodeJS.Timer;
|
||||
private _server_settings_updated: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._server_save_worker = setInterval(() => {
|
||||
if(this._server_settings_updated)
|
||||
this.save();
|
||||
}, 5 * 1000);
|
||||
}
|
||||
|
||||
server?<T>(key: string | SettingsKey<T>, _default?: T) : T {
|
||||
return StaticSettings.resolveKey(Settings.keyify(key), _default, key => this.cacheServer[key]);
|
||||
}
|
||||
|
||||
changeServer<T>(key: string | SettingsKey<T>, value?: T) {
|
||||
key = Settings.keyify(key);
|
||||
|
||||
if(this.cacheServer[key.key] == value) return;
|
||||
|
||||
this.updated = true;
|
||||
this._server_settings_updated = true;
|
||||
this.cacheServer[key.key] = StaticSettings.transformOtS(value);
|
||||
|
||||
if(Settings.UPDATE_DIRECT)
|
||||
|
@ -271,15 +307,12 @@ class Settings extends StaticSettings {
|
|||
}
|
||||
|
||||
save() {
|
||||
this.updated = false;
|
||||
this._server_settings_updated = false;
|
||||
|
||||
if(this.currentServer) {
|
||||
let serverId = this.currentServer.properties.virtualserver_unique_identifier;
|
||||
let server = JSON.stringify(this.cacheServer);
|
||||
localStorage.setItem("settings.server_" + serverId, server);
|
||||
}
|
||||
|
||||
let global = JSON.stringify(this.cacheGlobal);
|
||||
localStorage.setItem("settings.global", global);
|
||||
}
|
||||
}
|
|
@ -55,7 +55,7 @@ enum Sound {
|
|||
}
|
||||
|
||||
namespace sound {
|
||||
interface SpeechFile {
|
||||
export interface SoundHandle {
|
||||
key: string;
|
||||
filename: string;
|
||||
|
||||
|
@ -68,7 +68,7 @@ namespace sound {
|
|||
}
|
||||
|
||||
let warned = false;
|
||||
let speech_mapping: {[key: string]:SpeechFile} = {};
|
||||
let speech_mapping: {[key: string]:SoundHandle} = {};
|
||||
|
||||
let volume_require_save = false;
|
||||
let speech_volume: {[key: string]:number} = {};
|
||||
|
@ -80,7 +80,7 @@ namespace sound {
|
|||
let master_mixed: GainNode;
|
||||
|
||||
function register_sound(key: string, file: string) {
|
||||
speech_mapping[key] = {key: key, filename: file} as SpeechFile;
|
||||
speech_mapping[key] = {key: key, filename: file} as SoundHandle;
|
||||
}
|
||||
|
||||
export function get_sound_volume(sound: Sound, default_volume?: number) : number {
|
||||
|
@ -192,6 +192,7 @@ namespace sound {
|
|||
register_sound("message.received", "effects/message_received.wav");
|
||||
register_sound("message.send", "effects/message_send.wav");
|
||||
|
||||
manager = new SoundManager(undefined);
|
||||
audio.player.on_ready(reinitialisize_audio);
|
||||
return new Promise<void>(resolve => {
|
||||
$.ajax({
|
||||
|
@ -211,7 +212,7 @@ namespace sound {
|
|||
async: true,
|
||||
type: 'GET'
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export interface PlaybackOptions {
|
||||
|
@ -221,84 +222,44 @@ namespace sound {
|
|||
default_volume?: number;
|
||||
}
|
||||
|
||||
export function play(sound: Sound, options?: PlaybackOptions) {
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
export async function resolve_sound(sound: Sound) : Promise<SoundHandle> {
|
||||
const file: SoundHandle = speech_mapping[sound];
|
||||
if(!file)
|
||||
throw tr("Missing sound handle");
|
||||
|
||||
|
||||
const file: SpeechFile = speech_mapping[sound];
|
||||
if(!file) {
|
||||
console.warn(tr("Missing sound %o"), sound);
|
||||
return;
|
||||
}
|
||||
if(file.not_supported) {
|
||||
if(!file.not_supported_timeout || Date.now() < file.not_supported_timeout) //Test if the not supported isn't may timeouted
|
||||
return;
|
||||
if(!file.not_supported_timeout || Date.now() < file.not_supported_timeout) //Test if the not supported flag has been expired
|
||||
return file;
|
||||
|
||||
file.not_supported = false;
|
||||
file.not_supported_timeout = undefined;
|
||||
}
|
||||
|
||||
const path = "audio/" + file.filename;
|
||||
|
||||
const context = audio.player.context();
|
||||
if(!context) {
|
||||
console.warn(tr("Tried to replay a sound without an audio context (Sound: %o). Dropping playback"), sound);
|
||||
return;
|
||||
}
|
||||
const volume = get_sound_volume(sound, options.default_volume);
|
||||
|
||||
console.log(tr("Replaying sound %s (Sound volume: %o | Master volume %o)"), sound, volume, master_volume);
|
||||
if(volume == 0) return;
|
||||
if(master_volume == 0) return;
|
||||
if(!options.ignore_muted && !ignore_muted && globalClient.controlBar.muteOutput) return;
|
||||
if(!context)
|
||||
return file;
|
||||
|
||||
const path = "audio/" + file.filename;
|
||||
if(context.decodeAudioData) {
|
||||
if(file.cached) {
|
||||
if(!options.ignore_overlap && file.replaying && !overlap_sounds) {
|
||||
console.log(tr("Dropping requested playback for sound %s because it would overlap."), sound);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(tr("Using cached buffer: %o"), file.cached);
|
||||
const player = context.createBufferSource();
|
||||
player.buffer = file.cached;
|
||||
player.start(0);
|
||||
|
||||
file.replaying = true;
|
||||
player.onended = event => {
|
||||
file.replaying = false;
|
||||
};
|
||||
|
||||
if(volume != 1 && context.createGain) {
|
||||
const gain = context.createGain();
|
||||
if(gain.gain.setValueAtTime)
|
||||
gain.gain.setValueAtTime(volume, 0);
|
||||
else
|
||||
gain.gain.value = volume;
|
||||
|
||||
player.connect(gain);
|
||||
gain.connect(master_mixed);
|
||||
} else {
|
||||
player.connect(master_mixed);
|
||||
}
|
||||
} else {
|
||||
if(!file.cached) {
|
||||
const decode_data = buffer => {
|
||||
console.log(buffer);
|
||||
try {
|
||||
console.log(tr("Decoding data"));
|
||||
context.decodeAudioData(buffer, result => {
|
||||
log.info(LogCategory.VOICE, tr("Got decoded data"));
|
||||
file.cached = result;
|
||||
play(sound, options);
|
||||
}, error => {
|
||||
console.error(tr("Failed to decode audio data for %o"), sound);
|
||||
console.error(error);
|
||||
file.not_supported = true;
|
||||
file.not_supported_timeout = Date.now() + 1000 * 60 * 60; //Try in 2min again!
|
||||
file.not_supported_timeout = Date.now() + 1000 * 60 * 2; //Try in 2min again!
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
file.not_supported = true;
|
||||
file.not_supported_timeout = Date.now() + 1000 * 60 * 60; //Try in 2min again!
|
||||
file.not_supported_timeout = Date.now() + 1000 * 60 * 2; //Try in 2min again!
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -306,31 +267,33 @@ namespace sound {
|
|||
xhr.open('GET', path, true);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
|
||||
xhr.onload = function(e) {
|
||||
if (this.status == 200) {
|
||||
decode_data(this.response);
|
||||
} else {
|
||||
console.error(tr("Failed to load audio file. (Response code %o)"), this.status);
|
||||
file.not_supported = true;
|
||||
file.not_supported_timeout = Date.now() + 1000 * 60 * 60; //Try in 2min again!
|
||||
try {
|
||||
const result = new Promise((resolve, reject) => {
|
||||
xhr.onload = resolve;
|
||||
xhr.onerror = reject;
|
||||
});
|
||||
|
||||
xhr.send();
|
||||
await result;
|
||||
|
||||
if (xhr.status != 200)
|
||||
throw "invalid response code (" + xhr.status + ")";
|
||||
|
||||
console.log(tr("Decoding data"));
|
||||
try {
|
||||
file.cached = await context.decodeAudioData(xhr.response);
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
throw "failed to decode audio data";
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = error => {
|
||||
console.error(tr("Failed to load audio file "), sound);
|
||||
console.error(error);
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to load audio file %s. Error: %o"), sound, error);
|
||||
file.not_supported = true;
|
||||
file.not_supported_timeout = Date.now() + 1000 * 60 * 60; //Try in 2min again!
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
file.not_supported_timeout = Date.now() + 1000 * 60 * 2; //Try in 2min again!
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(tr("Replaying %s"), path);
|
||||
if(file.node) {
|
||||
file.node.currentTime = 0;
|
||||
file.node.play();
|
||||
} else {
|
||||
if(!file.node) {
|
||||
if(!warned) {
|
||||
warned = true;
|
||||
console.warn(tr("Your browser does not support decodeAudioData! Using a node to playback! This bypasses the audio output and volume regulation!"));
|
||||
|
@ -340,8 +303,81 @@ namespace sound {
|
|||
node.appendTo(container);
|
||||
|
||||
file.node = node[0];
|
||||
file.node.play();
|
||||
}
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
export let manager: SoundManager;
|
||||
|
||||
export class SoundManager {
|
||||
private _handle: ConnectionHandler;
|
||||
private _playing_sounds: {[key: string]:number} = {};
|
||||
|
||||
constructor(handle: ConnectionHandler) {
|
||||
this._handle = handle;
|
||||
}
|
||||
|
||||
play(_sound: Sound, options?: PlaybackOptions) {
|
||||
options = options || {};
|
||||
|
||||
const volume = get_sound_volume(_sound, options.default_volume);
|
||||
console.log(tr("Replaying sound %s (Sound volume: %o | Master volume %o)"), _sound, volume, master_volume);
|
||||
|
||||
if(volume == 0 || master_volume == 0)
|
||||
return;
|
||||
|
||||
if(this._handle && !options.ignore_muted && !sound.ignore_output_muted() && this._handle.client_status.output_muted)
|
||||
return;
|
||||
|
||||
const context = audio.player.context();
|
||||
if(!context) {
|
||||
console.warn(tr("Tried to replay a sound without an audio context (Sound: %o). Dropping playback"), _sound);
|
||||
return;
|
||||
}
|
||||
|
||||
sound.resolve_sound(_sound).then(handle => {
|
||||
if(!handle)
|
||||
return;
|
||||
|
||||
if(!options.ignore_overlap && (this._playing_sounds[_sound] > 0) && !sound.overlap_activated()) {
|
||||
console.log(tr("Dropping requested playback for sound %s because it would overlap."), _sound);
|
||||
return;
|
||||
}
|
||||
|
||||
if(handle.cached) {
|
||||
this._playing_sounds[_sound] = Date.now();
|
||||
|
||||
const player = context.createBufferSource();
|
||||
player.buffer = handle.cached;
|
||||
player.start(0);
|
||||
|
||||
handle.replaying = true;
|
||||
player.onended = event => {
|
||||
delete this._playing_sounds[_sound];
|
||||
};
|
||||
|
||||
if(volume != 1 && context.createGain) {
|
||||
const gain = context.createGain();
|
||||
if(gain.gain.setValueAtTime)
|
||||
gain.gain.setValueAtTime(volume, 0);
|
||||
else
|
||||
gain.gain.value = volume;
|
||||
|
||||
player.connect(gain);
|
||||
gain.connect(master_mixed);
|
||||
} else {
|
||||
player.connect(master_mixed);
|
||||
}
|
||||
} else if(handle.node) {
|
||||
handle.node.currentTime = 0;
|
||||
handle.node.play();
|
||||
} else {
|
||||
console.warn(tr("Failed to replay sound because of missing handles."), sound);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -457,13 +457,13 @@ class ChannelEntry {
|
|||
name: tr("Show channel info"),
|
||||
callback: () => {
|
||||
trigger_close = false;
|
||||
this.channelTree.client.selectInfo.open_popover()
|
||||
this.channelTree.client.select_info.open_popover()
|
||||
},
|
||||
icon: "client-about",
|
||||
visible: this.channelTree.client.selectInfo.is_popover()
|
||||
visible: this.channelTree.client.select_info.is_popover()
|
||||
}, {
|
||||
type: MenuEntryType.HR,
|
||||
visible: this.channelTree.client.selectInfo.is_popover(),
|
||||
visible: this.channelTree.client.select_info.is_popover(),
|
||||
name: ''
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
|
@ -500,7 +500,7 @@ class ChannelEntry {
|
|||
name: tr("Edit channel"),
|
||||
invalidPermission: !channelModify,
|
||||
callback: () => {
|
||||
Modals.createChannelModal(this, undefined, this.channelTree.client.permissions, (changes?, permissions?) => {
|
||||
Modals.createChannelModal(this.channelTree.client, this, undefined, this.channelTree.client.permissions, (changes?, permissions?) => {
|
||||
if(changes) {
|
||||
changes["cid"] = this.channelId;
|
||||
this.channelTree.client.serverConnection.send_command("channeledit", changes);
|
||||
|
@ -522,7 +522,7 @@ class ChannelEntry {
|
|||
this.channelTree.client.serverConnection.send_command("channeladdperm", perms, {
|
||||
flagset: ["continueonerror"]
|
||||
}).then(() => {
|
||||
sound.play(Sound.CHANNEL_EDITED_SELF);
|
||||
this.channelTree.client.sound.play(Sound.CHANNEL_EDITED_SELF);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -535,7 +535,7 @@ class ChannelEntry {
|
|||
invalidPermission: !flagDelete,
|
||||
callback: () => {
|
||||
this.channelTree.client.serverConnection.send_command("channeldelete", {cid: this.channelId}).then(() => {
|
||||
sound.play(Sound.CHANNEL_DELETED);
|
||||
this.channelTree.client.sound.play(Sound.CHANNEL_DELETED);
|
||||
})
|
||||
}
|
||||
},
|
||||
|
@ -694,8 +694,8 @@ class ChannelEntry {
|
|||
} else if(key == "channel_codec") {
|
||||
(this.properties.channel_codec == 5 || this.properties.channel_codec == 3 ? $.fn.show : $.fn.hide).apply(this.channelTag().find(".icons .icon_music"));
|
||||
this.channelTag().find(".icons .icon_no_sound").toggle(!(
|
||||
this.channelTree.client.voiceConnection &&
|
||||
this.channelTree.client.voiceConnection.codecSupported(this.properties.channel_codec)
|
||||
this.channelTree.client.serverConnection.support_voice() &&
|
||||
this.channelTree.client.serverConnection.voice_connection().decoding_supported(this.properties.channel_codec)
|
||||
));
|
||||
} else if(key == "channel_flag_default") {
|
||||
(this.properties.channel_flag_default ? $.fn.show : $.fn.hide).apply(this.channelTag().find(".icons .icon_default"));
|
||||
|
@ -771,7 +771,7 @@ class ChannelEntry {
|
|||
}).open();
|
||||
} else if(this.channelTree.client.getClient().currentChannel() != this)
|
||||
this.channelTree.client.getServerConnection().command_helper.joinChannel(this, this._cachedPassword).then(() => {
|
||||
sound.play(Sound.CHANNEL_JOINED);
|
||||
this.channelTree.client.sound.play(Sound.CHANNEL_JOINED);
|
||||
}).catch(error => {
|
||||
if(error instanceof CommandResult) {
|
||||
if(error.id == 781) { //Invalid password
|
||||
|
@ -803,7 +803,7 @@ class ChannelEntry {
|
|||
|
||||
if(inherited_subscription_mode) {
|
||||
this.subscribe_mode = ChannelSubscribeMode.INHERITED;
|
||||
unsubscribe = this.flag_subscribed && !this.channelTree.client.controlBar.channel_subscribe_all;
|
||||
unsubscribe = this.flag_subscribed && !this.channelTree.client.client_status.channel_subscribe_all;
|
||||
} else {
|
||||
this.subscribe_mode = ChannelSubscribeMode.UNSUBSCRIBED;
|
||||
unsubscribe = this.flag_subscribed;
|
||||
|
@ -831,7 +831,7 @@ class ChannelEntry {
|
|||
}
|
||||
|
||||
get subscribe_mode() : ChannelSubscribeMode {
|
||||
return typeof(this._subscribe_mode) !== 'undefined' ? this._subscribe_mode : (this._subscribe_mode = settings.server(Settings.FN_SERVER_CHANNEL_SUBSCRIBE_MODE(this), ChannelSubscribeMode.INHERITED));
|
||||
return typeof(this._subscribe_mode) !== 'undefined' ? this._subscribe_mode : (this._subscribe_mode = this.channelTree.client.settings.server(Settings.FN_SERVER_CHANNEL_SUBSCRIBE_MODE(this), ChannelSubscribeMode.INHERITED));
|
||||
}
|
||||
|
||||
set subscribe_mode(mode: ChannelSubscribeMode) {
|
||||
|
@ -839,21 +839,6 @@ class ChannelEntry {
|
|||
return;
|
||||
|
||||
this._subscribe_mode = mode;
|
||||
settings.changeServer(Settings.FN_SERVER_CHANNEL_SUBSCRIBE_MODE(this), mode);
|
||||
this.channelTree.client.settings.changeServer(Settings.FN_SERVER_CHANNEL_SUBSCRIBE_MODE(this), mode);
|
||||
}
|
||||
}
|
||||
|
||||
//Global functions
|
||||
function chat_channel_contextmenu(_element: any, event: any) {
|
||||
event.preventDefault();
|
||||
|
||||
let element = $(_element);
|
||||
let chid : number = Number.parseInt(element.attr("channelId"));
|
||||
let channel = globalClient.channelTree.findChannel(chid);
|
||||
if(!channel) {
|
||||
//TODO
|
||||
return;
|
||||
}
|
||||
|
||||
channel.showContextMenu(event.pageX, event.pageY);
|
||||
}
|
|
@ -54,28 +54,36 @@ class ClientEntry {
|
|||
protected _properties: ClientProperties;
|
||||
protected lastVariableUpdate: number = 0;
|
||||
protected _speaking: boolean = false;
|
||||
private _listener_initialized: boolean;
|
||||
protected _listener_initialized: boolean;
|
||||
protected _audio_handle: connection.voice.VoiceClient;
|
||||
|
||||
channelTree: ChannelTree;
|
||||
audioController: AudioController;
|
||||
|
||||
constructor(clientId, clientName, properties: ClientProperties = new ClientProperties()) {
|
||||
constructor(clientId: number, clientName, properties: ClientProperties = new ClientProperties()) {
|
||||
this._properties = properties;
|
||||
this._properties.client_nickname = clientName;
|
||||
this._clientId = clientId;
|
||||
this.channelTree = null;
|
||||
this._channel = null;
|
||||
this.audioController = new AudioController();
|
||||
}
|
||||
|
||||
const _this = this;
|
||||
this.audioController.onSpeaking = function () {
|
||||
_this.speaking = true;
|
||||
};
|
||||
set_audio_handle(handle: connection.voice.VoiceClient) {
|
||||
if(this._audio_handle === handle)
|
||||
return;
|
||||
|
||||
this.audioController.onSilence = function () {
|
||||
_this.speaking = false;
|
||||
};
|
||||
this.audioController.initialize();
|
||||
//TODO may ensure that the id is the same?
|
||||
this._audio_handle = handle;
|
||||
if(!handle) {
|
||||
this.speaking = false;
|
||||
return;
|
||||
}
|
||||
|
||||
handle.callback_playback = () => this.speaking = true;
|
||||
handle.callback_stopped = () => this.speaking = false;
|
||||
}
|
||||
|
||||
get_audio_handle() : connection.voice.VoiceClient {
|
||||
return this._audio_handle;
|
||||
}
|
||||
|
||||
get properties() : ClientProperties {
|
||||
|
@ -87,10 +95,6 @@ class ClientEntry {
|
|||
clientUid(){ return this.properties.client_unique_identifier; }
|
||||
clientId(){ return this._clientId; }
|
||||
|
||||
getAudioController() : AudioController {
|
||||
return this.audioController;
|
||||
}
|
||||
|
||||
protected initializeListener(){
|
||||
if(this._listener_initialized) return;
|
||||
this._listener_initialized = true;
|
||||
|
@ -100,8 +104,9 @@ class ClientEntry {
|
|||
this.channelTree.onSelect(this);
|
||||
}
|
||||
});
|
||||
this.tag.click(event => {
|
||||
console.log("Clicked!");
|
||||
|
||||
this.tag.on('click', event => {
|
||||
console.log("I've been clicked!");
|
||||
});
|
||||
|
||||
if(!(this instanceof LocalClientEntry) && !(this instanceof MusicClientEntry))
|
||||
|
@ -126,7 +131,7 @@ class ClientEntry {
|
|||
});
|
||||
}
|
||||
|
||||
this.tag.mousedown(event => {
|
||||
this.tag.on('mousedown', event => {
|
||||
if(event.which != 1) return; //Only the left button
|
||||
|
||||
let clients = this.channelTree.currently_selected as (ClientEntry | ClientEntry[]);
|
||||
|
@ -151,9 +156,9 @@ class ClientEntry {
|
|||
cid: target.getChannelId()
|
||||
}).then(event => {
|
||||
if(client.clientId() == this.channelTree.client.clientId)
|
||||
sound.play(Sound.CHANNEL_JOINED);
|
||||
this.channelTree.client.sound.play(Sound.CHANNEL_JOINED);
|
||||
else if(target !== source && target != self.currentChannel())
|
||||
sound.play(Sound.USER_MOVED);
|
||||
this.channelTree.client.sound.play(Sound.USER_MOVED);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -273,21 +278,21 @@ class ClientEntry {
|
|||
name: tr("Show client info"),
|
||||
callback: () => {
|
||||
trigger_close = false;
|
||||
this.channelTree.client.selectInfo.open_popover()
|
||||
this.channelTree.client.select_info.open_popover()
|
||||
},
|
||||
icon: "client-about",
|
||||
visible: this.channelTree.client.selectInfo.is_popover()
|
||||
visible: this.channelTree.client.select_info.is_popover()
|
||||
}, {
|
||||
type: MenuEntryType.HR,
|
||||
visible: this.channelTree.client.selectInfo.is_popover(),
|
||||
visible: this.channelTree.client.select_info.is_popover(),
|
||||
name: ''
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-change_nickname",
|
||||
name: tr("<b>Open text chat</b>"),
|
||||
callback: () => {
|
||||
chat.activeChat = this.chat(true);
|
||||
chat.focus();
|
||||
this.channelTree.client.chat.activeChat = this.chat(true);
|
||||
this.channelTree.client.chat.focus();
|
||||
}
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
|
@ -386,7 +391,7 @@ class ClientEntry {
|
|||
}, {
|
||||
flagset: [data.no_ip ? "no-ip" : "", data.no_hwid ? "no-hardware-id" : "", data.no_name ? "no-nickname" : ""]
|
||||
}).then(() => {
|
||||
sound.play(Sound.USER_BANNED);
|
||||
this.channelTree.client.sound.play(Sound.USER_BANNED);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -415,11 +420,11 @@ class ClientEntry {
|
|||
icon: "client-volume",
|
||||
name: tr("Change Volume"),
|
||||
callback: () => {
|
||||
Modals.spawnChangeVolume(this.audioController.volume, volume => {
|
||||
settings.changeServer("volume_client_" + this.clientUid(), volume);
|
||||
this.audioController.volume = volume;
|
||||
if(globalClient.selectInfo.currentSelected == this)
|
||||
globalClient.selectInfo.update();
|
||||
Modals.spawnChangeVolume(this._audio_handle.get_volume(), volume => {
|
||||
this.channelTree.client.settings.changeServer("volume_client_" + this.clientUid(), volume);
|
||||
this._audio_handle.set_volume(volume);
|
||||
if(this.channelTree.client.select_info.currentSelected == this)
|
||||
this.channelTree.client.select_info.update();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -592,7 +597,7 @@ class ClientEntry {
|
|||
log.table("Client update properties", entries);
|
||||
}
|
||||
|
||||
for(let variable of variables) {
|
||||
for(const variable of variables) {
|
||||
JSON.map_field_to(this._properties, variable.value, variable.key);
|
||||
|
||||
if(variable.key == "client_nickname") {
|
||||
|
@ -602,17 +607,26 @@ class ClientEntry {
|
|||
|
||||
reorder_channel = true;
|
||||
}
|
||||
if(variable.key == "client_away" || variable.key == "client_output_muted" || variable.key == "client_input_hardware" || variable.key == "client_input_muted" || variable.key == "client_is_channel_commander"){
|
||||
if(
|
||||
variable.key == "client_away" ||
|
||||
variable.key == "client_input_hardware" ||
|
||||
variable.key == "client_output_hardware" ||
|
||||
variable.key == "client_output_muted" ||
|
||||
variable.key == "client_input_muted" ||
|
||||
variable.key == "client_is_channel_commander"){
|
||||
update_icon_speech = true;
|
||||
}
|
||||
if(variable.key == "client_away_message" || variable.key == "client_away") {
|
||||
update_away = true;
|
||||
}
|
||||
if(variable.key == "client_unique_identifier") {
|
||||
this.audioController.volume = parseFloat(settings.server("volume_client_" + this.clientUid(), "1"));
|
||||
//TODO tr
|
||||
console.error("Updated volume from config " + this.audioController.volume + " - " + "volume_client_" + this.clientUid() + " - " + settings.server("volume_client_" + this.clientUid(), "1"));
|
||||
console.log(this.avatarId());
|
||||
if(this._audio_handle) {
|
||||
const volume = parseFloat(this.channelTree.client.settings.server("volume_client_" + this.clientUid(), "1"));
|
||||
this._audio_handle.set_volume(volume);
|
||||
log.debug(LogCategory.CLIENT, tr("Loaded client volume %d for client %s from config."), volume, this.clientUid());
|
||||
} else {
|
||||
log.warn(LogCategory.CLIENT, tr("Visible client got unique id assigned, but hasn't yet an audio handle. Ignoring volume assignment."));
|
||||
}
|
||||
}
|
||||
if(variable.key == "client_talk_power") {
|
||||
reorder_channel = true;
|
||||
|
@ -681,26 +695,26 @@ class ClientEntry {
|
|||
|
||||
chat(create: boolean = false) : ChatEntry {
|
||||
let chatName = "client_" + this.clientUid() + ":" + this.clientId();
|
||||
let c = chat.findChat(chatName);
|
||||
if(!c && create) {
|
||||
c = chat.createChat(chatName);
|
||||
c.flag_closeable = true;
|
||||
c.name = this.clientNickName();
|
||||
c.owner_unique_id = this.properties.client_unique_identifier;
|
||||
let chat = this.channelTree.client.chat.findChat(chatName);
|
||||
if(!chat && create) {
|
||||
chat = this.channelTree.client.chat.createChat(chatName);
|
||||
chat.flag_closeable = true;
|
||||
chat.name = this.clientNickName();
|
||||
chat.owner_unique_id = this.properties.client_unique_identifier;
|
||||
|
||||
c.onMessageSend = text => {
|
||||
chat.onMessageSend = text => {
|
||||
this.channelTree.client.serverConnection.command_helper.sendMessage(text, ChatType.CLIENT, this);
|
||||
};
|
||||
|
||||
c.onClose = () => {
|
||||
if(!c.flag_offline)
|
||||
chat.onClose = () => {
|
||||
if(!chat.flag_offline)
|
||||
this.channelTree.client.serverConnection.send_command("clientchatclosed", {"clid": this.clientId()}, {process_result: false}).catch(error => {
|
||||
log.warn(LogCategory.GENERAL, tr("Failed to notify chat participant (%o) that the chat has been closed. Error: %o"), this, error);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
return chat;
|
||||
}
|
||||
|
||||
updateClientIcon() {
|
||||
|
@ -746,10 +760,7 @@ class ClientEntry {
|
|||
} else return group.id == this.assignedChannelGroup();
|
||||
}
|
||||
|
||||
onDelete() {
|
||||
this.audioController.close();
|
||||
this.audioController = undefined;
|
||||
}
|
||||
onDelete() { }
|
||||
|
||||
calculateOnlineTime() : number {
|
||||
return Date.now() / 1000 - this.properties.client_lastconnected;
|
||||
|
@ -796,11 +807,11 @@ class ClientEntry {
|
|||
}
|
||||
|
||||
class LocalClientEntry extends ClientEntry {
|
||||
handle: TSClient;
|
||||
handle: ConnectionHandler;
|
||||
|
||||
private renaming: boolean;
|
||||
|
||||
constructor(handle: TSClient) {
|
||||
constructor(handle: ConnectionHandler) {
|
||||
super(0, "local client");
|
||||
this.handle = handle;
|
||||
}
|
||||
|
@ -866,7 +877,7 @@ class LocalClientEntry extends ClientEntry {
|
|||
}
|
||||
});
|
||||
|
||||
elm.focusout(function (e) {
|
||||
elm.focusout(e => {
|
||||
if(!_self.renaming) return;
|
||||
_self.renaming = false;
|
||||
|
||||
|
@ -878,9 +889,9 @@ class LocalClientEntry extends ClientEntry {
|
|||
|
||||
elm.text(_self.clientNickName());
|
||||
_self.handle.serverConnection.command_helper.updateClient("client_nickname", text).then((e) => {
|
||||
chat.serverChat().appendMessage(tr("Nickname successfully changed"));
|
||||
this.channelTree.client.chat.serverChat().appendMessage(tr("Nickname successfully changed"));
|
||||
}).catch((e: CommandResult) => {
|
||||
chat.serverChat().appendError(tr("Could not change nickname ({})"), e.extra_message);
|
||||
this.channelTree.client.chat.serverChat().appendError(tr("Could not change nickname ({})"), e.extra_message);
|
||||
_self.openRename();
|
||||
});
|
||||
});
|
||||
|
@ -938,13 +949,13 @@ class MusicClientEntry extends ClientEntry {
|
|||
name: tr("Show bot info"),
|
||||
callback: () => {
|
||||
trigger_close = false;
|
||||
this.channelTree.client.selectInfo.open_popover()
|
||||
this.channelTree.client.select_info.open_popover()
|
||||
},
|
||||
icon: "client-about",
|
||||
visible: this.channelTree.client.selectInfo.is_popover()
|
||||
visible: this.channelTree.client.select_info.is_popover()
|
||||
}, {
|
||||
type: MenuEntryType.HR,
|
||||
visible: this.channelTree.client.selectInfo.is_popover(),
|
||||
visible: this.channelTree.client.select_info.is_popover(),
|
||||
name: ''
|
||||
}, {
|
||||
name: tr("<b>Change bot name</b>"),
|
||||
|
@ -1065,11 +1076,11 @@ class MusicClientEntry extends ClientEntry {
|
|||
icon: "client-volume",
|
||||
name: tr("Change local volume"),
|
||||
callback: () => {
|
||||
Modals.spawnChangeVolume(this.audioController.volume, volume => {
|
||||
settings.changeServer("volume_client_" + this.clientUid(), volume);
|
||||
this.audioController.volume = volume;
|
||||
if(globalClient.selectInfo.currentSelected == this)
|
||||
(<MusicInfoManager>globalClient.selectInfo.current_manager()).update_local_volume(volume);
|
||||
Modals.spawnChangeVolume(this._audio_handle.get_volume(), volume => {
|
||||
this.channelTree.client.settings.changeServer("volume_client_" + this.clientUid(), volume);
|
||||
this._audio_handle.set_volume(volume);
|
||||
if(this.channelTree.client.select_info.currentSelected == this)
|
||||
(<MusicInfoManager>this.channelTree.client.select_info.current_manager()).update_local_volume(volume);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -1087,8 +1098,8 @@ class MusicClientEntry extends ClientEntry {
|
|||
clid: this.clientId(),
|
||||
player_volume: value,
|
||||
}).then(() => {
|
||||
if(globalClient.selectInfo.currentSelected == this)
|
||||
(<MusicInfoManager>globalClient.selectInfo.current_manager()).update_remote_volume(value);
|
||||
if(this.channelTree.client.select_info.currentSelected == this)
|
||||
(<MusicInfoManager>this.channelTree.client.select_info.current_manager()).update_remote_volume(value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -79,6 +79,9 @@ if(!$.fn.dividerfy) {
|
|||
$(document.documentElement).css("user-select", "");
|
||||
|
||||
element.removeClass("seperator-selected");
|
||||
|
||||
next_element.find("[x-divider-require-resize]").trigger('resize');
|
||||
previous_element.find("[x-divider-require-resize]").trigger('resize');
|
||||
};
|
||||
|
||||
element.on('mousedown', () => {
|
|
@ -1,12 +1,13 @@
|
|||
let context_menu: JQuery;
|
||||
|
||||
$(document).bind("mousedown", function (e) {
|
||||
$(document).bind("click", function (e) {
|
||||
let menu = context_menu || (context_menu = $(".context-menu"));
|
||||
|
||||
if(!menu.is(":visible")) return;
|
||||
|
||||
if ($(e.target).parents(".context-menu").length == 0) {
|
||||
despawn_context_menu();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
/// <reference path="../i18n/localize.ts" />
|
||||
/// <reference path="../../i18n/localize.ts" />
|
||||
|
||||
interface JQuery<TElement = HTMLElement> {
|
||||
asTabWidget(copy?: boolean) : JQuery<TElement>;
|
||||
|
@ -110,7 +110,7 @@ var TabFunctions = {
|
|||
tag.on('tab.resize', update_height);
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(!$.fn.asTabWidget) {
|
||||
$.fn.asTabWidget = function (copy?: boolean) : JQuery {
|
|
@ -1,4 +1,4 @@
|
|||
/// <reference path="../../client.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../modal/ModalSettings.ts" />
|
||||
/// <reference path="../modal/ModalBanList.ts" />
|
||||
/*
|
||||
|
@ -13,37 +13,72 @@
|
|||
client_away_message Value: ''
|
||||
*/
|
||||
|
||||
let control_bar: ControlBar; /* global variable to access the control bar */
|
||||
|
||||
type MicrophoneState = "disabled" | "muted" | "enabled";
|
||||
type HeadphoneState = "muted" | "enabled";
|
||||
type AwayState = "away-global" | "away" | "online";
|
||||
class ControlBar {
|
||||
private _muteInput: boolean;
|
||||
private _muteOutput: boolean;
|
||||
private _away: boolean;
|
||||
private _query_visible: boolean;
|
||||
private _awayMessage: string;
|
||||
private _channel_subscribe_all: boolean;
|
||||
private _button_away_active: AwayState;
|
||||
private _button_microphone: MicrophoneState;
|
||||
private _button_speakers: HeadphoneState;
|
||||
private _button_subscribe_all: boolean;
|
||||
private _button_query_visible: boolean;
|
||||
|
||||
private codec_supported: boolean = false;
|
||||
private support_playback: boolean = false;
|
||||
private support_record: boolean = false;
|
||||
private connection_handler: ConnectionHandler | undefined;
|
||||
|
||||
readonly handle: TSClient;
|
||||
htmlTag: JQuery;
|
||||
|
||||
constructor(handle: TSClient, htmlTag: JQuery) {
|
||||
this.handle = handle;
|
||||
constructor(htmlTag: JQuery) {
|
||||
this.htmlTag = htmlTag;
|
||||
}
|
||||
|
||||
initialise() {
|
||||
this.htmlTag.find(".btn_connect").on('click', this.onConnect.bind(this));
|
||||
this.htmlTag.find(".btn_disconnect").on('click', this.onDisconnect.bind(this));
|
||||
this.htmlTag.find(".btn_mute_input").on('click', this.onInputMute.bind(this));
|
||||
this.htmlTag.find(".btn_mute_output").on('click', this.onOutputMute.bind(this));
|
||||
this.htmlTag.find(".btn_open_settings").on('click', this.onOpenSettings.bind(this));
|
||||
this.htmlTag.find(".btn_permissions").on('click', this.onPermission.bind(this));
|
||||
this.htmlTag.find(".btn_banlist").on('click', this.onBanlist.bind(this));
|
||||
this.htmlTag.find(".button-subscribe-mode").on('click', this.on_toggle_channel_subscribe_all.bind(this));
|
||||
this.htmlTag.find(".button-playlist-manage").on('click', this.on_playlist_manage.bind(this));
|
||||
initialize_connection_handler_state(handler?: ConnectionHandler) {
|
||||
/* setup the state like the last displayed one */
|
||||
handler.client_status.output_muted = this._button_speakers === "muted";
|
||||
handler.client_status.input_muted = this._button_microphone === "muted";
|
||||
|
||||
handler.client_status.channel_subscribe_all = this._button_subscribe_all;
|
||||
handler.client_status.queries_visible = this._button_query_visible;
|
||||
}
|
||||
|
||||
set_connection_handler(handler?: ConnectionHandler) {
|
||||
if(this.connection_handler == handler)
|
||||
return;
|
||||
|
||||
this.connection_handler = handler;
|
||||
this.apply_server_state();
|
||||
}
|
||||
|
||||
apply_server_state() {
|
||||
if(!this.connection_handler)
|
||||
return;
|
||||
|
||||
|
||||
const flag_away = typeof(this.connection_handler.client_status.away) === "string" || this.connection_handler.client_status.away;
|
||||
if(!flag_away)
|
||||
this.button_away_active = "online";
|
||||
else if(flag_away && this._button_away_active === "online")
|
||||
this.button_away_active = "away";
|
||||
|
||||
this.button_query_visible = this.connection_handler.client_status.queries_visible;
|
||||
this.button_subscribe_all = this.connection_handler.client_status.channel_subscribe_all;
|
||||
|
||||
this.apply_server_voice_state();
|
||||
}
|
||||
|
||||
apply_server_voice_state() {
|
||||
if(!this.connection_handler)
|
||||
return;
|
||||
|
||||
this.button_microphone = !this.connection_handler.client_status.input_hardware ? "disabled" : this.connection_handler.client_status.input_muted ? "muted" : "enabled";
|
||||
this.button_speaker = this.connection_handler.client_status.output_muted ? "muted" : "enabled";
|
||||
}
|
||||
|
||||
current_connection_handler() {
|
||||
return this.connection_handler;
|
||||
}
|
||||
|
||||
initialise() {
|
||||
let dropdownify = (tag: JQuery) => {
|
||||
tag.find(".button-dropdown").on('click', () => {
|
||||
tag.addClass("displayed");
|
||||
|
@ -58,201 +93,293 @@ class ControlBar {
|
|||
tag.removeClass("displayed");
|
||||
});
|
||||
};
|
||||
{
|
||||
let tokens = this.htmlTag.find(".btn_token");
|
||||
dropdownify(tokens);
|
||||
|
||||
tokens.find(".btn_token_use").on('click', this.on_token_use.bind(this));
|
||||
tokens.find(".btn_token_list").on('click', this.on_token_list.bind(this));
|
||||
this.htmlTag.find(".btn_connect").on('click', this.on_open_connect.bind(this));
|
||||
this.htmlTag.find(".btn_disconnect").on('click', this.on_execute_disconnect.bind(this));
|
||||
|
||||
this.htmlTag.find(".btn_mute_input").on('click', this.on_toggle_microphone.bind(this));
|
||||
this.htmlTag.find(".btn_mute_output").on('click', this.on_toggle_sound.bind(this));
|
||||
this.htmlTag.find(".button-subscribe-mode").on('click', this.on_toggle_channel_subscribe.bind(this));
|
||||
this.htmlTag.find(".btn_query_toggle").on('click', this.on_toggle_query_view.bind(this));
|
||||
|
||||
this.htmlTag.find(".btn_open_settings").on('click', this.on_open_settings.bind(this));
|
||||
this.htmlTag.find(".btn_permissions").on('click', this.on_open_permissions.bind(this));
|
||||
this.htmlTag.find(".btn_banlist").on('click', this.on_open_banslist.bind(this));
|
||||
this.htmlTag.find(".button-playlist-manage").on('click', this.on_open_playlist_manage.bind(this));
|
||||
|
||||
this.htmlTag.find(".btn_token_use").on('click', this.on_token_use.bind(this));
|
||||
this.htmlTag.find(".btn_token_list").on('click', this.on_token_list.bind(this));
|
||||
|
||||
|
||||
{
|
||||
this.htmlTag.find(".btn_away_disable").on('click', this.on_away_disable.bind(this));
|
||||
this.htmlTag.find(".btn_away_disable_global").on('click', this.on_away_disable_global.bind(this));
|
||||
|
||||
this.htmlTag.find(".btn_away_enable").on('click', this.on_away_enable.bind(this));
|
||||
this.htmlTag.find(".btn_away_enable_global").on('click', this.on_away_enable_global.bind(this));
|
||||
|
||||
this.htmlTag.find(".btn_away_message").on('click', this.on_away_set_message.bind(this));
|
||||
this.htmlTag.find(".btn_away_message_global").on('click', this.on_away_set_message_global.bind(this));
|
||||
|
||||
this.htmlTag.find(".btn_away_toggle").on('click', this.on_away_toggle.bind(this));
|
||||
}
|
||||
|
||||
dropdownify(this.htmlTag.find(".container-disconnect"));
|
||||
dropdownify(this.htmlTag.find(".btn_token"));
|
||||
dropdownify(this.htmlTag.find(".btn_away"));
|
||||
dropdownify(this.htmlTag.find(".btn_bookmark"));
|
||||
dropdownify(this.htmlTag.find(".btn_query"));
|
||||
dropdownify(this.htmlTag.find(".dropdown-audio"));
|
||||
dropdownify(this.htmlTag.find(".dropdown-servertools"));
|
||||
|
||||
{
|
||||
|
||||
}
|
||||
{
|
||||
let away = this.htmlTag.find(".btn_away");
|
||||
dropdownify(away);
|
||||
|
||||
away.find(".btn_away_toggle").on('click', this.on_away_toggle.bind(this));
|
||||
away.find(".btn_away_message").on('click', this.on_away_set_message.bind(this));
|
||||
this.htmlTag.find(".btn_bookmark_list").on('click', this.on_bookmark_manage.bind(this));
|
||||
this.htmlTag.find(".btn_bookmark_add").on('click', this.on_bookmark_server_add.bind(this));
|
||||
|
||||
}
|
||||
{
|
||||
let bookmark = this.htmlTag.find(".btn_bookmark");
|
||||
dropdownify(bookmark);
|
||||
|
||||
bookmark.find(".btn_bookmark_list").on('click', this.on_bookmark_manage.bind(this));
|
||||
bookmark.find(".btn_bookmark_add").on('click', this.on_bookmark_server_add.bind(this));
|
||||
|
||||
this.update_bookmarks();
|
||||
this.update_bookmark_status();
|
||||
}
|
||||
{
|
||||
let query = this.htmlTag.find(".btn_query");
|
||||
dropdownify(query);
|
||||
|
||||
/* search for query buttons not only on the large device button */
|
||||
this.htmlTag.find(".btn_query_toggle").on('click', this.on_query_visibility_toggle.bind(this));
|
||||
this.htmlTag.find(".btn_query_create").on('click', this.on_query_create.bind(this));
|
||||
this.htmlTag.find(".btn_query_manage").on('click', this.on_query_manage.bind(this));
|
||||
this.htmlTag.find(".btn_query_create").on('click', this.on_open_query_create.bind(this));
|
||||
this.htmlTag.find(".btn_query_manage").on('click', this.on_open_query_manage.bind(this));
|
||||
}
|
||||
|
||||
/* Mobile dropdowns */
|
||||
{
|
||||
const dropdown = this.htmlTag.find(".dropdown-audio");
|
||||
dropdownify(dropdown);
|
||||
dropdown.find(".button-display").on('click', () => dropdown.addClass("displayed"));
|
||||
}
|
||||
{
|
||||
const dropdown = this.htmlTag.find(".dropdown-servertools");
|
||||
dropdownify(dropdown);
|
||||
dropdown.find(".button-display").on('click', () => dropdown.addClass("displayed"));
|
||||
}
|
||||
|
||||
this.update_bookmarks();
|
||||
this.update_bookmark_status();
|
||||
|
||||
//Need an initialise
|
||||
this.muteInput = settings.static_global(Settings.KEY_CONTROL_MUTE_INPUT, false);
|
||||
this.muteOutput = settings.static_global(Settings.KEY_CONTROL_MUTE_OUTPUT, false);
|
||||
this.query_visible = settings.static_global(Settings.KEY_CONTROL_SHOW_QUERIES, false);
|
||||
this.channel_subscribe_all = settings.static_global(Settings.KEY_CONTROL_CHANNEL_SUBSCRIBE_ALL, true);
|
||||
this.button_speaker = settings.static_global(Settings.KEY_CONTROL_MUTE_OUTPUT, false) ? "muted" : "enabled";
|
||||
this.button_microphone = settings.static_global(Settings.KEY_CONTROL_MUTE_INPUT, false) ? "muted" : "enabled";
|
||||
this.button_subscribe_all = true;
|
||||
this.button_query_visible = false;
|
||||
}
|
||||
|
||||
|
||||
on_away_toggle() {
|
||||
this._awayMessage = "";
|
||||
this.away = !this._away;
|
||||
|
||||
/* Update the UI */
|
||||
set button_away_active(flag: AwayState) {
|
||||
if(this._button_away_active === flag)
|
||||
return;
|
||||
this._button_away_active = flag;
|
||||
this.update_button_away();
|
||||
}
|
||||
update_button_away() {
|
||||
const button_away_enable = this.htmlTag.find(".btn_away_enable");
|
||||
const button_away_disable = this.htmlTag.find(".btn_away_disable");
|
||||
const button_away_toggle = this.htmlTag.find(".btn_away_toggle");
|
||||
|
||||
const button_away_disable_global = this.htmlTag.find(".btn_away_disable_global");
|
||||
const button_away_enable_global = this.htmlTag.find(".btn_away_enable_global");
|
||||
const button_away_message_global = this.htmlTag.find(".btn_away_message_global");
|
||||
|
||||
button_away_toggle.toggleClass("activated", this._button_away_active !== "online");
|
||||
button_away_enable.toggle(this._button_away_active === "online");
|
||||
button_away_disable.toggle(this._button_away_active !== "online");
|
||||
|
||||
const connections = server_connections.server_connection_handlers();
|
||||
if(connections.length <= 1) {
|
||||
button_away_disable_global.hide();
|
||||
button_away_enable_global.hide();
|
||||
button_away_message_global.hide();
|
||||
} else {
|
||||
button_away_message_global.show();
|
||||
button_away_enable_global.toggle(server_connections.server_connection_handlers().filter(e => !e.client_status.away).length > 0);
|
||||
button_away_disable_global.toggle(
|
||||
this._button_away_active === "away-global" ||
|
||||
server_connections.server_connection_handlers().filter(e => typeof(e.client_status.away) === "string" || e.client_status.away).length > 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
on_away_set_message() {
|
||||
createInputModal(tr("Set away message"), tr("Please enter the away message"), message => true, message => {
|
||||
if(message)
|
||||
this.away = message;
|
||||
}).open();
|
||||
}
|
||||
|
||||
onInputMute() {
|
||||
this.muteInput = !this._muteInput;
|
||||
}
|
||||
|
||||
onOutputMute() {
|
||||
this.muteOutput = !this._muteOutput;
|
||||
}
|
||||
|
||||
set muteInput(flag: boolean) {
|
||||
if(this._muteInput == flag) return;
|
||||
this._muteInput = flag;
|
||||
set button_microphone(state: MicrophoneState) {
|
||||
if(this._button_microphone === state)
|
||||
return;
|
||||
this._button_microphone = state;
|
||||
|
||||
let tag = this.htmlTag.find(".btn_mute_input");
|
||||
const tag_icon = tag.find(".icon_x32, .icon");
|
||||
|
||||
tag.toggleClass('activated', flag)
|
||||
tag.toggleClass('activated', state === "muted");
|
||||
|
||||
tag_icon
|
||||
.toggleClass('client-input_muted', flag)
|
||||
.toggleClass('client-capture', !flag);
|
||||
.toggleClass('client-input_muted', state === "muted")
|
||||
.toggleClass('client-capture', state === "enabled")
|
||||
.toggleClass('client-activate_microphone', state === "disabled");
|
||||
|
||||
|
||||
if(this.handle.serverConnection.connected())
|
||||
this.handle.serverConnection.send_command("clientupdate", {
|
||||
client_input_muted: this._muteInput
|
||||
});
|
||||
settings.changeGlobal(Settings.KEY_CONTROL_MUTE_INPUT, this._muteInput);
|
||||
this.updateMicrophoneRecordState();
|
||||
if(state === "disabled")
|
||||
tag_icon.attr('title', tr("Enable your microphone on this server"));
|
||||
else if(state === "enabled")
|
||||
tag_icon.attr('title', tr("Mute microphone"));
|
||||
else
|
||||
tag_icon.attr('title', tr("Unmute microphone"));
|
||||
}
|
||||
|
||||
get muteOutput() : boolean { return this._muteOutput; }
|
||||
|
||||
set muteOutput(flag: boolean) {
|
||||
if(this._muteOutput == flag) return;
|
||||
this._muteOutput = flag;
|
||||
|
||||
set button_speaker(state: HeadphoneState) {
|
||||
if(this._button_speakers === state)
|
||||
return;
|
||||
this._button_speakers = state;
|
||||
|
||||
let tag = this.htmlTag.find(".btn_mute_output");
|
||||
const tag_icon = tag.find(".icon_x32, .icon");
|
||||
|
||||
tag.toggleClass('activated', flag)
|
||||
|
||||
tag.toggleClass('activated', state === "muted");
|
||||
tag_icon
|
||||
.toggleClass('client-output_muted', flag)
|
||||
.toggleClass('client-volume', !flag);
|
||||
.toggleClass('client-output_muted', state !== "enabled")
|
||||
.toggleClass('client-volume', state === "enabled");
|
||||
|
||||
if(this.handle.serverConnection.connected())
|
||||
this.handle.serverConnection.send_command("clientupdate", {
|
||||
client_output_muted: this._muteOutput
|
||||
});
|
||||
settings.changeGlobal(Settings.KEY_CONTROL_MUTE_OUTPUT, this._muteOutput);
|
||||
this.updateMicrophoneRecordState();
|
||||
if(state === "enabled")
|
||||
tag_icon.attr('title', tr("Mute sound"));
|
||||
else
|
||||
tag_icon.attr('title', tr("Unmute sound"));
|
||||
}
|
||||
|
||||
set away(value: boolean | string) {
|
||||
if(typeof(value) == "boolean") {
|
||||
if(this._away == value) return;
|
||||
this._away = value;
|
||||
this._awayMessage = "";
|
||||
} else {
|
||||
this._awayMessage = value;
|
||||
this._away = true;
|
||||
set button_subscribe_all(state: boolean) {
|
||||
if(this._button_subscribe_all === state)
|
||||
return;
|
||||
this._button_subscribe_all = state;
|
||||
|
||||
this.htmlTag
|
||||
.find(".button-subscribe-mode")
|
||||
.toggleClass('activated', this._button_subscribe_all)
|
||||
.find('.icon_x32')
|
||||
.toggleClass('client-unsubscribe_from_all_channels', !this._button_subscribe_all)
|
||||
.toggleClass('client-subscribe_to_all_channels', this._button_subscribe_all);
|
||||
}
|
||||
|
||||
set button_query_visible(state: boolean) {
|
||||
if(this._button_query_visible === state)
|
||||
return;
|
||||
this._button_query_visible = state;
|
||||
|
||||
const button = this.htmlTag.find(".btn_query_toggle");
|
||||
button.toggleClass('activated', this._button_query_visible);
|
||||
if(this._button_query_visible)
|
||||
button.find(".query-text").text(tr("Hide server queries"));
|
||||
else
|
||||
button.find(".query-text").text(tr("Show server queries"));
|
||||
}
|
||||
|
||||
/* UI listener */
|
||||
private on_away_toggle() {
|
||||
if(this._button_away_active === "away" || this._button_away_active === "away-global")
|
||||
this.button_away_active = "online";
|
||||
else
|
||||
this.button_away_active = "away";
|
||||
if(this.connection_handler)
|
||||
this.connection_handler.set_away_status(this._button_away_active !== "online");
|
||||
}
|
||||
|
||||
private on_away_enable() {
|
||||
this.button_away_active = "away";
|
||||
if(this.connection_handler)
|
||||
this.connection_handler.set_away_status(true);
|
||||
}
|
||||
|
||||
private on_away_disable() {
|
||||
this.button_away_active = "online";
|
||||
if(this.connection_handler)
|
||||
this.connection_handler.set_away_status(false);
|
||||
}
|
||||
|
||||
private on_away_set_message() {
|
||||
createInputModal(tr("Set away message"), tr("Please enter your away message"), message => true, message => {
|
||||
if(typeof(message) === "string") {
|
||||
this.button_away_active = "away";
|
||||
if(this.connection_handler)
|
||||
this.connection_handler.set_away_status(message);
|
||||
}
|
||||
}).open();
|
||||
}
|
||||
|
||||
private on_away_enable_global() {
|
||||
this.button_away_active = "away-global";
|
||||
for(const connection of server_connections.server_connection_handlers())
|
||||
connection.set_away_status(true);
|
||||
}
|
||||
|
||||
private on_away_disable_global() {
|
||||
this.button_away_active = "online";
|
||||
for(const connection of server_connections.server_connection_handlers())
|
||||
connection.set_away_status(false);
|
||||
}
|
||||
|
||||
private on_away_set_message_global() {
|
||||
createInputModal(tr("Set global away message"), tr("Please enter your global away message"), message => true, message => {
|
||||
if(typeof(message) === "string") {
|
||||
this.button_away_active = "away";
|
||||
for(const connection of server_connections.server_connection_handlers())
|
||||
connection.set_away_status(message);
|
||||
}
|
||||
}).open();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private on_toggle_microphone() {
|
||||
if(this._button_microphone === "disabled" || this._button_microphone === "muted")
|
||||
this.button_microphone = "enabled";
|
||||
else
|
||||
this.button_microphone = "muted";
|
||||
|
||||
if(this.connection_handler) {
|
||||
this.connection_handler.client_status.input_muted = this._button_microphone !== "enabled";
|
||||
if(!this.connection_handler.client_status.input_hardware)
|
||||
this.connection_handler.acquire_recorder(voice_recoder, true); /* acquire_recorder already updates the voice status */
|
||||
else
|
||||
this.connection_handler.update_voice_status(undefined);
|
||||
|
||||
/* just update the last changed value */
|
||||
settings.changeGlobal(Settings.KEY_CONTROL_MUTE_INPUT, this.connection_handler.client_status.input_muted)
|
||||
}
|
||||
}
|
||||
|
||||
let tag = this.htmlTag.find(".btn_away_toggle");
|
||||
if( this._away) {
|
||||
tag.addClass("activated");
|
||||
} else {
|
||||
tag.removeClass("activated");
|
||||
private on_toggle_sound() {
|
||||
if(this._button_speakers === "muted")
|
||||
this.button_speaker = "enabled";
|
||||
else
|
||||
this.button_speaker = "muted";
|
||||
|
||||
if(this.connection_handler) {
|
||||
this.connection_handler.client_status.output_muted = this._button_speakers !== "enabled";
|
||||
this.connection_handler.update_voice_status(undefined);
|
||||
|
||||
/* just update the last changed value */
|
||||
settings.changeGlobal(Settings.KEY_CONTROL_MUTE_OUTPUT, this.connection_handler.client_status.output_muted)
|
||||
}
|
||||
|
||||
if(this.handle.serverConnection.connected)
|
||||
this.handle.serverConnection.send_command("clientupdate", {
|
||||
client_away: this._away,
|
||||
client_away_message: this._awayMessage
|
||||
});
|
||||
this.updateMicrophoneRecordState();
|
||||
}
|
||||
|
||||
private updateMicrophoneRecordState() {
|
||||
let enabled = !this._muteInput && !this._muteOutput && !this._away;
|
||||
if(this.handle.voiceConnection)
|
||||
this.handle.voiceConnection.voiceRecorder.update(enabled);
|
||||
private on_toggle_channel_subscribe() {
|
||||
this.button_subscribe_all = !this._button_subscribe_all;
|
||||
if(this.connection_handler) {
|
||||
this.connection_handler.client_status.channel_subscribe_all = this._button_subscribe_all;
|
||||
if(this._button_subscribe_all)
|
||||
this.connection_handler.channelTree.subscribe_all_channels();
|
||||
else
|
||||
this.connection_handler.channelTree.unsubscribe_all_channels(true);
|
||||
this.connection_handler.settings.changeServer(Settings.KEY_CONTROL_CHANNEL_SUBSCRIBE_ALL, this._button_subscribe_all);
|
||||
}
|
||||
}
|
||||
|
||||
updateProperties() {
|
||||
if(this.handle.serverConnection.connected)
|
||||
this.handle.serverConnection.send_command("clientupdate", {
|
||||
client_input_muted: this._muteInput,
|
||||
client_output_muted: this._muteOutput,
|
||||
client_away: this._away,
|
||||
client_away_message: this._awayMessage,
|
||||
client_input_hardware: this.codec_supported && this.support_record,
|
||||
client_output_hardware: this.codec_supported && this.support_playback
|
||||
});
|
||||
private on_toggle_query_view() {
|
||||
this.button_query_visible = !this._button_query_visible;
|
||||
if(this.connection_handler) {
|
||||
this.connection_handler.client_status.queries_visible = this._button_query_visible;
|
||||
this.connection_handler.channelTree.toggle_server_queries(this._button_query_visible);
|
||||
this.connection_handler.settings.changeServer(Settings.KEY_CONTROL_SHOW_QUERIES, this._button_subscribe_all);
|
||||
}
|
||||
}
|
||||
|
||||
updateVoice(targetChannel?: ChannelEntry) {
|
||||
if(!targetChannel)
|
||||
targetChannel = this.handle.getClient().currentChannel();
|
||||
let client = this.handle.getClient();
|
||||
|
||||
this.codec_supported = targetChannel ? this.handle.voiceConnection && this.handle.voiceConnection.codecSupported(targetChannel.properties.channel_codec) : true;
|
||||
this.support_record = this.handle.voiceConnection && this.handle.voiceConnection.voice_send_support();
|
||||
this.support_playback = this.handle.voiceConnection && this.handle.voiceConnection.voice_playback_support();
|
||||
|
||||
this.htmlTag.find(".btn_mute_input").prop("disabled", !this.codec_supported|| !this.support_playback || !this.support_record);
|
||||
this.htmlTag.find(".btn_mute_output").prop("disabled", !this.codec_supported || !this.support_playback);
|
||||
this.handle.serverConnection.send_command("clientupdate", {
|
||||
client_input_hardware: this.codec_supported && this.support_record,
|
||||
client_output_hardware: this.codec_supported && this.support_playback
|
||||
});
|
||||
|
||||
if(!this.codec_supported)
|
||||
createErrorModal(tr("Channel codec unsupported"), tr("This channel has an unsupported codec.<br>You cant speak or listen to anybody within this channel!")).open();
|
||||
|
||||
/* Update these properties anyways (for case the server fails to handle the command) */
|
||||
client.updateVariables(
|
||||
{key: "client_input_hardware", value: (this.codec_supported && this.support_record) + ""},
|
||||
{key: "client_output_hardware", value: (this.codec_supported && this.support_playback) + ""}
|
||||
);
|
||||
}
|
||||
|
||||
private onOpenSettings() {
|
||||
private on_open_settings() {
|
||||
Modals.spawnSettingsModal();
|
||||
}
|
||||
|
||||
private onConnect() {
|
||||
this.handle.cancel_reconnect();
|
||||
private on_open_connect() {
|
||||
if(this.connection_handler)
|
||||
this.connection_handler.cancel_reconnect();
|
||||
Modals.spawnConnectModal({
|
||||
url: "ts.TeaSpeak.de",
|
||||
enforce: false
|
||||
|
@ -260,31 +387,31 @@ class ControlBar {
|
|||
}
|
||||
|
||||
update_connection_state() {
|
||||
switch (this.handle.serverConnection ? this.handle.serverConnection._connectionState : ConnectionState.UNCONNECTED) {
|
||||
switch (this.connection_handler.serverConnection ? this.connection_handler.serverConnection._connectionState : ConnectionState.UNCONNECTED) {
|
||||
case ConnectionState.CONNECTED:
|
||||
case ConnectionState.CONNECTING:
|
||||
case ConnectionState.INITIALISING:
|
||||
this.htmlTag.find(".btn_disconnect").show();
|
||||
this.htmlTag.find(".btn_connect").hide();
|
||||
this.htmlTag.find(".container-disconnect").show();
|
||||
this.htmlTag.find(".container-connect").hide();
|
||||
break;
|
||||
default:
|
||||
this.htmlTag.find(".btn_disconnect").hide();
|
||||
this.htmlTag.find(".btn_connect").show();
|
||||
this.htmlTag.find(".container-disconnect").hide();
|
||||
this.htmlTag.find(".container-connect").show();
|
||||
}
|
||||
}
|
||||
|
||||
private onDisconnect() {
|
||||
this.handle.cancel_reconnect();
|
||||
this.handle.handleDisconnect(DisconnectReason.REQUESTED); //TODO message?
|
||||
private on_execute_disconnect() {
|
||||
this.connection_handler.cancel_reconnect();
|
||||
this.connection_handler.handleDisconnect(DisconnectReason.REQUESTED); //TODO message?
|
||||
this.update_connection_state();
|
||||
sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||
this.connection_handler.sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||
}
|
||||
|
||||
private on_token_use() {
|
||||
createInputModal(tr("Use token"), tr("Please enter your token/priviledge key"), message => message.length > 0, result => {
|
||||
if(!result) return;
|
||||
if(this.handle.serverConnection.connected)
|
||||
this.handle.serverConnection.send_command("tokenuse", {
|
||||
if(this.connection_handler.serverConnection.connected)
|
||||
this.connection_handler.serverConnection.send_command("tokenuse", {
|
||||
token: result
|
||||
}).then(() => {
|
||||
createInfoModal(tr("Use token"), tr("Toke successfully used!")).open();
|
||||
|
@ -299,37 +426,40 @@ class ControlBar {
|
|||
createErrorModal(tr("Not implemented"), tr("Token list is not implemented yet!")).open();
|
||||
}
|
||||
|
||||
private onPermission() {
|
||||
private on_open_permissions() {
|
||||
let button = this.htmlTag.find(".btn_permissions");
|
||||
button.addClass("activated");
|
||||
setTimeout(() => {
|
||||
Modals.spawnPermissionEdit().open();
|
||||
if(this.connection_handler)
|
||||
Modals.spawnPermissionEdit(this.connection_handler).open();
|
||||
else
|
||||
createErrorModal(tr("You have to be connected"), tr("You have to be connected!")).open();
|
||||
button.removeClass("activated");
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private onBanlist() {
|
||||
if(!this.handle.serverConnection) return;
|
||||
private on_open_banslist() {
|
||||
if(!this.connection_handler.serverConnection) return;
|
||||
|
||||
if(this.handle.permissions.neededPermission(PermissionType.B_CLIENT_BAN_LIST).granted(1)) {
|
||||
Modals.openBanList(this.handle);
|
||||
if(this.connection_handler.permissions.neededPermission(PermissionType.B_CLIENT_BAN_LIST).granted(1)) {
|
||||
Modals.openBanList(this.connection_handler);
|
||||
} else {
|
||||
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to view the ban list")).open();
|
||||
sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
|
||||
this.connection_handler.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
|
||||
}
|
||||
}
|
||||
|
||||
private on_bookmark_server_add() {
|
||||
if(globalClient && globalClient.connected) {
|
||||
if(this.connection_handler && this.connection_handler.connected) {
|
||||
createInputModal(tr("Enter bookmarks name"), tr("Please enter the bookmarks name:<br>"), text => true, result => {
|
||||
if(result) {
|
||||
const bookmark = bookmarks.create_bookmark(result as string, bookmarks.bookmarks(), {
|
||||
server_port: globalClient.serverConnection._remote_address.port,
|
||||
server_address: globalClient.serverConnection._remote_address.host,
|
||||
server_port: this.connection_handler.serverConnection._remote_address.port,
|
||||
server_address: this.connection_handler.serverConnection._remote_address.host,
|
||||
|
||||
server_password: "",
|
||||
server_password_hash: ""
|
||||
}, globalClient.getClient().clientNickName());
|
||||
}, this.connection_handler.getClient().clientNickName());
|
||||
bookmarks.save_bookmark(bookmark);
|
||||
this.update_bookmarks()
|
||||
}
|
||||
|
@ -353,6 +483,34 @@ class ControlBar {
|
|||
const build_entry = (bookmark: bookmarks.DirectoryBookmark | bookmarks.Bookmark) => {
|
||||
if(bookmark.type == bookmarks.BookmarkType.ENTRY) {
|
||||
const mark = <bookmarks.Bookmark>bookmark;
|
||||
|
||||
const bookmark_connect = (new_tab: boolean) => {
|
||||
this.htmlTag.find(".btn_bookmark").find(".dropdown").removeClass("displayed"); //FIXME Not working
|
||||
|
||||
const profile = profiles.find_profile(mark.connect_profile) || profiles.default_profile();
|
||||
if(profile.valid()) {
|
||||
const connection = this.connection_handler && !new_tab ? this.connection_handler : server_connections.spawn_server_connection_handler();
|
||||
server_connections.set_active_connection_handler(connection);
|
||||
connection.startConnection(
|
||||
mark.server_properties.server_address + ":" + mark.server_properties.server_port,
|
||||
profile,
|
||||
mark.nickname,
|
||||
{
|
||||
password: mark.server_properties.server_password_hash,
|
||||
hashed: true
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Modals.spawnConnectModal({
|
||||
url: mark.server_properties.server_address + ":" + mark.server_properties.server_port,
|
||||
enforce: true
|
||||
}, {
|
||||
profile: profile,
|
||||
enforce: true
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
return $.spawn("div")
|
||||
.addClass("bookmark")
|
||||
.append(
|
||||
|
@ -363,27 +521,32 @@ class ControlBar {
|
|||
.addClass("name")
|
||||
.text(bookmark.display_name)
|
||||
.on('click', event => {
|
||||
this.htmlTag.find(".btn_bookmark").find(".dropdown").removeClass("displayed");
|
||||
const profile = profiles.find_profile(mark.connect_profile) || profiles.default_profile();
|
||||
if(profile.valid()) {
|
||||
this.handle.startConnection(
|
||||
mark.server_properties.server_address + ":" + mark.server_properties.server_port,
|
||||
profile,
|
||||
mark.nickname,
|
||||
{
|
||||
password: mark.server_properties.server_password_hash,
|
||||
hashed: true
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Modals.spawnConnectModal({
|
||||
url: mark.server_properties.server_address + ":" + mark.server_properties.server_port,
|
||||
enforce: true
|
||||
}, {
|
||||
profile: profile,
|
||||
enforce: true
|
||||
})
|
||||
}
|
||||
if(event.isDefaultPrevented())
|
||||
return;
|
||||
bookmark_connect(false);
|
||||
})
|
||||
.on('contextmenu', event => {
|
||||
if(event.isDefaultPrevented())
|
||||
return;
|
||||
event.preventDefault();
|
||||
|
||||
spawn_context_menu(event.pageX, event.pageY, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
name: tr("Connect"),
|
||||
icon: 'client-connect',
|
||||
callback: () => bookmark_connect(false)
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
name: tr("Connect in a new tab"),
|
||||
icon: 'client-connect',
|
||||
callback: () => bookmark_connect(true)
|
||||
}, MenuEntry.CLOSE(() => {
|
||||
setTimeout(() => {
|
||||
this.htmlTag.find(".btn_bookmark.button-dropdown").removeClass("force-show")
|
||||
}, 250);
|
||||
}));
|
||||
|
||||
this.htmlTag.find(".btn_bookmark.button-dropdown").addClass("force-show");
|
||||
})
|
||||
)
|
||||
} else {
|
||||
|
@ -423,84 +586,28 @@ class ControlBar {
|
|||
Modals.spawnBookmarkModal();
|
||||
}
|
||||
|
||||
get query_visible() {
|
||||
return this._query_visible;
|
||||
}
|
||||
|
||||
set query_visible(flag: boolean) {
|
||||
if(this._query_visible == flag) return;
|
||||
|
||||
this._query_visible = flag;
|
||||
settings.changeGlobal(Settings.KEY_CONTROL_SHOW_QUERIES, flag);
|
||||
this.update_query_visibility_button();
|
||||
this.handle.channelTree.toggle_server_queries(flag);
|
||||
}
|
||||
|
||||
private on_query_visibility_toggle() {
|
||||
this.query_visible = !this._query_visible;
|
||||
this.update_query_visibility_button();
|
||||
}
|
||||
|
||||
private update_query_visibility_button() {
|
||||
const button = this.htmlTag.find(".btn_query_toggle");
|
||||
button.toggleClass('activated', this._query_visible);
|
||||
if(this._query_visible)
|
||||
button.find(".query-text").text(tr("Hide server queries"));
|
||||
else
|
||||
button.find(".query-text").text(tr("Show server queries"));
|
||||
}
|
||||
|
||||
private on_query_create() {
|
||||
if(this.handle.permissions.neededPermission(PermissionType.B_CLIENT_CREATE_MODIFY_SERVERQUERY_LOGIN).granted(1)) {
|
||||
Modals.spawnQueryCreate();
|
||||
private on_open_query_create() {
|
||||
if(this.connection_handler.permissions.neededPermission(PermissionType.B_CLIENT_CREATE_MODIFY_SERVERQUERY_LOGIN).granted(1)) {
|
||||
Modals.spawnQueryCreate(this.connection_handler);
|
||||
} else {
|
||||
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to create a server query login")).open();
|
||||
sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
|
||||
this.connection_handler.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
|
||||
}
|
||||
}
|
||||
|
||||
private on_query_manage() {
|
||||
if(globalClient && globalClient.connected) {
|
||||
Modals.spawnQueryManage(globalClient);
|
||||
private on_open_query_manage() {
|
||||
if(this.connection_handler && this.connection_handler.connected) {
|
||||
Modals.spawnQueryManage(this.connection_handler);
|
||||
} else {
|
||||
createErrorModal(tr("You have to be connected"), tr("You have to be connected!")).open();
|
||||
}
|
||||
}
|
||||
|
||||
private on_playlist_manage() {
|
||||
if(this.handle && this.handle.connected) {
|
||||
Modals.spawnPlaylistManage(this.handle);
|
||||
private on_open_playlist_manage() {
|
||||
if(this.connection_handler && this.connection_handler.connected) {
|
||||
Modals.spawnPlaylistManage(this.connection_handler);
|
||||
} else {
|
||||
createErrorModal(tr("You have to be connected"), tr("You have to be connected to use this function!")).open();
|
||||
}
|
||||
}
|
||||
|
||||
get channel_subscribe_all() : boolean {
|
||||
return this._channel_subscribe_all;
|
||||
}
|
||||
|
||||
set channel_subscribe_all(flag: boolean) {
|
||||
if(this._channel_subscribe_all == flag)
|
||||
return;
|
||||
|
||||
this._channel_subscribe_all = flag;
|
||||
|
||||
this.htmlTag
|
||||
.find(".button-subscribe-mode")
|
||||
.toggleClass('activated', this._channel_subscribe_all)
|
||||
.find('.icon_x32')
|
||||
.toggleClass('client-unsubscribe_from_all_channels', !this._channel_subscribe_all)
|
||||
.toggleClass('client-subscribe_to_all_channels', this._channel_subscribe_all);
|
||||
|
||||
settings.changeGlobal(Settings.KEY_CONTROL_CHANNEL_SUBSCRIBE_ALL, flag);
|
||||
|
||||
if(flag)
|
||||
this.handle.channelTree.subscribe_all_channels();
|
||||
else
|
||||
this.handle.channelTree.unsubscribe_all_channels();
|
||||
}
|
||||
|
||||
private on_toggle_channel_subscribe_all() {
|
||||
this.channel_subscribe_all = !this.channel_subscribe_all;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/// <reference path="../../client.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../../../vendor/bbcode/xbbcode.ts" />
|
||||
|
||||
abstract class InfoManagerBase {
|
||||
|
@ -50,24 +50,24 @@ abstract class InfoManager<T> extends InfoManagerBase {
|
|||
}
|
||||
|
||||
class InfoBar<AvailableTypes = ServerEntry | ChannelEntry | ClientEntry | undefined> {
|
||||
readonly handle: TSClient;
|
||||
readonly handle: ConnectionHandler;
|
||||
|
||||
private current_selected?: AvailableTypes;
|
||||
|
||||
private _tag: JQuery<HTMLElement>;
|
||||
private _tag_content: JQuery<HTMLElement>;
|
||||
private _tag_info: JQuery<HTMLElement>;
|
||||
private _tag_banner: JQuery<HTMLElement>;
|
||||
private readonly _tag_info: JQuery<HTMLElement>;
|
||||
private readonly _tag_banner: JQuery<HTMLElement>;
|
||||
|
||||
private _current_manager: InfoManagerBase = undefined;
|
||||
private managers: InfoManagerBase[] = [];
|
||||
private banner_manager: Hostbanner;
|
||||
|
||||
constructor(client: TSClient, htmlTag: JQuery<HTMLElement>) {
|
||||
constructor(client: ConnectionHandler) {
|
||||
this.handle = client;
|
||||
this._tag = htmlTag;
|
||||
this._tag_content = htmlTag.find("> .select_info");
|
||||
this._tag_info = this._tag_content.find(".container-select-info");
|
||||
this._tag_banner = this._tag_content.find(".container-banner");
|
||||
|
||||
this._tag = $("#tmpl_select_info").renderTag();
|
||||
this._tag_info = this._tag.find(".container-select-info");
|
||||
this._tag_banner = this._tag.find(".container-banner");
|
||||
|
||||
this.managers.push(new MusicInfoManager());
|
||||
this.managers.push(new ClientInfoManager());
|
||||
|
@ -76,18 +76,22 @@ class InfoBar<AvailableTypes = ServerEntry | ChannelEntry | ClientEntry | undefi
|
|||
|
||||
this.banner_manager = new Hostbanner(client, this._tag_banner);
|
||||
|
||||
this._tag.find("button.close").on('click', () => {
|
||||
this._tag.toggleClass('shown', false);
|
||||
});
|
||||
this._tag.find("button.close").on('click', () => this.close_popover());
|
||||
}
|
||||
|
||||
get_tag() : JQuery {
|
||||
return this._tag;
|
||||
}
|
||||
|
||||
handle_resize() {
|
||||
/* test if the popover isn't a popover anymore */
|
||||
if(this._tag.hasClass('shown')) {
|
||||
this._tag.removeClass('shown');
|
||||
if(this._tag.parent().hasClass('shown')) {
|
||||
this._tag.parent().removeClass('shown');
|
||||
if(this.is_popover())
|
||||
this._tag.addClass('shown');
|
||||
this._tag.parent().addClass('shown');
|
||||
}
|
||||
|
||||
this.banner_manager.handle_resize();
|
||||
}
|
||||
|
||||
setCurrentSelected(entry: AvailableTypes) {
|
||||
|
@ -128,14 +132,21 @@ class InfoBar<AvailableTypes = ServerEntry | ChannelEntry | ClientEntry | undefi
|
|||
|
||||
current_manager() { return this._current_manager; }
|
||||
|
||||
html_tag() { return this._tag_content; }
|
||||
|
||||
is_popover() : boolean {
|
||||
return !this._tag.is(':visible') || this._tag.hasClass('shown');
|
||||
return !this._tag.parent().is(':visible') || this._tag.parent().hasClass('shown');
|
||||
}
|
||||
|
||||
open_popover() {
|
||||
this._tag.toggleClass('shown', true);
|
||||
this._tag.parent().toggleClass('shown', true);
|
||||
this.banner_manager.handle_resize();
|
||||
}
|
||||
|
||||
close_popover() {
|
||||
this._tag.parent().toggleClass('shown', false);
|
||||
}
|
||||
|
||||
rendered_tag() {
|
||||
return this._tag_info;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,12 +157,12 @@ interface Window {
|
|||
|
||||
class Hostbanner {
|
||||
readonly html_tag: JQuery<HTMLElement>;
|
||||
readonly client: TSClient;
|
||||
readonly client: ConnectionHandler;
|
||||
|
||||
private updater: NodeJS.Timer;
|
||||
private _hostbanner_url: string;
|
||||
|
||||
constructor(client: TSClient, htmlTag: JQuery<HTMLElement>) {
|
||||
constructor(client: ConnectionHandler, htmlTag: JQuery<HTMLElement>) {
|
||||
this.client = client;
|
||||
this.html_tag = htmlTag;
|
||||
}
|
||||
|
@ -189,6 +200,10 @@ class Hostbanner {
|
|||
}
|
||||
}
|
||||
|
||||
handle_resize() {
|
||||
this.html_tag.find("[x-divider-require-resize]").trigger('resize');
|
||||
}
|
||||
|
||||
private generate_tag?() : Promise<JQuery<HTMLElement>> {
|
||||
if(!this.client.connected) return undefined;
|
||||
|
||||
|
@ -220,6 +235,43 @@ class Hostbanner {
|
|||
|
||||
const rendered = $("#tmpl_selected_hostbanner").renderTag(properties);
|
||||
|
||||
/* ration watcher */
|
||||
if(server.properties.virtualserver_hostbanner_mode == 2) {
|
||||
const jimage = rendered.find(".meta-image");
|
||||
if(jimage.length == 0) {
|
||||
log.warn(LogCategory.SERVER, tr("Missing hostbanner meta image tag"));
|
||||
} else {
|
||||
const image = jimage[0];
|
||||
image.onload = event => {
|
||||
const image: HTMLImageElement = jimage[0] as any;
|
||||
rendered.on('resize', event => {
|
||||
const container = rendered.parent();
|
||||
container.css('height', null);
|
||||
container.css('flex-grow', '1');
|
||||
|
||||
const max_height = rendered.visible_height();
|
||||
const max_width = rendered.visible_width();
|
||||
container.css('flex-grow', '0');
|
||||
|
||||
|
||||
const original_height = image.naturalHeight;
|
||||
const original_width = image.naturalWidth;
|
||||
|
||||
const ratio_height = max_height / original_height;
|
||||
const ratio_width = max_width / original_width;
|
||||
|
||||
const ratio = Math.min(ratio_height, ratio_width);
|
||||
|
||||
if(ratio == 0)
|
||||
return;
|
||||
const hostbanner_height = ratio * original_height;
|
||||
container.css('height', Math.ceil(hostbanner_height) + "px");
|
||||
/* the width is ignorable*/
|
||||
});
|
||||
setTimeout(() => rendered.trigger('resize'), 100);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if(window.fetch) {
|
||||
return (async () => {
|
||||
|
@ -244,6 +296,7 @@ class Hostbanner {
|
|||
}
|
||||
const url = (this._hostbanner_url = URL.createObjectURL(await result.blob()));
|
||||
tag_image.css('background-image', 'url(' + url + ')');
|
||||
tag_image.attr('src', url);
|
||||
log.debug(LogCategory.SERVER, tr("Fetsched hostbanner successfully (%o, type: %o, url: %o)"), Date.now() - start, result.type, url);
|
||||
} catch(error) {
|
||||
log.warn(LogCategory.SERVER, tr("Failed to fetch hostbanner image: %o"), error);
|
||||
|
@ -288,7 +341,7 @@ class ClientInfoManager extends InfoManager<ClientEntry> {
|
|||
|
||||
properties["client_name"] = client.createChatTag()[0];
|
||||
properties["client_onlinetime"] = formatDate(client.calculateOnlineTime());
|
||||
properties["sound_volume"] = client.audioController.volume * 100;
|
||||
properties["sound_volume"] = client.get_audio_handle() ? client.get_audio_handle().get_volume() * 100 : -1;
|
||||
properties["client_is_query"] = client.properties.client_type == ClientType.CLIENT_QUERY;
|
||||
properties["client_is_web"] = client.properties.client_type_exact == ClientType.CLIENT_WEB;
|
||||
|
||||
|
@ -777,11 +830,11 @@ class MusicInfoManager extends ClientInfoManager {
|
|||
}
|
||||
|
||||
update_local_volume(volume: number) {
|
||||
this.handle.html_tag().find(".property-volume-local").text(Math.floor(volume * 100) + "%");
|
||||
this.handle.rendered_tag().find(".property-volume-local").text(Math.floor(volume * 100) + "%");
|
||||
}
|
||||
|
||||
update_remote_volume(volume: number) {
|
||||
this.handle.html_tag().find(".property-volume-remote").text(Math.floor(volume * 100) + "%")
|
||||
this.handle.rendered_tag().find(".property-volume-remote").text(Math.floor(volume * 100) + "%")
|
||||
}
|
||||
|
||||
available<V>(object: V): boolean {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import LogType = log.LogType;
|
||||
|
||||
enum ChatType {
|
||||
GENERAL,
|
||||
SERVER,
|
||||
|
@ -56,20 +54,34 @@ namespace MessageHelper {
|
|||
|
||||
result.push(...formatElement(pattern.substr(begin, found - begin))); //Append the text
|
||||
|
||||
let number;
|
||||
let offset = 0;
|
||||
while ("0123456789".includes(pattern[found + 1 + offset])) offset++;
|
||||
number = parseInt(offset > 0 ? pattern.substr(found + 1, offset) : "0");
|
||||
if(pattern[found + 1] == ':') {
|
||||
offset++; /* the beginning : */
|
||||
while (pattern[found + 1 + offset] != ':') offset++;
|
||||
const tag = pattern.substr(found + 2, offset - 1);
|
||||
|
||||
if(pattern[found + offset + 1] != '}') {
|
||||
found++;
|
||||
continue;
|
||||
offset++; /* the ending : */
|
||||
if(pattern[found + offset + 1] != '}') {
|
||||
found++;
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push($.spawn(tag as any));
|
||||
} else {
|
||||
let number;
|
||||
while ("0123456789".includes(pattern[found + 1 + offset])) offset++;
|
||||
number = parseInt(offset > 0 ? pattern.substr(found + 1, offset) : "0");
|
||||
if(pattern[found + offset + 1] != '}') {
|
||||
found++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(objects.length < number)
|
||||
log.warn(LogCategory.GENERAL, tr("Message to format contains invalid index (%o)"), number);
|
||||
|
||||
result.push(...formatElement(objects[number]));
|
||||
}
|
||||
|
||||
if(objects.length < number)
|
||||
log.warn(LogCategory.GENERAL, tr("Message to format contains invalid index (%o)"), number);
|
||||
|
||||
result.push(...formatElement(objects[number]));
|
||||
found = found + 1 + offset;
|
||||
begin = found + 1;
|
||||
} while(found++);
|
||||
|
@ -251,9 +263,7 @@ class ChatEntry {
|
|||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-tab_close_button",
|
||||
name: tr("Close"),
|
||||
callback: () => {
|
||||
chat.deleteChat(this);
|
||||
}
|
||||
callback: () => this.handle.deleteChat(this)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -341,6 +351,7 @@ class ChatBox {
|
|||
//static readonly URL_REGEX = /^(?<hostname>([a-zA-Z0-9-]+\.)+[a-zA-Z0-9-]{2,63})(?:\/(?<path>(?:[^\s?]+)?)(?:\?(?<query>\S+))?)?$/gm;
|
||||
static readonly URL_REGEX = /^(([a-zA-Z0-9-]+\.)+[a-zA-Z0-9-]{2,63})(?:\/((?:[^\s?]+)?)(?:\?(\S+))?)?$/gm;
|
||||
|
||||
readonly connection_handler: ConnectionHandler;
|
||||
htmlTag: JQuery;
|
||||
chats: ChatEntry[];
|
||||
private _activeChat: ChatEntry;
|
||||
|
@ -348,9 +359,12 @@ class ChatBox {
|
|||
private _button_send: JQuery;
|
||||
private _input_message: JQuery;
|
||||
|
||||
constructor(htmlTag: JQuery) {
|
||||
this.htmlTag = htmlTag;
|
||||
constructor(connection_handler: ConnectionHandler) {
|
||||
this.connection_handler = connection_handler;
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.htmlTag = $("#tmpl_frame_chat").renderTag();
|
||||
this._button_send = this.htmlTag.find(".button-send");
|
||||
this._input_message = this.htmlTag.find(".input-message");
|
||||
|
||||
|
@ -372,15 +386,15 @@ class ChatBox {
|
|||
this._activeChat = undefined;
|
||||
|
||||
this.createChat("chat_server", ChatType.SERVER).onMessageSend = (text: string) => {
|
||||
if(!globalClient.serverConnection) {
|
||||
chat.serverChat().appendError(tr("Could not send chant message (Not connected)"));
|
||||
if(!this.connection_handler.serverConnection) {
|
||||
this.serverChat().appendError(tr("Could not send chant message (Not connected)"));
|
||||
return;
|
||||
}
|
||||
globalClient.serverConnection.command_helper.sendMessage(text, ChatType.SERVER).catch(error => {
|
||||
this.connection_handler.serverConnection.command_helper.sendMessage(text, ChatType.SERVER).catch(error => {
|
||||
if(error instanceof CommandResult)
|
||||
return;
|
||||
|
||||
chat.serverChat().appendMessage(tr("Failed to send text message."));
|
||||
this.serverChat().appendMessage(tr("Failed to send text message."));
|
||||
log.error(LogCategory.GENERAL, tr("Failed to send server text message: %o"), error);
|
||||
});
|
||||
};
|
||||
|
@ -388,20 +402,20 @@ class ChatBox {
|
|||
this.serverChat().flag_closeable = false;
|
||||
|
||||
this.createChat("chat_channel", ChatType.CHANNEL).onMessageSend = (text: string) => {
|
||||
if(!globalClient.serverConnection) {
|
||||
chat.channelChat().appendError(tr("Could not send chant message (Not connected)"));
|
||||
if(!this.connection_handler.serverConnection) {
|
||||
this.channelChat().appendError(tr("Could not send chant message (Not connected)"));
|
||||
return;
|
||||
}
|
||||
|
||||
globalClient.serverConnection.command_helper.sendMessage(text, ChatType.CHANNEL, globalClient.getClient().currentChannel()).catch(error => {
|
||||
chat.channelChat().appendMessage(tr("Failed to send text message."));
|
||||
this.connection_handler.serverConnection.command_helper.sendMessage(text, ChatType.CHANNEL, this.connection_handler.getClient().currentChannel()).catch(error => {
|
||||
this.channelChat().appendMessage(tr("Failed to send text message."));
|
||||
log.error(LogCategory.GENERAL, tr("Failed to send channel text message: %o"), error);
|
||||
});
|
||||
};
|
||||
this.channelChat().name = tr("Channel chat");
|
||||
this.channelChat().flag_closeable = false;
|
||||
|
||||
globalClient.permissions.initializedListener.push(flag => {
|
||||
this.connection_handler.permissions.initializedListener.push(flag => {
|
||||
if(flag) this.activeChat0(this._activeChat);
|
||||
});
|
||||
}
|
||||
|
@ -492,16 +506,16 @@ class ChatBox {
|
|||
this._activeChat.html_tag.addClass("active");
|
||||
this._activeChat.displayHistory();
|
||||
|
||||
if(!disable_input && globalClient && globalClient.permissions && globalClient.permissions.initialized())
|
||||
if(!disable_input && this.connection_handler && this.connection_handler.permissions && this.connection_handler.permissions.initialized())
|
||||
switch (this._activeChat.type) {
|
||||
case ChatType.CLIENT:
|
||||
disable_input = false;
|
||||
break;
|
||||
case ChatType.SERVER:
|
||||
disable_input = !globalClient.permissions.neededPermission(PermissionType.B_CLIENT_SERVER_TEXTMESSAGE_SEND).granted(1);
|
||||
disable_input = !this.connection_handler.permissions.neededPermission(PermissionType.B_CLIENT_SERVER_TEXTMESSAGE_SEND).granted(1);
|
||||
break;
|
||||
case ChatType.CHANNEL:
|
||||
disable_input = !globalClient.permissions.neededPermission(PermissionType.B_CLIENT_CHANNEL_TEXTMESSAGE_SEND).granted(1);
|
||||
disable_input = !this.connection_handler.permissions.neededPermission(PermissionType.B_CLIENT_CHANNEL_TEXTMESSAGE_SEND).granted(1);
|
||||
break;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
|
||||
let server_connections: ServerConnectionManager;
|
||||
|
||||
class ServerConnectionManager {
|
||||
private connection_handlers: ConnectionHandler[] = [];
|
||||
private active_handler: ConnectionHandler | undefined;
|
||||
|
||||
private _container_channel_tree: JQuery;
|
||||
private _container_select_info: JQuery;
|
||||
private _container_chat_box: JQuery;
|
||||
|
||||
private _tag: JQuery;
|
||||
private _tag_connection_entries: JQuery;
|
||||
private _tag_buttons_scoll: JQuery;
|
||||
private _tag_button_scoll_right: JQuery;
|
||||
private _tag_button_scoll_left: JQuery;
|
||||
|
||||
constructor(tag: JQuery) {
|
||||
this._tag = tag;
|
||||
|
||||
if(settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION, false))
|
||||
this._tag.hide();
|
||||
|
||||
this._tag_connection_entries = this._tag.find(".connection-handlers");
|
||||
this._tag_buttons_scoll = this._tag.find(".container-scroll");
|
||||
this._tag_button_scoll_left = this._tag_buttons_scoll.find(".button-scroll-left");
|
||||
this._tag_button_scoll_right = this._tag_buttons_scoll.find(".button-scroll-right");
|
||||
|
||||
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_channel_tree = $("#channelTree");
|
||||
this._container_select_info = $("#select_info");
|
||||
this._container_chat_box = $("#chat");
|
||||
|
||||
this.set_active_connection_handler(undefined);
|
||||
}
|
||||
|
||||
spawn_server_connection_handler() : ConnectionHandler {
|
||||
const handler = new ConnectionHandler();
|
||||
this.connection_handlers.push(handler);
|
||||
control_bar.update_button_away();
|
||||
control_bar.initialize_connection_handler_state(handler);
|
||||
|
||||
handler.tag_connection_handler.appendTo(this._tag_connection_entries);
|
||||
this._update_scroll();
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
destroy_server_connection_handler(handler: ConnectionHandler) {
|
||||
this.connection_handlers.remove(handler);
|
||||
handler.tag_connection_handler.detach();
|
||||
this._update_scroll();
|
||||
|
||||
if(handler.serverConnection) {
|
||||
const connected = handler.connected;
|
||||
handler.serverConnection.disconnect("handler destroyed");
|
||||
handler.handleDisconnect(DisconnectReason.HANDLER_DESTROYED, connected);
|
||||
}
|
||||
|
||||
if(handler === this.active_handler)
|
||||
this.set_active_connection_handler(this.connection_handlers[0]);
|
||||
}
|
||||
|
||||
set_active_connection_handler(handler: ConnectionHandler) {
|
||||
if(handler && this.connection_handlers.indexOf(handler) == -1)
|
||||
throw "Handler hasn't been registrated or is already obsolete!";
|
||||
|
||||
if(this.active_handler)
|
||||
this.active_handler.select_info.close_popover();
|
||||
this._tag_connection_entries.find(".active").removeClass("active");
|
||||
this._container_channel_tree.children().detach();
|
||||
this._container_select_info.children().detach();
|
||||
this._container_chat_box.children().detach();
|
||||
|
||||
control_bar.set_connection_handler(handler);
|
||||
if(handler) {
|
||||
handler.tag_connection_handler.addClass("active");
|
||||
|
||||
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);
|
||||
|
||||
if(handler.invoke_resized_on_activate)
|
||||
handler.resize_elements();
|
||||
}
|
||||
this.active_handler = handler;
|
||||
}
|
||||
|
||||
active_connection_handler() : ConnectionHandler | undefined {
|
||||
return this.active_handler;
|
||||
}
|
||||
|
||||
server_connection_handlers() : ConnectionHandler[] {
|
||||
return this.connection_handlers;
|
||||
}
|
||||
|
||||
update_ui() {
|
||||
this._update_scroll();
|
||||
}
|
||||
|
||||
private _update_scroll() {
|
||||
const has_scroll = this._tag_connection_entries.hasScrollBar("width")
|
||||
&& this._tag_connection_entries.width() + 10 >= this._tag_connection_entries.parent().width();
|
||||
|
||||
this._tag_buttons_scoll.toggleClass("enabled", has_scroll);
|
||||
this._tag.toggleClass("scrollbar", has_scroll);
|
||||
|
||||
if(has_scroll)
|
||||
this._update_scroll_buttons();
|
||||
}
|
||||
|
||||
private _button_scroll_right_clicked() {
|
||||
this._tag_connection_entries.scrollLeft((this._tag_connection_entries.scrollLeft() || 0) + 50);
|
||||
this._update_scroll_buttons();
|
||||
}
|
||||
|
||||
private _button_scroll_left_clicked() {
|
||||
this._tag_connection_entries.scrollLeft((this._tag_connection_entries.scrollLeft() || 0) - 50);
|
||||
this._update_scroll_buttons();
|
||||
}
|
||||
|
||||
private _update_scroll_buttons() {
|
||||
const scroll = this._tag_connection_entries.scrollLeft() || 0;
|
||||
this._tag_button_scoll_left.toggleClass("disabled", scroll <= 0);
|
||||
this._tag_button_scoll_right.toggleClass("disabled", scroll + this._tag_connection_entries.width() + 2 >= this._tag_connection_entries[0].scrollWidth);
|
||||
}
|
||||
}
|
|
@ -111,15 +111,16 @@ namespace htmltags {
|
|||
|
||||
let client: ClientEntry;
|
||||
|
||||
if(globalClient && globalClient.channelTree) {
|
||||
const current_connection = server_connections.active_connection_handler();
|
||||
if(current_connection && current_connection.channelTree) {
|
||||
if(!client && client_id) {
|
||||
client = globalClient.channelTree.findClient(client_id);
|
||||
client = current_connection.channelTree.findClient(client_id);
|
||||
if(client && (client_unique_id && client.properties.client_unique_identifier != client_unique_id)) {
|
||||
client = undefined; /* client id dosn't match anymore, lets search for the unique id */
|
||||
}
|
||||
}
|
||||
if(!client && client_unique_id)
|
||||
client = globalClient.channelTree.find_client_by_unique_id(client_unique_id);
|
||||
client = current_connection.channelTree.find_client_by_unique_id(client_unique_id);
|
||||
}
|
||||
if(!client) {
|
||||
/* we may should open a "offline" menu? */
|
||||
|
@ -138,9 +139,10 @@ namespace htmltags {
|
|||
export function callback_context_channel(element: JQuery) {
|
||||
const channel_id = parseInt(element.attr("channel-id") || "0");
|
||||
|
||||
const current_connection = server_connections.active_connection_handler();
|
||||
let channel: ChannelEntry;
|
||||
if(globalClient && globalClient.channelTree) {
|
||||
channel = globalClient.channelTree.findChannel(channel_id);
|
||||
if(current_connection && current_connection.channelTree) {
|
||||
channel = current_connection.channelTree.findChannel(channel_id);
|
||||
}
|
||||
|
||||
if(!channel)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
/// <reference path="../../client.ts" />
|
||||
|
||||
namespace Modals {
|
||||
const avatar_to_uid = (id: string) => {
|
||||
|
@ -20,7 +20,7 @@ namespace Modals {
|
|||
return (size / Math.pow(1024, exp)).toFixed(2) + 'KMGTPE'.charAt(exp - 1) + "iB";
|
||||
};
|
||||
|
||||
export function spawnAvatarList(client: TSClient) {
|
||||
export function spawnAvatarList(client: ConnectionHandler) {
|
||||
const modal = createModal({
|
||||
header: tr("Avatars"),
|
||||
footer: undefined,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
/// <reference path="../../client.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export function spawnBanClient(name: string | string[], callback: (data: {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
namespace Modals {
|
||||
export function spawnBanCreate(base?: BanEntry, callback?: (entry?: BanEntry) => any) {
|
||||
export function spawnBanCreate(connection: ConnectionHandler, base?: BanEntry, callback?: (entry?: BanEntry) => any) {
|
||||
let result: BanEntry = {} as any;
|
||||
result.banid = base ? base.banid : 0;
|
||||
|
||||
|
@ -98,8 +98,8 @@ namespace Modals {
|
|||
input_global.prop("checked", base.server_id == 0);
|
||||
}
|
||||
|
||||
if(globalClient && globalClient.permissions)
|
||||
input_global.prop("disabled", !globalClient.permissions.neededPermission(base ? PermissionType.B_CLIENT_BAN_EDIT_GLOBAL : PermissionType.B_CLIENT_BAN_CREATE_GLOBAL));
|
||||
if(connection && connection.permissions)
|
||||
input_global.prop("disabled", !connection.permissions.neededPermission(base ? PermissionType.B_CLIENT_BAN_EDIT_GLOBAL : PermissionType.B_CLIENT_BAN_CREATE_GLOBAL));
|
||||
|
||||
return template;
|
||||
},
|
||||
|
|
|
@ -28,10 +28,10 @@ namespace Modals {
|
|||
modal: Modal;
|
||||
}
|
||||
|
||||
export function openBanList(client: TSClient) {
|
||||
export function openBanList(client: ConnectionHandler) {
|
||||
let update;
|
||||
const modal = spawnBanListModal(() => update(), () => {
|
||||
spawnBanCreate(undefined, result => {
|
||||
spawnBanCreate(client, undefined, result => {
|
||||
if(result.server_id < 0) result.server_id = undefined;
|
||||
console.log(tr("Adding ban %o"), result);
|
||||
|
||||
|
@ -52,7 +52,7 @@ namespace Modals {
|
|||
});
|
||||
}, ban => {
|
||||
console.log(tr("Editing ban %o"), ban);
|
||||
spawnBanCreate(ban, result => {
|
||||
spawnBanCreate(client, ban, result => {
|
||||
console.log(tr("Apply edit changes %o"), result);
|
||||
if(result.server_id < 0) result.server_id = undefined;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
|
@ -252,7 +253,7 @@ namespace Modals {
|
|||
width: 750
|
||||
});
|
||||
|
||||
modal.close_listener.push(() => globalClient.controlBar.update_bookmarks());
|
||||
modal.close_listener.push(() => control_bar.update_bookmarks());
|
||||
modal.open();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export function spawnConnectModal(defaultHost: { url: string, enforce: boolean} = { url: "ts.TeaSpeak.de", enforce: false}, connect_profile?: { profile: profiles.ConnectionProfile, enforce: boolean}) {
|
||||
|
@ -13,9 +13,11 @@ namespace Modals {
|
|||
const connect_modal = $("#tmpl_connect").renderTag({
|
||||
client: native_client,
|
||||
forum_path: settings.static("forum_path"),
|
||||
password_id: random_id
|
||||
password_id: random_id,
|
||||
multi_tab: !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION, false)
|
||||
}).modalize((header, body, footer) => {
|
||||
const button_connect = footer.find(".button-connect");
|
||||
const button_connect_tab = footer.find(".button-connect-new-tab");
|
||||
const button_manage = body.find(".button-manage-profiles");
|
||||
|
||||
const input_profile = body.find(".container-select-profile select");
|
||||
|
@ -41,11 +43,9 @@ namespace Modals {
|
|||
input_address.attr('pattern', flag_address ? null : '^[a]{1000}$').toggleClass('is-invalid', !flag_address);
|
||||
input_nickname.attr('pattern', flag_nickname ? null : '^[a]{1000}$').toggleClass('is-invalid', !flag_nickname);
|
||||
|
||||
if(!flag_nickname || !flag_address || !selected_profile || !selected_profile.valid()) {
|
||||
button_connect.prop("disabled", true);
|
||||
} else {
|
||||
button_connect.prop("disabled", false);
|
||||
}
|
||||
const flag_disabled = !flag_nickname || !flag_address || !selected_profile || !selected_profile.valid();
|
||||
button_connect.prop("disabled", flag_disabled);
|
||||
button_connect_tab.prop("disabled", flag_disabled);
|
||||
};
|
||||
|
||||
input_nickname.val(settings.static_global(Settings.KEY_CONNECT_USERNAME, undefined));
|
||||
|
@ -89,7 +89,24 @@ namespace Modals {
|
|||
button_connect.on('click', event => {
|
||||
connect_modal.close();
|
||||
|
||||
globalClient.startConnection(
|
||||
const connection = server_connections.active_connection_handler();
|
||||
if(connection) {
|
||||
connection.startConnection(
|
||||
input_address.val().toString(),
|
||||
selected_profile,
|
||||
input_nickname.val().toString() || selected_profile.default_username,
|
||||
{password: input_password.val().toString(), hashed: false}
|
||||
);
|
||||
} else {
|
||||
button_connect_tab.trigger('click');
|
||||
}
|
||||
});
|
||||
button_connect_tab.on('click', event => {
|
||||
connect_modal.close();
|
||||
|
||||
const connection = server_connections.spawn_server_connection_handler();
|
||||
server_connections.set_active_connection_handler(connection);
|
||||
connection.startConnection(
|
||||
input_address.val().toString(),
|
||||
selected_profile,
|
||||
input_nickname.val().toString() || selected_profile.default_username,
|
||||
|
@ -102,57 +119,6 @@ namespace Modals {
|
|||
|
||||
connect_modal.open();
|
||||
return;
|
||||
|
||||
|
||||
const connectModal = createModal({
|
||||
header: (tr("Create a new connection")),
|
||||
body: function () {
|
||||
const random_id = (() => {
|
||||
const array = new Uint32Array(10);
|
||||
window.crypto.getRandomValues(array);
|
||||
return array.join("");
|
||||
})();
|
||||
|
||||
let tag = $("#tmpl_connect").renderTag({
|
||||
client: native_client,
|
||||
forum_path: settings.static("forum_path"),
|
||||
password_id: random_id
|
||||
});
|
||||
|
||||
|
||||
//connect_address
|
||||
return tag;
|
||||
},
|
||||
footer: function () {
|
||||
let tag = $.spawn("div");
|
||||
tag.css("text-align", "right");
|
||||
tag.css("margin-top", "3px");
|
||||
tag.css("margin-bottom", "6px");
|
||||
tag.addClass("modal-button-group");
|
||||
|
||||
let button = $.spawn("button");
|
||||
button.addClass("connect_connect_button");
|
||||
button.text(tr("Connect"));
|
||||
button.on("click", function () {
|
||||
connectModal.close();
|
||||
|
||||
let field_address = tag.parents(".modal-content").find(".connect_address");
|
||||
let address = field_address.val().toString();
|
||||
globalClient.startConnection(
|
||||
address,
|
||||
selected_profile,
|
||||
tag.parents(".modal-content").find(".connect_nickname").val().toString() || selected_profile.default_username,
|
||||
{password: tag.parents(".modal-content").find(".connect_password").val().toString(), hashed: false}
|
||||
);
|
||||
});
|
||||
tag.append(button);
|
||||
return tag;
|
||||
},
|
||||
|
||||
width: '70%',
|
||||
//flag_closeable: false
|
||||
});
|
||||
connectModal.open();
|
||||
}
|
||||
|
||||
let Regex = {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export function createChannelModal(channel: ChannelEntry | undefined, parent: ChannelEntry | undefined, permissions: PermissionManager, callback: (properties?: ChannelProperties, permissions?: PermissionValue[]) => any) {
|
||||
export function createChannelModal(connection: ConnectionHandler, channel: ChannelEntry | undefined, parent: ChannelEntry | undefined, permissions: PermissionManager, callback: (properties?: ChannelProperties, permissions?: PermissionValue[]) => any) {
|
||||
let properties: ChannelProperties = { } as ChannelProperties; //The changes properties
|
||||
const modal = createModal({
|
||||
header: channel ? tr("Edit channel") : tr("Create channel"),
|
||||
|
@ -11,7 +11,7 @@ namespace Modals {
|
|||
channel_flag_maxfamilyclients_unlimited: true,
|
||||
channel_flag_maxclients_unlimited: true,
|
||||
});
|
||||
render_properties["channel_icon"] = globalClient.fileManager.icons.generateTag(channel ? channel.properties.channel_icon_id : 0);
|
||||
render_properties["channel_icon"] = connection.fileManager.icons.generateTag(channel ? channel.properties.channel_icon_id : 0);
|
||||
|
||||
let template = $("#tmpl_channel_edit").renderTag(render_properties);
|
||||
return template.tabify();
|
||||
|
@ -36,11 +36,11 @@ namespace Modals {
|
|||
});
|
||||
|
||||
|
||||
applyGeneralListener(properties, modal.htmlTag.find(".general_properties"), modal.htmlTag.find(".button_ok"), channel);
|
||||
applyStandardListener(properties, modal.htmlTag.find(".settings_standard"), modal.htmlTag.find(".button_ok"), parent, !channel);
|
||||
applyPermissionListener(properties, modal.htmlTag.find(".settings_permissions"), modal.htmlTag.find(".button_ok"), permissions, channel);
|
||||
applyAudioListener(properties, modal.htmlTag.find(".container-channel-settings-audio"), modal.htmlTag.find(".button_ok"), channel);
|
||||
applyAdvancedListener(properties, modal.htmlTag.find(".settings_advanced"), modal.htmlTag.find(".button_ok"), channel);
|
||||
applyGeneralListener(connection, properties, modal.htmlTag.find(".general_properties"), modal.htmlTag.find(".button_ok"), channel);
|
||||
applyStandardListener(connection, properties, modal.htmlTag.find(".settings_standard"), modal.htmlTag.find(".button_ok"), parent, !channel);
|
||||
applyPermissionListener(connection, properties, modal.htmlTag.find(".settings_permissions"), modal.htmlTag.find(".button_ok"), permissions, channel);
|
||||
applyAudioListener(connection, properties, modal.htmlTag.find(".container-channel-settings-audio"), modal.htmlTag.find(".button_ok"), channel);
|
||||
applyAdvancedListener(connection, properties, modal.htmlTag.find(".settings_advanced"), modal.htmlTag.find(".button_ok"), channel);
|
||||
|
||||
let updated: PermissionValue[] = [];
|
||||
modal.htmlTag.find(".button_ok").click(() => {
|
||||
|
@ -72,7 +72,7 @@ namespace Modals {
|
|||
modal.htmlTag.find(".channel_name").focus();
|
||||
}
|
||||
|
||||
function applyGeneralListener(properties: ChannelProperties, tag: JQuery, button: JQuery, channel: ChannelEntry | undefined) {
|
||||
function applyGeneralListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, button: JQuery, channel: ChannelEntry | undefined) {
|
||||
let updateButton = () => {
|
||||
if(tag.find(".input_error").length == 0)
|
||||
button.removeAttr("disabled");
|
||||
|
@ -86,13 +86,13 @@ namespace Modals {
|
|||
if(this.value.length < 1 || this.value.length > 40)
|
||||
$(this).addClass("input_error");
|
||||
updateButton();
|
||||
}).prop("disabled", channel && !globalClient.permissions.neededPermission(PermissionType.B_CHANNEL_MODIFY_NAME).granted(1));
|
||||
}).prop("disabled", channel && !connection.permissions.neededPermission(PermissionType.B_CHANNEL_MODIFY_NAME).granted(1));
|
||||
|
||||
tag.find(".button-select-icon").on('click', event => {
|
||||
Modals.spawnIconSelect(globalClient, id => {
|
||||
Modals.spawnIconSelect(connection, id => {
|
||||
const icon_node = tag.find(".button-select-icon").find(".icon-node");
|
||||
icon_node.empty();
|
||||
icon_node.append(globalClient.fileManager.icons.generateTag(id));
|
||||
icon_node.append(connection.fileManager.icons.generateTag(id));
|
||||
|
||||
console.log("Selected icon ID: %d", id);
|
||||
properties.channel_icon_id = id;
|
||||
|
@ -106,18 +106,18 @@ namespace Modals {
|
|||
|
||||
$(this).removeClass("input_error");
|
||||
if(!properties.channel_flag_password)
|
||||
if(globalClient.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_FORCE_PASSWORD).granted(1))
|
||||
if(connection.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_FORCE_PASSWORD).granted(1))
|
||||
$(this).addClass("input_error");
|
||||
updateButton();
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(!channel ? PermissionType.B_CHANNEL_CREATE_WITH_PASSWORD : PermissionType.B_CHANNEL_MODIFY_PASSWORD).granted(1));
|
||||
}).prop("disabled", !connection.permissions.neededPermission(!channel ? PermissionType.B_CHANNEL_CREATE_WITH_PASSWORD : PermissionType.B_CHANNEL_MODIFY_PASSWORD).granted(1));
|
||||
|
||||
tag.find(".channel_topic").change(function (this: HTMLInputElement) {
|
||||
properties.channel_topic = this.value;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(!channel ? PermissionType.B_CHANNEL_CREATE_WITH_TOPIC : PermissionType.B_CHANNEL_MODIFY_TOPIC).granted(1));
|
||||
}).prop("disabled", !connection.permissions.neededPermission(!channel ? PermissionType.B_CHANNEL_CREATE_WITH_TOPIC : PermissionType.B_CHANNEL_MODIFY_TOPIC).granted(1));
|
||||
|
||||
tag.find(".channel_description").change(function (this: HTMLInputElement) {
|
||||
properties.channel_description = this.value;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(!channel ? PermissionType.B_CHANNEL_CREATE_WITH_DESCRIPTION : PermissionType.B_CHANNEL_MODIFY_DESCRIPTION).granted(1));
|
||||
}).prop("disabled", !connection.permissions.neededPermission(!channel ? PermissionType.B_CHANNEL_CREATE_WITH_DESCRIPTION : PermissionType.B_CHANNEL_MODIFY_DESCRIPTION).granted(1));
|
||||
|
||||
if(!channel) {
|
||||
setTimeout(() => {
|
||||
|
@ -127,7 +127,7 @@ namespace Modals {
|
|||
}
|
||||
}
|
||||
|
||||
function applyStandardListener(properties: ChannelProperties, tag: JQuery, button: JQuery, parent: ChannelEntry, create: boolean) {
|
||||
function applyStandardListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, button: JQuery, parent: ChannelEntry, create: boolean) {
|
||||
tag.find("input[name=\"channel_type\"]").change(function (this: HTMLInputElement) {
|
||||
switch(this.value) {
|
||||
case "semi":
|
||||
|
@ -145,11 +145,11 @@ namespace Modals {
|
|||
}
|
||||
});
|
||||
tag.find("input[name=\"channel_type\"][value=\"temp\"]")
|
||||
.prop("disabled", !globalClient.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_TEMPORARY : PermissionType.B_CHANNEL_MODIFY_MAKE_TEMPORARY).granted(1));
|
||||
.prop("disabled", !connection.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_TEMPORARY : PermissionType.B_CHANNEL_MODIFY_MAKE_TEMPORARY).granted(1));
|
||||
tag.find("input[name=\"channel_type\"][value=\"semi\"]")
|
||||
.prop("disabled", !globalClient.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_SEMI_PERMANENT : PermissionType.B_CHANNEL_MODIFY_MAKE_SEMI_PERMANENT).granted(1));
|
||||
.prop("disabled", !connection.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_SEMI_PERMANENT : PermissionType.B_CHANNEL_MODIFY_MAKE_SEMI_PERMANENT).granted(1));
|
||||
tag.find("input[name=\"channel_type\"][value=\"perm\"]")
|
||||
.prop("disabled", !globalClient.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_PERMANENT : PermissionType.B_CHANNEL_MODIFY_MAKE_PERMANENT).granted(1));
|
||||
.prop("disabled", !connection.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_PERMANENT : PermissionType.B_CHANNEL_MODIFY_MAKE_PERMANENT).granted(1));
|
||||
if(create)
|
||||
tag.find("input[name=\"channel_type\"]:not(:disabled)").last().prop("checked", true).trigger('change');
|
||||
|
||||
|
@ -164,26 +164,26 @@ namespace Modals {
|
|||
tag.find("input[name=\"channel_type\"][value=\"perm\"]").prop("checked", true).trigger("change");
|
||||
}
|
||||
}).prop("disabled",
|
||||
!globalClient.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_PERMANENT : PermissionType.B_CHANNEL_MODIFY_MAKE_PERMANENT).granted(1) ||
|
||||
!globalClient.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_WITH_DEFAULT : PermissionType.B_CHANNEL_MODIFY_MAKE_DEFAULT).granted(1));
|
||||
!connection.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_PERMANENT : PermissionType.B_CHANNEL_MODIFY_MAKE_PERMANENT).granted(1) ||
|
||||
!connection.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_WITH_DEFAULT : PermissionType.B_CHANNEL_MODIFY_MAKE_DEFAULT).granted(1));
|
||||
|
||||
tag.find("input[name=\"talk_power\"]").change(function (this: HTMLInputElement) {
|
||||
properties.channel_needed_talk_power = parseInt(this.value);
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_WITH_NEEDED_TALK_POWER : PermissionType.B_CHANNEL_MODIFY_NEEDED_TALK_POWER).granted(1));
|
||||
}).prop("disabled", !connection.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_WITH_NEEDED_TALK_POWER : PermissionType.B_CHANNEL_MODIFY_NEEDED_TALK_POWER).granted(1));
|
||||
|
||||
let orderTag = tag.find(".order_id");
|
||||
for(let channel of (parent ? parent.children() : globalClient.channelTree.rootChannel()))
|
||||
for(let channel of (parent ? parent.children() : connection.channelTree.rootChannel()))
|
||||
$.spawn("option").attr("channelId", channel.channelId.toString()).text(channel.channelName()).appendTo(orderTag);
|
||||
|
||||
orderTag.change(function (this: HTMLSelectElement) {
|
||||
let selected = $(this.options.item(this.selectedIndex));
|
||||
properties.channel_order = parseInt(selected.attr("channelId"));
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_WITH_SORTORDER : PermissionType.B_CHANNEL_MODIFY_SORTORDER).granted(1));
|
||||
}).prop("disabled", !connection.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_WITH_SORTORDER : PermissionType.B_CHANNEL_MODIFY_SORTORDER).granted(1));
|
||||
orderTag.find("option").last().prop("selected", true);
|
||||
}
|
||||
|
||||
|
||||
function applyPermissionListener(properties: ChannelProperties, tag: JQuery, button: JQuery, permissions: PermissionManager, channel?: ChannelEntry) {
|
||||
function applyPermissionListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, button: JQuery, permissions: PermissionManager, channel?: ChannelEntry) {
|
||||
let apply_permissions = (channel_permissions: PermissionValue[]) => {
|
||||
console.log(tr("Got permissions: %o"), channel_permissions);
|
||||
let required_power = -2;
|
||||
|
@ -229,7 +229,7 @@ namespace Modals {
|
|||
} else apply_permissions([]);
|
||||
}
|
||||
|
||||
function applyAudioListener(properties: ChannelProperties, tag: JQuery, button: JQuery, channel?: ChannelEntry) {
|
||||
function applyAudioListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, button: JQuery, channel?: ChannelEntry) {
|
||||
let update_template = () => {
|
||||
let codec = properties.channel_codec;
|
||||
if(!codec && channel)
|
||||
|
@ -291,19 +291,19 @@ namespace Modals {
|
|||
}
|
||||
});
|
||||
tag.find("input[name=\"voice_template\"][value=\"voice_mobile\"]")
|
||||
.prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSVOICE).granted(1));
|
||||
.prop("disabled", !connection.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSVOICE).granted(1));
|
||||
tag.find("input[name=\"voice_template\"][value=\"voice_desktop\"]")
|
||||
.prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSVOICE).granted(1));
|
||||
.prop("disabled", !connection.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSVOICE).granted(1));
|
||||
tag.find("input[name=\"voice_template\"][value=\"music\"]")
|
||||
.prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSMUSIC).granted(1));
|
||||
.prop("disabled", !connection.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSMUSIC).granted(1));
|
||||
|
||||
let codecs = tag.find(".voice_codec option");
|
||||
codecs.eq(0).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX8).granted(1));
|
||||
codecs.eq(1).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX16).granted(1));
|
||||
codecs.eq(2).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX32).granted(1));
|
||||
codecs.eq(3).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_CELTMONO48).granted(1));
|
||||
codecs.eq(4).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSVOICE).granted(1));
|
||||
codecs.eq(5).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSMUSIC).granted(1));
|
||||
codecs.eq(0).prop("disabled", !connection.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX8).granted(1));
|
||||
codecs.eq(1).prop("disabled", !connection.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX16).granted(1));
|
||||
codecs.eq(2).prop("disabled", !connection.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX32).granted(1));
|
||||
codecs.eq(3).prop("disabled", !connection.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_CELTMONO48).granted(1));
|
||||
codecs.eq(4).prop("disabled", !connection.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSVOICE).granted(1));
|
||||
codecs.eq(5).prop("disabled", !connection.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSMUSIC).granted(1));
|
||||
tag.find(".voice_codec").change(function (this: HTMLSelectElement) {
|
||||
if($(this.item(this.selectedIndex)).prop("disabled")) return false;
|
||||
|
||||
|
@ -322,25 +322,25 @@ namespace Modals {
|
|||
quality_slider.on('input', event => change_quality(parseInt(quality_slider.val() as string)));
|
||||
}
|
||||
|
||||
function applyAdvancedListener(properties: ChannelProperties, tag: JQuery, button: JQuery, channel?: ChannelEntry) {
|
||||
function applyAdvancedListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, button: JQuery, channel?: ChannelEntry) {
|
||||
tag.find(".channel_name_phonetic").change(function (this: HTMLInputElement) {
|
||||
properties.channel_topic = this.value;
|
||||
});
|
||||
|
||||
tag.find(".channel_delete_delay").change(function (this: HTMLInputElement) {
|
||||
properties.channel_delete_delay = parseInt(this.value);
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_CHANNEL_MODIFY_TEMP_DELETE_DELAY).granted(1));
|
||||
}).prop("disabled", !connection.permissions.neededPermission(PermissionType.B_CHANNEL_MODIFY_TEMP_DELETE_DELAY).granted(1));
|
||||
|
||||
tag.find(".channel_codec_is_unencrypted").change(function (this: HTMLInputElement) {
|
||||
properties.channel_codec_is_unencrypted = parseInt(this.value) == 0;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_CHANNEL_MODIFY_MAKE_CODEC_ENCRYPTED).granted(1));
|
||||
}).prop("disabled", !connection.permissions.neededPermission(PermissionType.B_CHANNEL_MODIFY_MAKE_CODEC_ENCRYPTED).granted(1));
|
||||
|
||||
{
|
||||
let tag_infinity = tag.find("input[name=\"max_users\"][value=\"infinity\"]");
|
||||
let tag_limited = tag.find("input[name=\"max_users\"][value=\"limited\"]");
|
||||
let tag_limited_value = tag.find(".channel_maxclients");
|
||||
|
||||
if(!globalClient.permissions.neededPermission(!channel ? PermissionType.B_CHANNEL_CREATE_WITH_MAXCLIENTS : PermissionType.B_CHANNEL_MODIFY_MAXCLIENTS).granted(1)) {
|
||||
if(!connection.permissions.neededPermission(!channel ? PermissionType.B_CHANNEL_CREATE_WITH_MAXCLIENTS : PermissionType.B_CHANNEL_MODIFY_MAXCLIENTS).granted(1)) {
|
||||
tag_infinity.prop("disabled", true);
|
||||
tag_limited.prop("disabled", true);
|
||||
tag_limited_value.prop("disabled", true);
|
||||
|
@ -363,7 +363,7 @@ namespace Modals {
|
|||
let tag_limited = tag.find("input[name=\"max_users_family\"][value=\"limited\"]");
|
||||
let tag_limited_value = tag.find(".channel_maxfamilyclients");
|
||||
|
||||
if(!globalClient.permissions.neededPermission(!channel ? PermissionType.B_CHANNEL_CREATE_WITH_MAXCLIENTS : PermissionType.B_CHANNEL_MODIFY_MAXCLIENTS).granted(1)) {
|
||||
if(!connection.permissions.neededPermission(!channel ? PermissionType.B_CHANNEL_CREATE_WITH_MAXCLIENTS : PermissionType.B_CHANNEL_MODIFY_MAXCLIENTS).granted(1)) {
|
||||
tag_inherited.prop("disabled", true);
|
||||
tag_infinity.prop("disabled", true);
|
||||
tag_limited.prop("disabled", true);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
/// <reference path="../../client.ts" />
|
||||
|
||||
namespace Modals {
|
||||
//TODO Upload/delete button
|
||||
export function spawnIconSelect(client: TSClient, callback_icon?: (id: number) => any, selected_icon?: number) {
|
||||
export function spawnIconSelect(client: ConnectionHandler, callback_icon?: (id: number) => any, selected_icon?: number) {
|
||||
callback_icon = callback_icon || (() => {});
|
||||
selected_icon = selected_icon || 0;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
/// <reference path="../../client.ts" />
|
||||
|
||||
/*
|
||||
TODO: Check needed permissions and may not even try to request, because we dont have the permission. Permissions:
|
||||
|
@ -542,7 +542,7 @@ namespace Modals {
|
|||
}
|
||||
}
|
||||
|
||||
export function spawnPermissionEdit() : Modal {
|
||||
export function spawnPermissionEdit(connection: ConnectionHandler) : Modal {
|
||||
const connectModal = createModal({
|
||||
header: function() {
|
||||
return tr("Server Permissions");
|
||||
|
@ -551,7 +551,7 @@ namespace Modals {
|
|||
let properties: any = {};
|
||||
|
||||
let tag = $("#tmpl_server_permissions").renderTag(properties);
|
||||
const pe = new PermissionEditor(globalClient.permissions.groupedPermissions());
|
||||
const pe = new PermissionEditor(connection.permissions.groupedPermissions());
|
||||
pe.build_tag();
|
||||
/* initialisation */
|
||||
{
|
||||
|
@ -560,11 +560,11 @@ namespace Modals {
|
|||
}
|
||||
|
||||
|
||||
apply_server_groups(pe, tag.find(".tab-group-server"));
|
||||
apply_channel_groups(pe, tag.find(".tab-group-channel"));
|
||||
apply_channel_permission(pe, tag.find(".tab-channel"));
|
||||
apply_client_permission(pe, tag.find(".tab-client"));
|
||||
apply_client_channel_permission(pe, tag.find(".tab-client-channel"));
|
||||
apply_server_groups(connection, pe, tag.find(".tab-group-server"));
|
||||
apply_channel_groups(connection, pe, tag.find(".tab-group-channel"));
|
||||
apply_channel_permission(connection, pe, tag.find(".tab-channel"));
|
||||
apply_client_permission(connection, pe, tag.find(".tab-client"));
|
||||
apply_client_channel_permission(connection, pe, tag.find(".tab-client-channel"));
|
||||
return tag.tabify(false);
|
||||
},
|
||||
footer: undefined,
|
||||
|
@ -583,16 +583,16 @@ namespace Modals {
|
|||
return connectModal;
|
||||
}
|
||||
|
||||
function build_channel_tree(channel_list: JQuery, select_callback: (channel: ChannelEntry) => any) {
|
||||
const root = globalClient.channelTree.get_first_channel();
|
||||
function build_channel_tree(connection: ConnectionHandler, channel_list: JQuery, select_callback: (channel: ChannelEntry) => any) {
|
||||
const root = connection.channelTree.get_first_channel();
|
||||
if(!root) return;
|
||||
|
||||
const build_channel = (channel: ChannelEntry) => {
|
||||
let tag = $.spawn("div").addClass("channel").attr("channel-id", channel.channelId);
|
||||
globalClient.fileManager.icons.generateTag(channel.properties.channel_icon_id).appendTo(tag);
|
||||
connection.fileManager.icons.generateTag(channel.properties.channel_icon_id).appendTo(tag);
|
||||
{
|
||||
let name = $.spawn("a").text(channel.channelName() + " (" + channel.channelId + ")").addClass("name");
|
||||
//if(globalClient.channelTree.server.properties. == group.id)
|
||||
//if(connection.channelTree.server.properties. == group.id)
|
||||
// name.addClass("default");
|
||||
name.appendTo(tag);
|
||||
}
|
||||
|
@ -619,7 +619,7 @@ namespace Modals {
|
|||
setTimeout(() => channel_list.find('.channel').first().trigger('click'), 0);
|
||||
}
|
||||
|
||||
function apply_client_channel_permission(editor: PermissionEditor, tab_tag: JQuery) {
|
||||
function apply_client_channel_permission(connection: ConnectionHandler, editor: PermissionEditor, tab_tag: JQuery) {
|
||||
let current_cldbid: number = 0;
|
||||
let current_channel: ChannelEntry;
|
||||
|
||||
|
@ -629,7 +629,7 @@ namespace Modals {
|
|||
tab_tag.on('show', event => {
|
||||
console.error("Channel tab show");
|
||||
pe_client.append(editor.container);
|
||||
if(globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CLIENT_PERMISSION_LIST).granted(1)) {
|
||||
if(connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CLIENT_PERMISSION_LIST).granted(1)) {
|
||||
if(current_cldbid && current_channel)
|
||||
editor.set_mode(PermissionEditorMode.VISIBLE);
|
||||
else
|
||||
|
@ -643,7 +643,7 @@ namespace Modals {
|
|||
editor.set_listener_update(() => {
|
||||
if(!current_cldbid || !current_channel) return;
|
||||
|
||||
globalClient.permissions.requestClientChannelPermissions(current_cldbid, current_channel.channelId).then(result => {
|
||||
connection.permissions.requestClientChannelPermissions(current_cldbid, current_channel.channelId).then(result => {
|
||||
editor.set_permissions(result);
|
||||
editor.set_mode(PermissionEditorMode.VISIBLE);
|
||||
}).catch(error => {
|
||||
|
@ -665,7 +665,7 @@ namespace Modals {
|
|||
permission.id,
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("channelclientdelperm", {
|
||||
return connection.serverConnection.send_command("channelclientdelperm", {
|
||||
cldbid: current_cldbid,
|
||||
cid: current_channel.channelId,
|
||||
permid: permission.id,
|
||||
|
@ -677,7 +677,7 @@ namespace Modals {
|
|||
value.granted,
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("channelclientdelperm", {
|
||||
return connection.serverConnection.send_command("channelclientdelperm", {
|
||||
cldbid: current_cldbid,
|
||||
cid: current_channel.channelId,
|
||||
permid: permission.id_grant(),
|
||||
|
@ -694,7 +694,7 @@ namespace Modals {
|
|||
value.flag_negate
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("channelclientaddperm", {
|
||||
return connection.serverConnection.send_command("channelclientaddperm", {
|
||||
cldbid: current_cldbid,
|
||||
cid: current_channel.channelId,
|
||||
permid: permission.id,
|
||||
|
@ -709,7 +709,7 @@ namespace Modals {
|
|||
value.granted,
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("channelclientaddperm", {
|
||||
return connection.serverConnection.send_command("channelclientaddperm", {
|
||||
cldbid: current_cldbid,
|
||||
cid: current_channel.channelId,
|
||||
permid: permission.id_grant(),
|
||||
|
@ -726,7 +726,7 @@ namespace Modals {
|
|||
});
|
||||
}
|
||||
|
||||
build_channel_tree(tab_tag.find(".list-channel .entries"), channel => {
|
||||
build_channel_tree(connection, tab_tag.find(".list-channel .entries"), channel => {
|
||||
if(current_channel == channel) return;
|
||||
|
||||
current_channel = channel;
|
||||
|
@ -745,7 +745,7 @@ namespace Modals {
|
|||
|
||||
const resolve_client = () => {
|
||||
let client_uid = tag_select_uid.val() as string;
|
||||
globalClient.serverConnection.command_helper.info_from_uid(client_uid).then(result => {
|
||||
connection.serverConnection.command_helper.info_from_uid(client_uid).then(result => {
|
||||
if(!result || result.length == 0) return Promise.reject("invalid data");
|
||||
tag_select_uid.attr('pattern', null).removeClass('is-invalid');
|
||||
|
||||
|
@ -778,7 +778,7 @@ namespace Modals {
|
|||
}
|
||||
}
|
||||
|
||||
function apply_client_permission(editor: PermissionEditor, tab_tag: JQuery) {
|
||||
function apply_client_permission(connection: ConnectionHandler, editor: PermissionEditor, tab_tag: JQuery) {
|
||||
let current_cldbid: number = 0;
|
||||
|
||||
/* the editor */
|
||||
|
@ -788,7 +788,7 @@ namespace Modals {
|
|||
console.error("Channel tab show");
|
||||
|
||||
pe_client.append(editor.container);
|
||||
if(globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CLIENT_PERMISSION_LIST).granted(1)) {
|
||||
if(connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CLIENT_PERMISSION_LIST).granted(1)) {
|
||||
if(current_cldbid)
|
||||
editor.set_mode(PermissionEditorMode.VISIBLE);
|
||||
else
|
||||
|
@ -801,7 +801,7 @@ namespace Modals {
|
|||
editor.set_listener_update(() => {
|
||||
if(!current_cldbid) return;
|
||||
|
||||
globalClient.permissions.requestClientPermissions(current_cldbid).then(result => {
|
||||
connection.permissions.requestClientPermissions(current_cldbid).then(result => {
|
||||
editor.set_permissions(result);
|
||||
editor.set_mode(PermissionEditorMode.VISIBLE);
|
||||
}).catch(error => {
|
||||
|
@ -821,7 +821,7 @@ namespace Modals {
|
|||
permission.id,
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("clientaddperm", {
|
||||
return connection.serverConnection.send_command("clientaddperm", {
|
||||
cldbid: current_cldbid,
|
||||
permid: permission.id,
|
||||
});
|
||||
|
@ -832,7 +832,7 @@ namespace Modals {
|
|||
value.granted,
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("clientaddperm", {
|
||||
return connection.serverConnection.send_command("clientaddperm", {
|
||||
cldbid: current_cldbid,
|
||||
permid: permission.id_grant(),
|
||||
});
|
||||
|
@ -848,7 +848,7 @@ namespace Modals {
|
|||
value.flag_negate
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("clientaddperm", {
|
||||
return connection.serverConnection.send_command("clientaddperm", {
|
||||
cldbid: current_cldbid,
|
||||
permid: permission.id,
|
||||
permvalue: value.value,
|
||||
|
@ -862,7 +862,7 @@ namespace Modals {
|
|||
value.granted,
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("clientaddperm", {
|
||||
return connection.serverConnection.send_command("clientaddperm", {
|
||||
cldbid: current_cldbid,
|
||||
permid: permission.id_grant(),
|
||||
permvalue: value.granted,
|
||||
|
@ -888,7 +888,7 @@ namespace Modals {
|
|||
|
||||
const resolve_client = () => {
|
||||
let client_uid = tag_select_uid.val() as string;
|
||||
globalClient.serverConnection.command_helper.info_from_uid(client_uid).then(result => {
|
||||
connection.serverConnection.command_helper.info_from_uid(client_uid).then(result => {
|
||||
if(!result || result.length == 0) return Promise.reject("invalid data");
|
||||
tag_select_uid.attr('pattern', null).removeClass('is-invalid');
|
||||
|
||||
|
@ -920,7 +920,7 @@ namespace Modals {
|
|||
tab_tag.find(".client-select-uid").on('change', event => resolve_client());
|
||||
}
|
||||
|
||||
function apply_channel_permission(editor: PermissionEditor, tab_tag: JQuery) {
|
||||
function apply_channel_permission(connection: ConnectionHandler, editor: PermissionEditor, tab_tag: JQuery) {
|
||||
let current_channel: ChannelEntry | undefined;
|
||||
|
||||
/* the editor */
|
||||
|
@ -929,7 +929,7 @@ namespace Modals {
|
|||
tab_tag.on('show', event => {
|
||||
console.error("Channel tab show");
|
||||
pe_channel.append(editor.container);
|
||||
if(globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CHANNEL_PERMISSION_LIST).granted(1))
|
||||
if(connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CHANNEL_PERMISSION_LIST).granted(1))
|
||||
editor.set_mode(PermissionEditorMode.VISIBLE);
|
||||
else {
|
||||
editor.set_mode(PermissionEditorMode.NO_PERMISSION);
|
||||
|
@ -939,7 +939,7 @@ namespace Modals {
|
|||
editor.set_listener_update(() => {
|
||||
if(!current_channel) return;
|
||||
|
||||
globalClient.permissions.requestChannelPermissions(current_channel.channelId).then(result => editor.set_permissions(result)).catch(error => {
|
||||
connection.permissions.requestChannelPermissions(current_channel.channelId).then(result => editor.set_permissions(result)).catch(error => {
|
||||
console.log(error); //TODO handling?
|
||||
});
|
||||
});
|
||||
|
@ -956,7 +956,7 @@ namespace Modals {
|
|||
permission.id,
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("channeldelperm", {
|
||||
return connection.serverConnection.send_command("channeldelperm", {
|
||||
cid: current_channel.channelId,
|
||||
permid: permission.id,
|
||||
});
|
||||
|
@ -968,7 +968,7 @@ namespace Modals {
|
|||
value.granted,
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("channeldelperm", {
|
||||
return connection.serverConnection.send_command("channeldelperm", {
|
||||
cid: current_channel.channelId,
|
||||
permid: permission.id_grant(),
|
||||
});
|
||||
|
@ -984,7 +984,7 @@ namespace Modals {
|
|||
value.flag_negate
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("channeladdperm", {
|
||||
return connection.serverConnection.send_command("channeladdperm", {
|
||||
cid: current_channel.channelId,
|
||||
permid: permission.id,
|
||||
permvalue: value.value,
|
||||
|
@ -999,7 +999,7 @@ namespace Modals {
|
|||
value.granted,
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("channeladdperm", {
|
||||
return connection.serverConnection.send_command("channeladdperm", {
|
||||
cid: current_channel.channelId,
|
||||
permid: permission.id_grant(),
|
||||
permvalue: value.granted,
|
||||
|
@ -1016,13 +1016,13 @@ namespace Modals {
|
|||
}
|
||||
|
||||
let channel_list = tab_tag.find(".list-channel .entries");
|
||||
build_channel_tree(channel_list, channel => {
|
||||
build_channel_tree(connection, channel_list, channel => {
|
||||
current_channel = channel;
|
||||
editor.trigger_update();
|
||||
});
|
||||
}
|
||||
|
||||
function apply_channel_groups(editor: PermissionEditor, tab_tag: JQuery) {
|
||||
function apply_channel_groups(connection: ConnectionHandler, editor: PermissionEditor, tab_tag: JQuery) {
|
||||
let current_group;
|
||||
|
||||
/* the editor */
|
||||
|
@ -1031,7 +1031,7 @@ namespace Modals {
|
|||
tab_tag.on('show', event => {
|
||||
console.error("Channel group tab show");
|
||||
pe_server.append(editor.container);
|
||||
if(globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CHANNELGROUP_PERMISSION_LIST).granted(1))
|
||||
if(connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CHANNELGROUP_PERMISSION_LIST).granted(1))
|
||||
editor.set_mode(PermissionEditorMode.VISIBLE);
|
||||
else {
|
||||
editor.set_mode(PermissionEditorMode.NO_PERMISSION);
|
||||
|
@ -1041,7 +1041,7 @@ namespace Modals {
|
|||
editor.set_listener_update(() => {
|
||||
if(!current_group) return;
|
||||
|
||||
globalClient.groups.request_permissions(current_group).then(result => editor.set_permissions(result)).catch(error => {
|
||||
connection.groups.request_permissions(current_group).then(result => editor.set_permissions(result)).catch(error => {
|
||||
console.log(error); //TODO handling?
|
||||
});
|
||||
});
|
||||
|
@ -1058,7 +1058,7 @@ namespace Modals {
|
|||
permission.id,
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("channelgroupdelperm", {
|
||||
return connection.serverConnection.send_command("channelgroupdelperm", {
|
||||
cgid: current_group.id,
|
||||
permid: permission.id,
|
||||
});
|
||||
|
@ -1069,7 +1069,7 @@ namespace Modals {
|
|||
value.granted,
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("channelgroupdelperm", {
|
||||
return connection.serverConnection.send_command("channelgroupdelperm", {
|
||||
cgid: current_group.id,
|
||||
permid: permission.id_grant(),
|
||||
});
|
||||
|
@ -1085,7 +1085,7 @@ namespace Modals {
|
|||
value.flag_negate
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("channelgroupaddperm", {
|
||||
return connection.serverConnection.send_command("channelgroupaddperm", {
|
||||
cgid: current_group.id,
|
||||
permid: permission.id,
|
||||
permvalue: value.value,
|
||||
|
@ -1099,7 +1099,7 @@ namespace Modals {
|
|||
value.granted,
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("channelgroupaddperm", {
|
||||
return connection.serverConnection.send_command("channelgroupaddperm", {
|
||||
cgid: current_group.id,
|
||||
permid: permission.id_grant(),
|
||||
permvalue: value.granted,
|
||||
|
@ -1120,14 +1120,24 @@ namespace Modals {
|
|||
{
|
||||
let group_list = tab_tag.find(".list-group-channel .entries");
|
||||
|
||||
for(let group of globalClient.groups.channelGroups.sort(GroupManager.sorter())) {
|
||||
const allow_query_groups = connection.permissions.neededPermission(PermissionType.B_SERVERINSTANCE_MODIFY_QUERYGROUP).granted(1);
|
||||
const allow_template_groups = connection.permissions.neededPermission(PermissionType.B_SERVERINSTANCE_MODIFY_TEMPLATES).granted(1);
|
||||
for(let group of connection.groups.channelGroups.sort(GroupManager.sorter())) {
|
||||
if(group.type == GroupType.QUERY) {
|
||||
if(!allow_query_groups)
|
||||
continue;
|
||||
} else if(group.type == GroupType.TEMPLATE) {
|
||||
if(!allow_template_groups)
|
||||
continue;
|
||||
}
|
||||
|
||||
let tag = $.spawn("div").addClass("group").attr("group-id", group.id);
|
||||
globalClient.fileManager.icons.generateTag(group.properties.iconid).appendTo(tag);
|
||||
connection.fileManager.icons.generateTag(group.properties.iconid).appendTo(tag);
|
||||
{
|
||||
let name = $.spawn("a").text(group.name + " (" + group.id + ")").addClass("name");
|
||||
if(group.properties.savedb)
|
||||
name.addClass("savedb");
|
||||
if(globalClient.channelTree.server.properties.virtualserver_default_channel_group == group.id)
|
||||
if(connection.channelTree.server.properties.virtualserver_default_channel_group == group.id)
|
||||
name.addClass("default");
|
||||
name.appendTo(tag);
|
||||
}
|
||||
|
@ -1156,21 +1166,30 @@ namespace Modals {
|
|||
b_virtualserver_channelclient_permission_list
|
||||
b_virtualserver_playlist_permission_list
|
||||
*/
|
||||
function apply_server_groups(editor: PermissionEditor, tab_tag: JQuery) {
|
||||
function apply_server_groups(connection: ConnectionHandler, editor: PermissionEditor, tab_tag: JQuery) {
|
||||
let current_group;
|
||||
|
||||
/* list all groups */
|
||||
{
|
||||
let group_list = tab_tag.find(".list-group-server .entries");
|
||||
|
||||
for(let group of globalClient.groups.serverGroups.sort(GroupManager.sorter())) {
|
||||
const allow_query_groups = connection.permissions.neededPermission(PermissionType.B_SERVERINSTANCE_MODIFY_QUERYGROUP).granted(1);
|
||||
const allow_template_groups = connection.permissions.neededPermission(PermissionType.B_SERVERINSTANCE_MODIFY_TEMPLATES).granted(1);
|
||||
for(const group of connection.groups.serverGroups.sort(GroupManager.sorter())) {
|
||||
if(group.type == GroupType.QUERY) {
|
||||
if(!allow_query_groups)
|
||||
continue;
|
||||
} else if(group.type == GroupType.TEMPLATE) {
|
||||
if(!allow_template_groups)
|
||||
continue;
|
||||
}
|
||||
let tag = $.spawn("div").addClass("group").attr("group-id", group.id);
|
||||
globalClient.fileManager.icons.generateTag(group.properties.iconid).appendTo(tag);
|
||||
connection.fileManager.icons.generateTag(group.properties.iconid).appendTo(tag);
|
||||
{
|
||||
let name = $.spawn("a").text(group.name + " (" + group.id + ")").addClass("name");
|
||||
if(group.properties.savedb)
|
||||
name.addClass("savedb");
|
||||
if(globalClient.channelTree.server.properties.virtualserver_default_server_group == group.id)
|
||||
if(connection.channelTree.server.properties.virtualserver_default_server_group == group.id)
|
||||
name.addClass("default");
|
||||
name.appendTo(tag);
|
||||
}
|
||||
|
@ -1194,14 +1213,14 @@ namespace Modals {
|
|||
tab_tag.on('show', event => {
|
||||
console.error("Server tab show");
|
||||
pe_server.append(editor.container);
|
||||
if(globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_SERVERGROUP_PERMISSION_LIST).granted(1))
|
||||
if(connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_SERVERGROUP_PERMISSION_LIST).granted(1))
|
||||
editor.set_mode(PermissionEditorMode.VISIBLE);
|
||||
else {
|
||||
editor.set_mode(PermissionEditorMode.NO_PERMISSION);
|
||||
return;
|
||||
}
|
||||
editor.set_listener_update(() => {
|
||||
globalClient.groups.request_permissions(current_group).then(result => editor.set_permissions(result)).catch(error => {
|
||||
connection.groups.request_permissions(current_group).then(result => editor.set_permissions(result)).catch(error => {
|
||||
console.log(error); //TODO handling?
|
||||
});
|
||||
});
|
||||
|
@ -1218,7 +1237,7 @@ namespace Modals {
|
|||
permission.id,
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("servergroupdelperm", {
|
||||
return connection.serverConnection.send_command("servergroupdelperm", {
|
||||
sgid: current_group.id,
|
||||
permid: permission.id,
|
||||
});
|
||||
|
@ -1229,7 +1248,7 @@ namespace Modals {
|
|||
value.granted,
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("servergroupdelperm", {
|
||||
return connection.serverConnection.send_command("servergroupdelperm", {
|
||||
sgid: current_group.id,
|
||||
permid: permission.id_grant(),
|
||||
});
|
||||
|
@ -1245,7 +1264,7 @@ namespace Modals {
|
|||
value.flag_negate
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("servergroupaddperm", {
|
||||
return connection.serverConnection.send_command("servergroupaddperm", {
|
||||
sgid: current_group.id,
|
||||
permid: permission.id,
|
||||
permvalue: value.value,
|
||||
|
@ -1259,7 +1278,7 @@ namespace Modals {
|
|||
value.granted,
|
||||
);
|
||||
|
||||
return globalClient.serverConnection.send_command("servergroupaddperm", {
|
||||
return connection.serverConnection.send_command("servergroupaddperm", {
|
||||
sgid: current_group.id,
|
||||
permid: permission.id_grant(),
|
||||
permvalue: value.granted,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
/// <reference path="../../client.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export function spawnPlaylistSongInfo(song: PlaylistSong) {
|
||||
|
@ -68,7 +68,7 @@ namespace Modals {
|
|||
modal.open();
|
||||
}
|
||||
|
||||
export function spawnPlaylistEdit(client: TSClient, playlist: Playlist) {
|
||||
export function spawnPlaylistEdit(client: ConnectionHandler, playlist: Playlist) {
|
||||
let modal: Modal;
|
||||
let changed_properties = {};
|
||||
let changed_permissions = {};
|
||||
|
@ -153,7 +153,7 @@ namespace Modals {
|
|||
return modal;
|
||||
}
|
||||
|
||||
function apply_songs(tag: JQuery, client: TSClient, playlist: Playlist) {
|
||||
function apply_songs(tag: JQuery, client: ConnectionHandler, playlist: Playlist) {
|
||||
const owns_playlist = playlist.playlist_owner_dbid == client.getClient().properties.client_database_id;
|
||||
const song_tag = tag.find(".container-songs");
|
||||
|
||||
|
@ -254,7 +254,7 @@ namespace Modals {
|
|||
return set_current_song;
|
||||
}
|
||||
|
||||
function apply_permissions(tag: JQuery, client: TSClient, playlist: Playlist, change_permission: (key: string, value: number) => any) {
|
||||
function apply_permissions(tag: JQuery, client: ConnectionHandler, playlist: Playlist, change_permission: (key: string, value: number) => any) {
|
||||
const owns_playlist = playlist.playlist_owner_dbid == client.getClient().properties.client_database_id;
|
||||
const permission_tag = tag.find(".container-permissions");
|
||||
const nopermission_tag = tag.find(".container-no-permissions");
|
||||
|
@ -294,7 +294,7 @@ namespace Modals {
|
|||
return update_permissions;
|
||||
}
|
||||
|
||||
function apply_properties(tag: JQuery, client: TSClient, playlist: Playlist, change_property: (key: string, value: string) => any, callback_current_song: (id: number) => any) {
|
||||
function apply_properties(tag: JQuery, client: ConnectionHandler, playlist: Playlist, change_property: (key: string, value: string) => any, callback_current_song: (id: number) => any) {
|
||||
const owns_playlist = playlist.playlist_owner_dbid == client.getClient().properties.client_database_id;
|
||||
|
||||
client.serverConnection.command_helper.request_playlist_info(playlist.playlist_id).then(info => {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
/// <reference path="../../client.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export function spawnPlaylistManage(client: TSClient) {
|
||||
export function spawnPlaylistManage(client: ConnectionHandler) {
|
||||
let modal: Modal;
|
||||
let selected_playlist: Playlist;
|
||||
let available_playlists: Playlist[];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
/// <reference path="../../client.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export function spawnPoke(invoker: {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
/// <reference path="../../client.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export function spawnQueryCreate(callback_created?: (user, pass) => any) {
|
||||
export function spawnQueryCreate(connection: ConnectionHandler, callback_created?: (user, pass) => any) {
|
||||
let modal;
|
||||
modal = createModal({
|
||||
header: tr("Create a server query login"),
|
||||
|
@ -34,11 +34,11 @@ namespace Modals {
|
|||
return true;
|
||||
}
|
||||
};
|
||||
globalClient.serverConnection.command_handler_boss().register_single_handler(single_handler);
|
||||
globalClient.serverConnection.send_command("querycreate", {
|
||||
connection.serverConnection.command_handler_boss().register_single_handler(single_handler);
|
||||
connection.serverConnection.send_command("querycreate", {
|
||||
client_login_name: name
|
||||
}).catch(error => {
|
||||
globalClient.serverConnection.command_handler_boss().remove_single_handler(single_handler);
|
||||
connection.serverConnection.command_handler_boss().remove_single_handler(single_handler);
|
||||
|
||||
if(error instanceof CommandResult)
|
||||
error = error.extra_message || error.message;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
/// <reference path="../../client.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export function spawnQueryManage(client: TSClient) {
|
||||
export function spawnQueryManage(client: ConnectionHandler) {
|
||||
let modal: Modal;
|
||||
let selected_query: QueryListEntry;
|
||||
|
||||
|
@ -66,7 +66,7 @@ namespace Modals {
|
|||
|
||||
template.find(".footer .buttons .button-refresh").on('click', update_list);
|
||||
template.find(".button-query-create").on('click', () => {
|
||||
Modals.spawnQueryCreate((user, pass) => update_list());
|
||||
Modals.spawnQueryCreate(client, (user, pass) => update_list());
|
||||
});
|
||||
template.find(".button-query-rename").on('click', () => {
|
||||
if(!selected_query) return;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export function createServerModal(server: ServerEntry, callback: (properties?: ServerProperties) => any) {
|
||||
|
@ -37,6 +37,8 @@ namespace Modals {
|
|||
}
|
||||
|
||||
function server_applyGeneralListener(properties: ServerProperties, server: ServerEntry, tag: JQuery, button: JQuery) {
|
||||
const connection_handler = server.channelTree.client;
|
||||
|
||||
let updateButton = () => {
|
||||
if(tag.find(".input_error").length == 0)
|
||||
button.removeAttr("disabled");
|
||||
|
@ -50,11 +52,11 @@ namespace Modals {
|
|||
if(this.value.length < 1 || this.value.length > 70)
|
||||
$(this).addClass("input_error");
|
||||
updateButton();
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_NAME).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_NAME).granted(1));
|
||||
|
||||
tag.find(".virtualserver_name_phonetic").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_name_phonetic = this.value;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_NAME).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_NAME).granted(1));
|
||||
|
||||
tag.find(".virtualserver_password").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_flag_password = this.value.length != 0;
|
||||
|
@ -63,16 +65,16 @@ namespace Modals {
|
|||
|
||||
$(this).removeClass("input_error");
|
||||
if(!properties.virtualserver_flag_password)
|
||||
if(globalClient.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_FORCE_PASSWORD).granted(1))
|
||||
if(connection_handler.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_MODIFY_WITH_FORCE_PASSWORD).granted(1))
|
||||
$(this).addClass("input_error");
|
||||
updateButton();
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_PASSWORD).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_PASSWORD).granted(1));
|
||||
|
||||
|
||||
|
||||
tag.find(".virtualserver_maxclients").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_maxclients = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_MAXCLIENTS).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_MAXCLIENTS).granted(1));
|
||||
|
||||
tag.find(".virtualserver_reserved_slots").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_reserved_slots = this.valueAsNumber;
|
||||
|
@ -80,17 +82,17 @@ namespace Modals {
|
|||
if(this.valueAsNumber > properties.virtualserver_maxclients)
|
||||
$(this).addClass("input_error");
|
||||
updateButton();
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_RESERVED_SLOTS).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_RESERVED_SLOTS).granted(1));
|
||||
|
||||
tag.find(".virtualserver_welcomemessage").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_welcomemessage = this.value;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_WELCOMEMESSAGE).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_WELCOMEMESSAGE).granted(1));
|
||||
|
||||
tag.find(".button-select-icon").on('click', event => {
|
||||
Modals.spawnIconSelect(globalClient, id => {
|
||||
Modals.spawnIconSelect(connection_handler, id => {
|
||||
const icon_node = tag.find(".button-select-icon").find(".icon-node");
|
||||
icon_node.empty();
|
||||
icon_node.append(globalClient.fileManager.icons.generateTag(id));
|
||||
icon_node.append(connection_handler.fileManager.icons.generateTag(id));
|
||||
|
||||
console.log("Selected icon ID: %d", id);
|
||||
properties.virtualserver_icon_id = id;
|
||||
|
@ -100,54 +102,56 @@ namespace Modals {
|
|||
|
||||
|
||||
function server_applyHostListener(server: ServerEntry, properties: ServerProperties, original_properties: ServerProperties, tag: JQuery, button: JQuery) {
|
||||
const connection_handler = server.channelTree.client;
|
||||
|
||||
tag.find(".virtualserver_host").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_host = this.value;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOST).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOST).granted(1));
|
||||
|
||||
tag.find(".virtualserver_port").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_port = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_PORT).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_PORT).granted(1));
|
||||
|
||||
|
||||
tag.find(".virtualserver_hostmessage").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_hostmessage = this.value;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTMESSAGE).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTMESSAGE).granted(1));
|
||||
|
||||
tag.find(".virtualserver_hostmessage_mode").change(function (this: HTMLSelectElement) {
|
||||
properties.virtualserver_hostmessage_mode = this.selectedIndex;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTMESSAGE).granted(1))
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTMESSAGE).granted(1))
|
||||
.find("option").eq(original_properties.virtualserver_hostmessage_mode).prop('selected', true);
|
||||
|
||||
|
||||
|
||||
tag.find(".virtualserver_hostbanner_url").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_hostbanner_url = this.value;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTBANNER).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTBANNER).granted(1));
|
||||
|
||||
tag.find(".virtualserver_hostbanner_gfx_url").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_hostbanner_gfx_url = this.value;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTBANNER).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTBANNER).granted(1));
|
||||
|
||||
tag.find(".virtualserver_hostbanner_gfx_interval").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_hostbanner_gfx_interval = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTBANNER).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTBANNER).granted(1));
|
||||
|
||||
tag.find(".virtualserver_hostbanner_mode").change(function (this: HTMLSelectElement) {
|
||||
properties.virtualserver_hostbanner_mode = this.selectedIndex;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTMESSAGE).granted(1))
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTMESSAGE).granted(1))
|
||||
.find("option").eq(original_properties.virtualserver_hostbanner_mode).prop('selected', true);
|
||||
|
||||
tag.find(".virtualserver_hostbutton_tooltip").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_hostbutton_tooltip = this.value;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTBUTTON).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTBUTTON).granted(1));
|
||||
|
||||
tag.find(".virtualserver_hostbutton_url").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_hostbutton_url = this.value;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTBUTTON).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTBUTTON).granted(1));
|
||||
|
||||
tag.find(".virtualserver_hostbutton_gfx_url").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_hostbutton_gfx_url = this.value;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTBUTTON).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_HOSTBUTTON).granted(1));
|
||||
|
||||
server.updateProperties().then(() => {
|
||||
tag.find(".virtualserver_host").val(server.properties.virtualserver_host);
|
||||
|
@ -156,6 +160,8 @@ namespace Modals {
|
|||
}
|
||||
|
||||
function server_applyMessages(properties: ServerProperties, server: ServerEntry, tag: JQuery) {
|
||||
const connection_handler = server.channelTree.client;
|
||||
|
||||
server.updateProperties().then(() => {
|
||||
tag.find(".virtualserver_default_client_description").val(server.properties.virtualserver_default_client_description);
|
||||
tag.find(".virtualserver_default_channel_description").val(server.properties.virtualserver_default_channel_description);
|
||||
|
@ -164,18 +170,20 @@ namespace Modals {
|
|||
|
||||
tag.find(".virtualserver_default_client_description").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_default_client_description = this.value;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_DEFAULT_MESSAGES).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_DEFAULT_MESSAGES).granted(1));
|
||||
|
||||
tag.find(".virtualserver_default_channel_description").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_default_channel_description = this.value;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_DEFAULT_MESSAGES).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_DEFAULT_MESSAGES).granted(1));
|
||||
|
||||
tag.find(".virtualserver_default_channel_topic").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_default_channel_topic = this.value;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_DEFAULT_MESSAGES).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_DEFAULT_MESSAGES).granted(1));
|
||||
}
|
||||
|
||||
function server_applyFlood(properties: ServerProperties, server: ServerEntry, tag: JQuery) {
|
||||
const connection_handler = server.channelTree.client;
|
||||
|
||||
server.updateProperties().then(() => {
|
||||
tag.find(".virtualserver_antiflood_points_tick_reduce").val(server.properties.virtualserver_antiflood_points_tick_reduce);
|
||||
tag.find(".virtualserver_antiflood_points_needed_command_block").val(server.properties.virtualserver_antiflood_points_needed_command_block);
|
||||
|
@ -184,34 +192,38 @@ namespace Modals {
|
|||
|
||||
tag.find(".virtualserver_antiflood_points_tick_reduce").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_antiflood_points_tick_reduce = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_ANTIFLOOD).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_ANTIFLOOD).granted(1));
|
||||
|
||||
tag.find(".virtualserver_antiflood_points_needed_command_block").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_antiflood_points_needed_command_block = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_ANTIFLOOD).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_ANTIFLOOD).granted(1));
|
||||
|
||||
tag.find(".virtualserver_antiflood_points_needed_ip_block").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_antiflood_points_needed_ip_block = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_ANTIFLOOD).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_ANTIFLOOD).granted(1));
|
||||
}
|
||||
|
||||
|
||||
function server_applySecurity(properties: ServerProperties, server: ServerEntry, tag: JQuery) {
|
||||
const connection_handler = server.channelTree.client;
|
||||
|
||||
server.updateProperties().then(() => {
|
||||
tag.find(".virtualserver_needed_identity_security_level").val(server.properties.virtualserver_needed_identity_security_level);
|
||||
});
|
||||
|
||||
tag.find(".virtualserver_needed_identity_security_level").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_needed_identity_security_level = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_NEEDED_IDENTITY_SECURITY_LEVEL).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_NEEDED_IDENTITY_SECURITY_LEVEL).granted(1));
|
||||
|
||||
tag.find(".virtualserver_codec_encryption_mode").change(function (this: HTMLSelectElement) {
|
||||
properties.virtualserver_codec_encryption_mode = this.selectedIndex;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_ANTIFLOOD).granted(1))
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_ANTIFLOOD).granted(1))
|
||||
.find("option").eq(server.properties.virtualserver_codec_encryption_mode).prop('selected', true);
|
||||
}
|
||||
|
||||
function server_applyMisc(properties: ServerProperties, server: ServerEntry, tag: JQuery) {
|
||||
const connection_handler = server.channelTree.client;
|
||||
|
||||
{ //TODO notify on tmp channeladmin group and vice versa
|
||||
{
|
||||
let groups_tag = tag.find(".default_server_group");
|
||||
|
@ -293,37 +305,38 @@ namespace Modals {
|
|||
|
||||
tag.find(".virtualserver_antiflood_points_needed_ip_block").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_antiflood_points_needed_ip_block = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_ANTIFLOOD).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_ANTIFLOOD).granted(1));
|
||||
|
||||
tag.find(".virtualserver_antiflood_points_needed_command_block").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_antiflood_points_needed_command_block = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_ANTIFLOOD).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_ANTIFLOOD).granted(1));
|
||||
|
||||
tag.find(".virtualserver_antiflood_points_tick_reduce").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_antiflood_points_tick_reduce = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_ANTIFLOOD).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_ANTIFLOOD).granted(1));
|
||||
|
||||
|
||||
tag.find(".virtualserver_complain_autoban_count").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_complain_autoban_count = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_COMPLAIN).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_COMPLAIN).granted(1));
|
||||
|
||||
tag.find(".virtualserver_complain_autoban_time").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_complain_autoban_time = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_COMPLAIN).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_COMPLAIN).granted(1));
|
||||
|
||||
tag.find(".virtualserver_complain_remove_time").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_complain_remove_time = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_COMPLAIN).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_COMPLAIN).granted(1));
|
||||
|
||||
|
||||
tag.find(".virtualserver_weblist_enabled").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_weblist_enabled = $(this).prop("checked");
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_WEBLIST).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_WEBLIST).granted(1));
|
||||
}
|
||||
|
||||
|
||||
function server_applyTransferListener(properties: ServerProperties, server: ServerEntry, tag: JQuery) {
|
||||
const connection_handler = server.channelTree.client;
|
||||
server.updateProperties().then(() => {
|
||||
//virtualserver_max_upload_total_bandwidth
|
||||
//virtualserver_upload_quota
|
||||
|
@ -339,16 +352,16 @@ namespace Modals {
|
|||
|
||||
tag.find(".virtualserver_max_upload_total_bandwidth").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_max_upload_total_bandwidth = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_FT_SETTINGS).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_FT_SETTINGS).granted(1));
|
||||
tag.find(".virtualserver_max_download_total_bandwidth").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_max_download_total_bandwidth = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_FT_SETTINGS).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_FT_SETTINGS).granted(1));
|
||||
|
||||
tag.find(".virtualserver_upload_quota").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_upload_quota = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_FT_QUOTAS).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_FT_QUOTAS).granted(1));
|
||||
tag.find(".virtualserver_download_quota").change(function (this: HTMLInputElement) {
|
||||
properties.virtualserver_download_quota = this.valueAsNumber;
|
||||
}).prop("disabled", !globalClient.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_FT_QUOTAS).granted(1));
|
||||
}).prop("disabled", !connection_handler.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_MODIFY_FT_QUOTAS).granted(1));
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
/// <reference path="../../../declarations/imports_client.d.ts" />
|
||||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../utils/tab.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
/// <reference path="../../voice/AudioController.ts" />
|
||||
/// <reference path="../../voice/VoiceClient.ts" />
|
||||
/// <reference path="../../profiles/Identity.ts" />
|
||||
|
||||
namespace Modals {
|
||||
|
@ -207,7 +205,7 @@ namespace Modals {
|
|||
client: native_client,
|
||||
valid_forum_identity: profiles.identities.valid_static_forum_identity(),
|
||||
forum_path: settings.static("forum_path"),
|
||||
voice_available: !!globalClient.voiceConnection
|
||||
voice_available: !settings.static_global(Settings.KEY_DISABLE_VOICE, false)
|
||||
});
|
||||
|
||||
initialiseVoiceListeners(modal, (template = template.tabify()).find(".settings_audio"));
|
||||
|
@ -262,7 +260,7 @@ namespace Modals {
|
|||
.text(message);
|
||||
};
|
||||
|
||||
if (globalClient.voiceConnection) {
|
||||
if (!settings.static_global(Settings.KEY_DISABLE_VOICE, false)) {
|
||||
{ //Initialized voice activation detection
|
||||
const vad_tag = tag.find(".settings-vad-container");
|
||||
|
||||
|
@ -274,7 +272,7 @@ namespace Modals {
|
|||
}
|
||||
{
|
||||
settings.changeGlobal("vad_type", select.value);
|
||||
globalClient.voiceConnection.voiceRecorder.reinitialiseVAD();
|
||||
voice_recoder.reinitialiseVAD();
|
||||
}
|
||||
|
||||
switch (select.value) {
|
||||
|
@ -288,10 +286,10 @@ namespace Modals {
|
|||
break;
|
||||
case "vad":
|
||||
let slider = vad_tag.find(".vad_vad_slider");
|
||||
let vad: VoiceActivityDetectorVAD = globalClient.voiceConnection.voiceRecorder.getVADHandler() as VoiceActivityDetectorVAD;
|
||||
let vad: VoiceActivityDetectorVAD = voice_recoder.getVADHandler() as VoiceActivityDetectorVAD;
|
||||
slider.val(vad.percentageThreshold);
|
||||
slider.trigger("change");
|
||||
globalClient.voiceConnection.voiceRecorder.update(true);
|
||||
voice_recoder.set_recording(true);
|
||||
vad.percentage_listener = per => {
|
||||
vad_tag.find(".vad_vad_bar_filler")
|
||||
.css("width", (100 - per) + "%");
|
||||
|
@ -323,7 +321,7 @@ namespace Modals {
|
|||
Object.assign(ppt_settings, event);
|
||||
settings.changeGlobal('vad_ppt_settings', ppt_settings);
|
||||
|
||||
globalClient.voiceConnection.voiceRecorder.reinitialiseVAD();
|
||||
voice_recoder.reinitialiseVAD();
|
||||
|
||||
ppt.unregister_key_listener(listener);
|
||||
modal.close();
|
||||
|
@ -340,7 +338,7 @@ namespace Modals {
|
|||
ppt_settings.delay = (<HTMLInputElement>event.target).valueAsNumber;
|
||||
settings.changeGlobal('vad_ppt_settings', ppt_settings);
|
||||
|
||||
globalClient.voiceConnection.voiceRecorder.reinitialiseVAD();
|
||||
voice_recoder.reinitialiseVAD();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -348,13 +346,13 @@ namespace Modals {
|
|||
let slider = vad_tag.find(".vad_vad_slider");
|
||||
slider.on("input change", () => {
|
||||
settings.changeGlobal("vad_threshold", slider.val().toString());
|
||||
let vad = globalClient.voiceConnection.voiceRecorder.getVADHandler();
|
||||
let vad = voice_recoder.getVADHandler();
|
||||
if (vad instanceof VoiceActivityDetectorVAD)
|
||||
vad.percentageThreshold = slider.val() as number;
|
||||
vad_tag.find(".vad_vad_slider_value").text(slider.val().toString());
|
||||
});
|
||||
modal.properties.registerCloseListener(() => {
|
||||
let vad = globalClient.voiceConnection.voiceRecorder.getVADHandler();
|
||||
let vad = voice_recoder.getVADHandler();
|
||||
if (vad instanceof VoiceActivityDetectorVAD)
|
||||
vad.percentage_listener = undefined;
|
||||
|
||||
|
@ -386,7 +384,7 @@ namespace Modals {
|
|||
.appendTo(tag_select);
|
||||
|
||||
navigator.mediaDevices.enumerateDevices().then(devices => {
|
||||
const active_device = globalClient.voiceConnection.voiceRecorder.device_id();
|
||||
const active_device = voice_recoder.device_id();
|
||||
|
||||
for (const device of devices) {
|
||||
console.debug(tr("Got device %s (%s): %s (%o)"), device.deviceId, device.kind, device.label);
|
||||
|
@ -416,7 +414,7 @@ namespace Modals {
|
|||
let deviceId = selected_tag.attr("device-id");
|
||||
let groupId = selected_tag.attr("device-group");
|
||||
console.log(tr("Selected microphone device: id: %o group: %o"), deviceId, groupId);
|
||||
globalClient.voiceConnection.voiceRecorder.change_device(deviceId, groupId);
|
||||
voice_recoder.change_device(deviceId, groupId);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -519,7 +517,7 @@ namespace Modals {
|
|||
});
|
||||
|
||||
entry.find(".button-playback").on('click', event => {
|
||||
sound.play(sound_name as Sound);
|
||||
sound.manager.play(sound_name as Sound);
|
||||
});
|
||||
|
||||
entry_tag.append(entry);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export function spawnYesNo(header: BodyCreator, body: BodyCreator, callback: (_: boolean) => any, properties?: {
|
||||
|
|
|
@ -141,13 +141,13 @@ class ServerEntry {
|
|||
name: tr("Show server info"),
|
||||
callback: () => {
|
||||
trigger_close = false;
|
||||
this.channelTree.client.selectInfo.open_popover()
|
||||
this.channelTree.client.select_info.open_popover()
|
||||
},
|
||||
icon: "client-about",
|
||||
visible: this.channelTree.client.selectInfo.is_popover()
|
||||
visible: this.channelTree.client.select_info.is_popover()
|
||||
}, {
|
||||
type: MenuEntryType.HR,
|
||||
visible: this.channelTree.client.selectInfo.is_popover(),
|
||||
visible: this.channelTree.client.select_info.is_popover(),
|
||||
name: ''
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
|
@ -159,7 +159,7 @@ class ServerEntry {
|
|||
console.log(tr("Changed properties: %o"), properties);
|
||||
if (properties)
|
||||
this.channelTree.client.serverConnection.send_command("serveredit", properties).then(() => {
|
||||
sound.play(Sound.SERVER_EDITED_SELF);
|
||||
this.channelTree.client.sound.play(Sound.SERVER_EDITED_SELF);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -210,6 +210,8 @@ class ServerEntry {
|
|||
|
||||
if(variable.key == "virtualserver_name") {
|
||||
this.htmlTag.find(".name").text(variable.value);
|
||||
this.channelTree.client.tag_connection_handler.find(".server-name").text(variable.value);
|
||||
server_connections.update_ui();
|
||||
} else if(variable.key == "virtualserver_icon_id") {
|
||||
if(this.channelTree.client.fileManager && this.channelTree.client.fileManager.icons)
|
||||
this.htmlTag.find(".icon_property").replaceWith(this.channelTree.client.fileManager.icons.generateTag(this.properties.virtualserver_icon_id).addClass("icon_property"));
|
||||
|
@ -218,7 +220,7 @@ class ServerEntry {
|
|||
}
|
||||
}
|
||||
if(update_bannner)
|
||||
this.channelTree.client.selectInfo.update_banner();
|
||||
this.channelTree.client.select_info.update_banner();
|
||||
|
||||
group.end();
|
||||
if(is_self_notify && this.info_request_promise_resolve) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/// <reference path="../voice/VoiceHandler.ts" />
|
||||
/// <reference path="../client.ts" />
|
||||
/// <reference path="../contextMenu.ts" />
|
||||
/// <reference path="../ConnectionHandler.ts" />
|
||||
/// <reference path="../proto.ts" />
|
||||
/// <reference path="channel.ts" />
|
||||
/// <reference path="client.ts" />
|
||||
|
@ -8,10 +7,9 @@
|
|||
|
||||
|
||||
class ChannelTree {
|
||||
client: TSClient;
|
||||
htmlTree: JQuery;
|
||||
htmlTree_parent: JQuery;
|
||||
client: ConnectionHandler;
|
||||
server: ServerEntry;
|
||||
|
||||
channels: ChannelEntry[];
|
||||
clients: ClientEntry[];
|
||||
|
||||
|
@ -19,6 +17,9 @@ class ChannelTree {
|
|||
currently_selected_context_callback: (event) => any = undefined;
|
||||
readonly client_mover: ClientMover;
|
||||
|
||||
private _tag_container: JQuery;
|
||||
private _tag_entries: JQuery;
|
||||
|
||||
private _tree_detached: boolean = false;
|
||||
private _show_queries: boolean;
|
||||
private channel_last?: ChannelEntry;
|
||||
|
@ -26,18 +27,19 @@ class ChannelTree {
|
|||
|
||||
private selected_event?: Event;
|
||||
|
||||
constructor(client, htmlTree) {
|
||||
constructor(client) {
|
||||
document.addEventListener("touchstart", function(){}, true);
|
||||
|
||||
this.client = client;
|
||||
this.htmlTree = htmlTree;
|
||||
this.htmlTree_parent = this.htmlTree.parent();
|
||||
|
||||
this._tag_container = $.spawn("div").addClass("channel-tree-container");
|
||||
this._tag_entries = $.spawn("div").addClass("channel-tree");
|
||||
|
||||
this.client_mover = new ClientMover(this);
|
||||
this.reset();
|
||||
|
||||
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
|
||||
this.htmlTree.parent().on("contextmenu", (event) => {
|
||||
this._tag_container.on("contextmenu", (event) => {
|
||||
if(event.isDefaultPrevented()) return;
|
||||
|
||||
for(const element of document.elementsFromPoint(event.pageX, event.pageY))
|
||||
|
@ -54,7 +56,7 @@ class ChannelTree {
|
|||
});
|
||||
}
|
||||
|
||||
this.htmlTree.on('resize', this.handle_resized.bind(this));
|
||||
this._tag_container.on('resize', this.handle_resized.bind(this));
|
||||
|
||||
/* TODO release these events again when ChannelTree get deinitialized */
|
||||
$(document).on('click', event => {
|
||||
|
@ -62,19 +64,23 @@ class ChannelTree {
|
|||
this.selected_event = undefined;
|
||||
});
|
||||
$(document).on('keydown', this.handle_key_press.bind(this));
|
||||
this.htmlTree.on('click', event => {{
|
||||
this._tag_container.on('click', event => {{
|
||||
this.selected_event = event.originalEvent;
|
||||
}});
|
||||
}
|
||||
|
||||
tag_tree() : JQuery {
|
||||
return this._tag_container;
|
||||
}
|
||||
|
||||
hide_channel_tree() {
|
||||
this.htmlTree.detach();
|
||||
this._tag_entries.detach();
|
||||
this._tree_detached = true;
|
||||
}
|
||||
|
||||
show_channel_tree() {
|
||||
this._tree_detached = false;
|
||||
this.htmlTree.appendTo(this.htmlTree_parent);
|
||||
this._tag_entries.appendTo(this._tag_container);
|
||||
|
||||
this.channels.forEach(e => e.recalculate_repetitive_name());
|
||||
}
|
||||
|
@ -99,14 +105,14 @@ class ChannelTree {
|
|||
|
||||
initialiseHead(serverName: string, address: ServerAddress) {
|
||||
this.server = new ServerEntry(this, serverName, address);
|
||||
this.server.htmlTag.appendTo(this.htmlTree);
|
||||
this.server.htmlTag.appendTo(this._tag_entries);
|
||||
this.server.initializeListener();
|
||||
}
|
||||
|
||||
private __deleteAnimation(element: ChannelEntry | ClientEntry) {
|
||||
let tag = element instanceof ChannelEntry ? element.rootTag() : element.tag;
|
||||
this.htmlTree.find(tag).fadeOut("slow", () => {
|
||||
tag.remove();
|
||||
tag.fadeOut("slow", () => {
|
||||
tag.detach();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -152,7 +158,7 @@ class ChannelTree {
|
|||
this.channels.push(channel);
|
||||
|
||||
let elm = undefined;
|
||||
let tag = this.htmlTree;
|
||||
let tag = this._tag_entries;
|
||||
|
||||
let previous_channel = null;
|
||||
if(channel.hasParent()) {
|
||||
|
@ -266,7 +272,7 @@ class ChannelTree {
|
|||
channel.channel_next.channel_previous = channel;
|
||||
}
|
||||
} else {
|
||||
this.htmlTree.find(".server").after(channel.rootTag());
|
||||
this._tag_entries.find(".server").after(channel.rootTag());
|
||||
|
||||
channel.channel_next = this.channel_first;
|
||||
if(this.channel_first)
|
||||
|
@ -290,24 +296,45 @@ class ChannelTree {
|
|||
}
|
||||
}
|
||||
|
||||
deleteClient(client: ClientEntry) {
|
||||
deleteClient(client: ClientEntry, animate_tag?: boolean) {
|
||||
this.clients.remove(client);
|
||||
this.__deleteAnimation(client);
|
||||
if(typeof(animate_tag) !== "boolean" || animate_tag)
|
||||
this.__deleteAnimation(client);
|
||||
else
|
||||
client.tag.detach();
|
||||
client.onDelete();
|
||||
|
||||
const voice_connection = this.client.serverConnection.voice_connection();
|
||||
if(client.get_audio_handle()) {
|
||||
if(!voice_connection) {
|
||||
log.warn(LogCategory.VOICE, tr("Deleting client with a voice handle, but we haven't a voice connection!"));
|
||||
} else {
|
||||
voice_connection.unregister_client(client.get_audio_handle());
|
||||
}
|
||||
}
|
||||
client.set_audio_handle(undefined); /* just to be sure */
|
||||
}
|
||||
|
||||
registerClient(client: ClientEntry) {
|
||||
this.clients.push(client);
|
||||
client.channelTree = this;
|
||||
|
||||
const voice_connection = this.client.serverConnection.voice_connection();
|
||||
if(voice_connection)
|
||||
client.set_audio_handle(voice_connection.register_client(client.clientId()));
|
||||
}
|
||||
|
||||
insertClient(client: ClientEntry, channel: ChannelEntry) : ClientEntry {
|
||||
let newClient = this.findClient(client.clientId());
|
||||
if(newClient) client = newClient; //Got new client :)
|
||||
else
|
||||
this.clients.push(client);
|
||||
if(newClient)
|
||||
client = newClient; //Got new client :)
|
||||
else {
|
||||
this.registerClient(client);
|
||||
}
|
||||
|
||||
client.channelTree = this;
|
||||
client["_channel"] = channel;
|
||||
|
||||
let tag = client.tag;
|
||||
|
||||
|
||||
if(!this._show_queries && client.properties.client_type == ClientType.CLIENT_QUERY)
|
||||
client.tag.hide();
|
||||
else if(!this._tree_detached)
|
||||
|
@ -321,11 +348,6 @@ class ChannelTree {
|
|||
return client;
|
||||
}
|
||||
|
||||
registerClient(client: ClientEntry) {
|
||||
this.clients.push(client);
|
||||
client.channelTree = this;
|
||||
}
|
||||
|
||||
moveClient(client: ClientEntry, channel: ChannelEntry) {
|
||||
let oldChannel = client.currentChannel();
|
||||
client["_channel"] = channel;
|
||||
|
@ -397,7 +419,7 @@ class ChannelTree {
|
|||
|
||||
if(!$.isArray(this.currently_selected) || enforce_single) {
|
||||
this.currently_selected = entry;
|
||||
this.htmlTree.find(".selected").each(function (idx, e) {
|
||||
this._tag_entries.find(".selected").each(function (idx, e) {
|
||||
$(e).removeClass("selected");
|
||||
});
|
||||
} else {
|
||||
|
@ -427,7 +449,7 @@ class ChannelTree {
|
|||
else if(entry instanceof ServerEntry)
|
||||
(entry as ServerEntry).htmlTag.addClass("selected");
|
||||
|
||||
this.client.selectInfo.setCurrentSelected($.isArray(this.currently_selected) ? undefined : entry);
|
||||
this.client.select_info.setCurrentSelected($.isArray(this.currently_selected) ? undefined : entry);
|
||||
}
|
||||
|
||||
private callback_multiselect_channel(event) {
|
||||
|
@ -527,7 +549,7 @@ class ChannelTree {
|
|||
}, {
|
||||
flagset: [data.no_ip ? "no-ip" : "", data.no_hwid ? "no-hardware-id" : "", data.no_name ? "no-nickname" : ""]
|
||||
}).then(() => {
|
||||
sound.play(Sound.USER_BANNED);
|
||||
this.client.sound.play(Sound.USER_BANNED);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -586,14 +608,14 @@ class ChannelTree {
|
|||
this.server = null;
|
||||
this.clients = [];
|
||||
this.channels = [];
|
||||
this.htmlTree.children().detach(); //Do not remove the listener!
|
||||
this._tag_entries.children().detach(); //Do not remove the listener!
|
||||
|
||||
this.channel_first = undefined;
|
||||
this.channel_last = undefined;
|
||||
}
|
||||
|
||||
spawnCreateChannel(parent?: ChannelEntry) {
|
||||
Modals.createChannelModal(undefined, parent, this.client.permissions, (properties?, permissions?) => {
|
||||
Modals.createChannelModal(this.client, undefined, parent, this.client.permissions, (properties?, permissions?) => {
|
||||
if(!properties) return;
|
||||
properties["cpid"] = parent ? parent.channelId : 0;
|
||||
log.debug(LogCategory.CHANNEL, tr("Creating a new channel.\nProperties: %o\nPermissions: %o"), properties);
|
||||
|
@ -622,8 +644,8 @@ class ChannelTree {
|
|||
|
||||
return new Promise<ChannelEntry>(resolve => { resolve(channel); })
|
||||
}).then(channel => {
|
||||
chat.serverChat().appendMessage(tr("Channel {} successfully created!"), true, channel.generate_tag(true));
|
||||
sound.play(Sound.CHANNEL_CREATED);
|
||||
this.client.chat.serverChat().appendMessage(tr("Channel {} successfully created!"), true, channel.generate_tag(true));
|
||||
this.client.sound.play(Sound.CHANNEL_CREATED);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,248 +0,0 @@
|
|||
enum PlayerState {
|
||||
PREBUFFERING,
|
||||
PLAYING,
|
||||
BUFFERING,
|
||||
STOPPING,
|
||||
STOPPED
|
||||
}
|
||||
|
||||
class AudioController {
|
||||
private static _audioInstances: AudioController[] = [];
|
||||
private static _globalReplayScheduler: NodeJS.Timer;
|
||||
private static _timeIndex: number = 0;
|
||||
private static _audioDestinationStream: MediaStream;
|
||||
|
||||
static initializeAudioController() {
|
||||
if(!audio.player.initialize())
|
||||
console.warn(tr("Failed to initialize audio controller!"));
|
||||
sound.initialize().then(() => {
|
||||
console.log(tr("Sounds initialitzed"));
|
||||
});
|
||||
//this._globalReplayScheduler = setInterval(() => { AudioController.invokeNextReplay(); }, 20); //Fix me
|
||||
}
|
||||
|
||||
/*
|
||||
private static joinTracks(tracks: AudioBuffer[]) : Promise<AudioBuffer> {
|
||||
let length = Math.max.apply(Math, tracks.map(e => e.length));
|
||||
if(length == 0 || tracks.length == 0) return new Promise<AudioBuffer>((resolve, reject) => {}); //Do nothink
|
||||
|
||||
let context = new OfflineAudioContext(2, length, 44100);
|
||||
//let context = new OfflineAudioContext(tracks[0].numberOfChannels, tracks[0].length, tracks[0].sampleRate);
|
||||
|
||||
tracks.forEach(track => {
|
||||
let player = context.createBufferSource();
|
||||
player.buffer = track;
|
||||
player.connect(context.destination);
|
||||
player.start(0);
|
||||
});
|
||||
|
||||
return context.startRendering();
|
||||
}
|
||||
|
||||
static invokeNextReplay() {
|
||||
let replay: {controller: AudioController,buffer: AudioBuffer}[] = [];
|
||||
|
||||
for(let instance of this._audioInstances)
|
||||
if(instance.playerState == PlayerState.PLAYING || instance.playerState == PlayerState.STOPPING) {
|
||||
let entry = {controller: instance, buffer: instance.audioCache.pop_front() };
|
||||
instance.flagPlaying = !!entry.buffer;
|
||||
instance.testBufferQueue();
|
||||
if(!!entry.buffer) replay.push(entry);
|
||||
} else if(instance.flagPlaying) {
|
||||
instance.flagPlaying = false;
|
||||
instance.testBufferQueue();
|
||||
}
|
||||
|
||||
|
||||
this.joinTracks(replay.map(e => e.buffer)).then(buffer => {
|
||||
if(this._timeIndex < this._globalContext.currentTime) {
|
||||
this._timeIndex = this._globalContext.currentTime;
|
||||
console.log("Resetting time index!");
|
||||
}
|
||||
//console.log(buffer.length + "|" + buffer.duration);
|
||||
//console.log(buffer);
|
||||
|
||||
let player = this._globalContext.createBufferSource();
|
||||
player.buffer = buffer;
|
||||
player.connect(this._globalContext.destination);
|
||||
player.start(this._timeIndex);
|
||||
|
||||
this._timeIndex += buffer.duration;
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
speakerContext: AudioContext;
|
||||
private playerState: PlayerState = PlayerState.STOPPED;
|
||||
private audioCache: AudioBuffer[] = [];
|
||||
private playingAudioCache: AudioBufferSourceNode[] = [];
|
||||
private _volume: number = 1;
|
||||
private _codecCache: CodecClientCache[] = [];
|
||||
private _timeIndex: number = 0;
|
||||
private _latencyBufferLength: number = 3;
|
||||
private _buffer_timeout: NodeJS.Timer;
|
||||
|
||||
allowBuffering: boolean = true;
|
||||
|
||||
//Events
|
||||
onSpeaking: () => void;
|
||||
onSilence: () => void;
|
||||
|
||||
constructor() {
|
||||
audio.player.on_ready(() => this.speakerContext = audio.player.context());
|
||||
|
||||
this.onSpeaking = function () { };
|
||||
this.onSilence = function () { };
|
||||
}
|
||||
|
||||
public initialize() {
|
||||
AudioController._audioInstances.push(this);
|
||||
}
|
||||
|
||||
public close(){
|
||||
AudioController._audioInstances.remove(this);
|
||||
}
|
||||
|
||||
playBuffer(buffer: AudioBuffer) {
|
||||
if(!buffer) {
|
||||
console.warn(tr("[AudioController] Got empty or undefined buffer! Dropping it"));
|
||||
return;
|
||||
}
|
||||
if(!this.speakerContext) {
|
||||
console.warn(tr("[AudioController] Failed to replay audio. Global audio context not initialized yet!"));
|
||||
return;
|
||||
}
|
||||
if (buffer.sampleRate != this.speakerContext.sampleRate)
|
||||
console.warn(tr("[AudioController] Source sample rate isn't equal to playback sample rate! (%o | %o)"), buffer.sampleRate, this.speakerContext.sampleRate);
|
||||
|
||||
this.applyVolume(buffer);
|
||||
this.audioCache.push(buffer);
|
||||
if(this.playerState == PlayerState.STOPPED || this.playerState == PlayerState.STOPPING) {
|
||||
console.log(tr("[Audio] Starting new playback"));
|
||||
this.playerState = PlayerState.PREBUFFERING;
|
||||
//New audio
|
||||
}
|
||||
|
||||
|
||||
switch (this.playerState) {
|
||||
case PlayerState.PREBUFFERING:
|
||||
case PlayerState.BUFFERING:
|
||||
this.reset_buffer_timeout(true); //Reset timeout, we got a new buffer
|
||||
if(this.audioCache.length <= this._latencyBufferLength) {
|
||||
if(this.playerState == PlayerState.BUFFERING) {
|
||||
if(this.allowBuffering) break;
|
||||
} else break;
|
||||
}
|
||||
if(this.playerState == PlayerState.PREBUFFERING) {
|
||||
console.log(tr("[Audio] Prebuffering succeeded (Replaying now)"));
|
||||
this.onSpeaking();
|
||||
} else {
|
||||
if(this.allowBuffering)
|
||||
console.log(tr("[Audio] Buffering succeeded (Replaying now)"));
|
||||
}
|
||||
this.playerState = PlayerState.PLAYING;
|
||||
case PlayerState.PLAYING:
|
||||
this.playQueue();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private playQueue() {
|
||||
let buffer: AudioBuffer;
|
||||
while((buffer = this.audioCache.pop_front())) {
|
||||
if(this.playingAudioCache.length >= this._latencyBufferLength * 1.5 + 3) {
|
||||
console.log(tr("Dropping buffer because playing queue grows to much"));
|
||||
continue; /* drop the data (we're behind) */
|
||||
}
|
||||
if(this._timeIndex < this.speakerContext.currentTime) this._timeIndex = this.speakerContext.currentTime;
|
||||
|
||||
let player = this.speakerContext.createBufferSource();
|
||||
player.buffer = buffer;
|
||||
|
||||
player.onended = () => this.removeNode(player);
|
||||
this.playingAudioCache.push(player);
|
||||
|
||||
player.connect(audio.player.destination());
|
||||
player.start(this._timeIndex);
|
||||
this._timeIndex += buffer.duration;
|
||||
}
|
||||
}
|
||||
|
||||
private removeNode(node: AudioBufferSourceNode) {
|
||||
this.playingAudioCache.remove(node);
|
||||
this.testBufferQueue();
|
||||
}
|
||||
|
||||
stopAudio(now: boolean = false) {
|
||||
this.playerState = PlayerState.STOPPING;
|
||||
if(now) {
|
||||
this.playerState = PlayerState.STOPPED;
|
||||
this.audioCache = [];
|
||||
|
||||
for(let entry of this.playingAudioCache)
|
||||
entry.stop(0);
|
||||
this.playingAudioCache = [];
|
||||
}
|
||||
this.testBufferQueue();
|
||||
this.playQueue(); //Flush queue
|
||||
}
|
||||
|
||||
private testBufferQueue() {
|
||||
if(this.audioCache.length == 0 && this.playingAudioCache.length == 0) {
|
||||
if(this.playerState != PlayerState.STOPPING && this.playerState != PlayerState.STOPPED) {
|
||||
if(this.playerState == PlayerState.BUFFERING) return; //We're already buffering
|
||||
|
||||
this.playerState = PlayerState.BUFFERING;
|
||||
if(!this.allowBuffering)
|
||||
console.warn(tr("[Audio] Detected a buffer underflow!"));
|
||||
this.reset_buffer_timeout(true);
|
||||
} else {
|
||||
this.playerState = PlayerState.STOPPED;
|
||||
this.onSilence();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private reset_buffer_timeout(restart: boolean) {
|
||||
if(this._buffer_timeout)
|
||||
clearTimeout(this._buffer_timeout);
|
||||
if(restart)
|
||||
this._buffer_timeout = setTimeout(() => {
|
||||
if(this.playerState == PlayerState.PREBUFFERING || this.playerState == PlayerState.BUFFERING) {
|
||||
console.warn(tr("[Audio] Buffering exceeded timeout. Flushing and stopping replay"));
|
||||
this.stopAudio();
|
||||
}
|
||||
this._buffer_timeout = undefined;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
get volume() : number { return this._volume; }
|
||||
|
||||
set volume(val: number) {
|
||||
if(this._volume == val) return;
|
||||
this._volume = val;
|
||||
for(let buffer of this.audioCache)
|
||||
this.applyVolume(buffer);
|
||||
}
|
||||
|
||||
private applyVolume(buffer: AudioBuffer) {
|
||||
if(this._volume == 1) return;
|
||||
|
||||
for(let channel = 0; channel < buffer.numberOfChannels; channel++) {
|
||||
let data = buffer.getChannelData(channel);
|
||||
for(let sample = 0; sample < data.length; sample++) {
|
||||
let lane = data[sample];
|
||||
lane *= this._volume;
|
||||
data[sample] = lane;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
codecCache(codec: number) : CodecClientCache {
|
||||
while(this._codecCache.length <= codec)
|
||||
this._codecCache.push(new CodecClientCache());
|
||||
return this._codecCache[codec];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
/// <reference path="../connection/ConnectionBase.ts" />
|
||||
|
||||
namespace audio {
|
||||
export namespace js {
|
||||
export class VoiceClientController implements connection.voice.VoiceClient {
|
||||
callback_playback: () => any;
|
||||
callback_state_changed: (new_state: connection.voice.PlayerState) => any;
|
||||
callback_stopped: () => any;
|
||||
client_id: number;
|
||||
|
||||
speakerContext: AudioContext;
|
||||
private _player_state: connection.voice.PlayerState = connection.voice.PlayerState.STOPPED;
|
||||
private _codecCache: CodecClientCache[] = [];
|
||||
|
||||
private _time_index: number = 0;
|
||||
private _latency_buffer_length: number = 3;
|
||||
private _buffer_timeout: NodeJS.Timer;
|
||||
|
||||
private _buffered_samples: AudioBuffer[] = [];
|
||||
private _playing_nodes: AudioBufferSourceNode[] = [];
|
||||
|
||||
private _volume: number = 1;
|
||||
allowBuffering: boolean = true;
|
||||
|
||||
constructor(client_id: number) {
|
||||
this.client_id = client_id;
|
||||
|
||||
audio.player.on_ready(() => this.speakerContext = audio.player.context());
|
||||
}
|
||||
|
||||
public initialize() { }
|
||||
|
||||
public close(){ }
|
||||
|
||||
playback_buffer(buffer: AudioBuffer) {
|
||||
if(!buffer) {
|
||||
console.warn(tr("[AudioController] Got empty or undefined buffer! Dropping it"));
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this.speakerContext) {
|
||||
console.warn(tr("[AudioController] Failed to replay audio. Global audio context not initialized yet!"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer.sampleRate != this.speakerContext.sampleRate)
|
||||
console.warn(tr("[AudioController] Source sample rate isn't equal to playback sample rate! (%o | %o)"), buffer.sampleRate, this.speakerContext.sampleRate);
|
||||
|
||||
this.apply_volume_to_buffer(buffer);
|
||||
|
||||
this._buffered_samples.push(buffer);
|
||||
if(this._player_state == connection.voice.PlayerState.STOPPED || this._player_state == connection.voice.PlayerState.STOPPING) {
|
||||
console.log(tr("[Audio] Starting new playback"));
|
||||
this.set_state(connection.voice.PlayerState.PREBUFFERING);
|
||||
}
|
||||
|
||||
|
||||
switch (this._player_state) {
|
||||
case connection.voice.PlayerState.PREBUFFERING:
|
||||
case connection.voice.PlayerState.BUFFERING:
|
||||
this.reset_buffer_timeout(true); //Reset timeout, we got a new buffer
|
||||
if(this._buffered_samples.length <= this._latency_buffer_length) {
|
||||
if(this._player_state == connection.voice.PlayerState.BUFFERING) {
|
||||
if(this.allowBuffering)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
if(this._player_state == connection.voice.PlayerState.PREBUFFERING) {
|
||||
console.log(tr("[Audio] Prebuffering succeeded (Replaying now)"));
|
||||
if(this.callback_playback)
|
||||
this.callback_playback();
|
||||
} else if(this.allowBuffering) {
|
||||
console.log(tr("[Audio] Buffering succeeded (Replaying now)"));
|
||||
}
|
||||
this._player_state = connection.voice.PlayerState.PLAYING;
|
||||
case connection.voice.PlayerState.PLAYING:
|
||||
this.replay_queue();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private replay_queue() {
|
||||
let buffer: AudioBuffer;
|
||||
while((buffer = this._buffered_samples.pop_front())) {
|
||||
if(this._playing_nodes.length >= this._latency_buffer_length * 1.5 + 3) {
|
||||
console.log(tr("Dropping buffer because playing queue grows to much"));
|
||||
continue; /* drop the data (we're behind) */
|
||||
}
|
||||
if(this._time_index < this.speakerContext.currentTime)
|
||||
this._time_index = this.speakerContext.currentTime;
|
||||
|
||||
const player = this.speakerContext.createBufferSource();
|
||||
player.buffer = buffer;
|
||||
|
||||
player.onended = () => this.on_buffer_replay_finished(player);
|
||||
this._playing_nodes.push(player);
|
||||
|
||||
player.connect(audio.player.destination());
|
||||
player.start(this._time_index);
|
||||
this._time_index += buffer.duration;
|
||||
}
|
||||
}
|
||||
|
||||
private on_buffer_replay_finished(node: AudioBufferSourceNode) {
|
||||
this._playing_nodes.remove(node);
|
||||
this.test_buffer_queue();
|
||||
}
|
||||
|
||||
stopAudio(now: boolean = false) {
|
||||
this._player_state = connection.voice.PlayerState.STOPPING;
|
||||
if(now) {
|
||||
this._player_state = connection.voice.PlayerState.STOPPED;
|
||||
this._buffered_samples = [];
|
||||
|
||||
for(const entry of this._playing_nodes)
|
||||
entry.stop(0);
|
||||
this._playing_nodes = [];
|
||||
|
||||
if(this.callback_stopped)
|
||||
this.callback_stopped();
|
||||
} else {
|
||||
this.test_buffer_queue(); /* test if we're not already done */
|
||||
this.replay_queue(); /* flush the queue */
|
||||
}
|
||||
}
|
||||
|
||||
private test_buffer_queue() {
|
||||
if(this._buffered_samples.length == 0 && this._playing_nodes.length == 0) {
|
||||
if(this._player_state != connection.voice.PlayerState.STOPPING && this._player_state != connection.voice.PlayerState.STOPPED) {
|
||||
if(this._player_state == connection.voice.PlayerState.BUFFERING)
|
||||
return; //We're already buffering
|
||||
|
||||
this._player_state = connection.voice.PlayerState.BUFFERING;
|
||||
if(!this.allowBuffering)
|
||||
console.warn(tr("[Audio] Detected a buffer underflow!"));
|
||||
this.reset_buffer_timeout(true);
|
||||
} else {
|
||||
this._player_state = connection.voice.PlayerState.STOPPED;
|
||||
if(this.callback_stopped)
|
||||
this.callback_stopped();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private reset_buffer_timeout(restart: boolean) {
|
||||
if(this._buffer_timeout)
|
||||
clearTimeout(this._buffer_timeout);
|
||||
|
||||
if(restart)
|
||||
this._buffer_timeout = setTimeout(() => {
|
||||
if(this._player_state == connection.voice.PlayerState.PREBUFFERING || this._player_state == connection.voice.PlayerState.BUFFERING) {
|
||||
console.warn(tr("[Audio] Buffering exceeded timeout. Flushing and stopping replay"));
|
||||
this.stopAudio();
|
||||
}
|
||||
this._buffer_timeout = undefined;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
private apply_volume_to_buffer(buffer: AudioBuffer) {
|
||||
if(this._volume == 1)
|
||||
return;
|
||||
|
||||
for(let channel = 0; channel < buffer.numberOfChannels; channel++) {
|
||||
let data = buffer.getChannelData(channel);
|
||||
for(let sample = 0; sample < data.length; sample++) {
|
||||
let lane = data[sample];
|
||||
lane *= this._volume;
|
||||
data[sample] = lane;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private set_state(state: connection.voice.PlayerState) {
|
||||
if(this._player_state == state)
|
||||
return;
|
||||
|
||||
this._player_state = state;
|
||||
if(this.callback_state_changed)
|
||||
this.callback_state_changed(this._player_state);
|
||||
}
|
||||
|
||||
get_codec_cache(codec: number) : CodecClientCache {
|
||||
while(this._codecCache.length <= codec)
|
||||
this._codecCache.push(new CodecClientCache());
|
||||
|
||||
return this._codecCache[codec];
|
||||
}
|
||||
|
||||
get_state(): connection.voice.PlayerState {
|
||||
return this._player_state;
|
||||
}
|
||||
|
||||
get_volume(): number {
|
||||
return this._volume;
|
||||
}
|
||||
|
||||
set_volume(volume: number): void {
|
||||
if(this._volume == volume)
|
||||
return;
|
||||
|
||||
this._volume = volume;
|
||||
|
||||
/* apply the volume to all other buffers */
|
||||
for(const buffer of this._buffered_samples)
|
||||
this.apply_volume_to_buffer(buffer);
|
||||
}
|
||||
|
||||
abort_replay() {
|
||||
this.stopAudio(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,5 @@
|
|||
/// <reference path="VoiceHandler.ts" />
|
||||
/// <reference path="../utils/modal.ts" />
|
||||
/// <reference path="../ui/elements/modal.ts" />
|
||||
|
||||
abstract class VoiceActivityDetector {
|
||||
protected handle: VoiceRecorder;
|
||||
|
@ -34,17 +34,25 @@ if(!AudioBuffer.prototype.copyToChannel) { //Webkit does not implement this func
|
|||
}
|
||||
}
|
||||
|
||||
let voice_recoder: VoiceRecorder;
|
||||
class VoiceRecorder {
|
||||
private static readonly CHANNEL = 0;
|
||||
private static readonly CHANNELS = 2;
|
||||
private static readonly BUFFER_SIZE = 1024 * 4;
|
||||
|
||||
handle: VoiceConnection;
|
||||
on_support_state_change: () => any;
|
||||
on_data: (data: AudioBuffer, head: boolean) => void = undefined;
|
||||
on_end: () => any;
|
||||
on_start: () => any;
|
||||
on_yield: () => any; /* called when owner looses ownership */
|
||||
|
||||
owner: connection.voice.AbstractVoiceConnection | undefined;
|
||||
|
||||
private on_ready_callbacks: (() => any)[] = [];
|
||||
|
||||
private _recording: boolean = false;
|
||||
private _recording_supported: boolean = true; /* recording is supported until anything else had been set */
|
||||
private _tag_favicon: JQuery;
|
||||
|
||||
private microphoneStream: MediaStreamAudioSourceNode = undefined;
|
||||
private mediaStream: MediaStream = undefined;
|
||||
|
@ -59,9 +67,9 @@ class VoiceRecorder {
|
|||
private _deviceId: string;
|
||||
private _deviceGroup: string;
|
||||
|
||||
constructor(handle: VoiceConnection) {
|
||||
this.handle = handle;
|
||||
private current_handler: ConnectionHandler;
|
||||
|
||||
constructor() {
|
||||
this._deviceId = settings.global("microphone_device_id", "default");
|
||||
this._deviceGroup = settings.global("microphone_device_group", "default");
|
||||
|
||||
|
@ -72,8 +80,8 @@ class VoiceRecorder {
|
|||
const empty_buffer = this.audioContext.createBuffer(VoiceRecorder.CHANNELS, VoiceRecorder.BUFFER_SIZE, 48000);
|
||||
this.processor.addEventListener('audioprocess', ev => {
|
||||
if(this.microphoneStream && this.vadHandler.shouldRecord(ev.inputBuffer)) {
|
||||
if(this._chunkCount == 0 && this.on_start)
|
||||
this.on_start();
|
||||
if(this._chunkCount == 0)
|
||||
this.on_voice_start();
|
||||
|
||||
if(this.on_data)
|
||||
this.on_data(ev.inputBuffer, this._chunkCount == 0);
|
||||
|
@ -83,8 +91,8 @@ class VoiceRecorder {
|
|||
}
|
||||
this._chunkCount++;
|
||||
} else {
|
||||
if(this._chunkCount != 0 && this.on_end)
|
||||
this.on_end();
|
||||
if(this._chunkCount != 0 )
|
||||
this.on_voice_end();
|
||||
this._chunkCount = 0;
|
||||
|
||||
for(let channel = 0; channel < ev.inputBuffer.numberOfChannels; channel++)
|
||||
|
@ -96,17 +104,39 @@ class VoiceRecorder {
|
|||
if(this.vadHandler)
|
||||
this.vadHandler.initialise();
|
||||
this.on_microphone(this.mediaStream);
|
||||
|
||||
for(const callback of this.on_ready_callbacks)
|
||||
callback();
|
||||
this.on_ready_callbacks = [];
|
||||
});
|
||||
|
||||
this.setVADHandler(new PassThroughVAD());
|
||||
this._tag_favicon = $("head link[rel='icon']");
|
||||
}
|
||||
|
||||
available() : boolean {
|
||||
return !!getUserMediaFunction() && !!getUserMediaFunction();
|
||||
own_recoder(connection: connection.voice.AbstractVoiceConnection | undefined) {
|
||||
if(connection === this.owner)
|
||||
return;
|
||||
if(this.on_yield)
|
||||
this.on_yield();
|
||||
|
||||
this.owner = connection;
|
||||
|
||||
this.on_end = undefined;
|
||||
this.on_start = undefined;
|
||||
this.on_data = undefined;
|
||||
this.on_yield = undefined;
|
||||
this.on_support_state_change = undefined;
|
||||
this.on_ready_callbacks = [];
|
||||
|
||||
this._chunkCount = 0;
|
||||
|
||||
if(this.processor) /* processor stream might be null because of the late audio initialisation */
|
||||
this.processor.connect(this.audioContext.destination);
|
||||
}
|
||||
|
||||
recording() : boolean {
|
||||
return this._recording;
|
||||
input_available() : boolean {
|
||||
return !!getUserMediaFunction();
|
||||
}
|
||||
|
||||
getMediaStream() : MediaStream {
|
||||
|
@ -182,12 +212,22 @@ class VoiceRecorder {
|
|||
return this.vadHandler;
|
||||
}
|
||||
|
||||
update(flag: boolean) {
|
||||
if(this._recording == flag) return;
|
||||
if(flag) this.start(this._deviceId, this._deviceGroup);
|
||||
else this.stop();
|
||||
set_recording(flag_enabled: boolean) {
|
||||
if(this._recording == flag_enabled)
|
||||
return;
|
||||
|
||||
if(flag_enabled)
|
||||
this.start_recording(this._deviceId, this._deviceGroup);
|
||||
else
|
||||
this.stop_recording();
|
||||
}
|
||||
|
||||
clean_recording_supported() { this._recording_supported = true; }
|
||||
|
||||
is_recording_supported() { return this._recording_supported; }
|
||||
|
||||
is_recording() { return this._recording; }
|
||||
|
||||
device_group_id() : string { return this._deviceGroup; }
|
||||
device_id() : string { return this._deviceId; }
|
||||
|
||||
|
@ -199,12 +239,12 @@ class VoiceRecorder {
|
|||
settings.changeGlobal("microphone_device_id", device);
|
||||
settings.changeGlobal("microphone_device_group", group);
|
||||
if(this._recording) {
|
||||
this.stop();
|
||||
this.start(device, group);
|
||||
this.stop_recording();
|
||||
this.start_recording(device, group);
|
||||
}
|
||||
}
|
||||
|
||||
start(device: string, groupId: string){
|
||||
start_recording(device: string, groupId: string){
|
||||
this._deviceId = device;
|
||||
this._deviceGroup = groupId;
|
||||
|
||||
|
@ -220,13 +260,20 @@ class VoiceRecorder {
|
|||
echoCancellationType: 'browser'
|
||||
}
|
||||
}, this.on_microphone.bind(this), error => {
|
||||
this._recording = false;
|
||||
if(this._recording_supported) {
|
||||
this._recording_supported = false;
|
||||
if(this.on_support_state_change)
|
||||
this.on_support_state_change();
|
||||
}
|
||||
|
||||
createErrorModal(tr("Could not resolve microphone!"), tr("Could not resolve microphone!<br>Message: ") + error).open();
|
||||
console.error(tr("Could not get microphone!"));
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
stop(stop_media_stream: boolean = true){
|
||||
stop_recording(stop_media_stream: boolean = true){
|
||||
console.log(tr("Stop recording!"));
|
||||
this._recording = false;
|
||||
|
||||
|
@ -244,13 +291,21 @@ class VoiceRecorder {
|
|||
}
|
||||
}
|
||||
|
||||
on_initialized(callback: () => any) {
|
||||
if(this.processor)
|
||||
callback();
|
||||
else
|
||||
this.on_ready_callbacks.push(callback);
|
||||
}
|
||||
|
||||
private on_microphone(stream: MediaStream) {
|
||||
const old_microphone_stream = this.microphoneStream;
|
||||
if(old_microphone_stream)
|
||||
this.stop(this.mediaStream != stream); //Disconnect old stream
|
||||
this.stop_recording(this.mediaStream != stream); //Disconnect old stream
|
||||
|
||||
this.mediaStream = stream;
|
||||
if(!this.mediaStream) return;
|
||||
if(!this.mediaStream)
|
||||
return;
|
||||
|
||||
if(!this.audioContext) {
|
||||
console.log(tr("[VoiceRecorder] Got microphone stream, but havn't a audio context. Waiting until its initialized"));
|
||||
|
@ -261,6 +316,24 @@ class VoiceRecorder {
|
|||
this.microphoneStream.connect(this.processor);
|
||||
if(this.vadHandler)
|
||||
this.vadHandler.initialiseNewStream(old_microphone_stream, this.microphoneStream);
|
||||
|
||||
if(!this._recording_supported) {
|
||||
this._recording_supported = true;
|
||||
if(this.on_support_state_change)
|
||||
this.on_support_state_change();
|
||||
}
|
||||
}
|
||||
|
||||
private on_voice_start() {
|
||||
this._tag_favicon.attr('href', "img/favicon/speaking.png");
|
||||
if(this.on_start)
|
||||
this.on_start();
|
||||
|
||||
}
|
||||
private on_voice_end() {
|
||||
this._tag_favicon.attr('href', "img/favicon/teacup.png");
|
||||
if(this.on_end)
|
||||
this.on_end();
|
||||
}
|
||||
}
|
||||
class MuteVAD extends VoiceActivityDetector {
|
||||
|
|
|
@ -322,6 +322,9 @@ generators[SyntaxKind.ClassDeclaration] = (settings, stack, node: ts.ClassDeclar
|
|||
};
|
||||
|
||||
generators[SyntaxKind.PropertySignature] = (settings, stack, node: ts.PropertySignature) => {
|
||||
if(!node.type)
|
||||
return node;
|
||||
|
||||
console.log(SyntaxKind[node.type.kind]);
|
||||
let type: ts.TypeNode = node.type;
|
||||
switch (node.type.kind) {
|
||||
|
|
|
@ -5,13 +5,13 @@ html, body {
|
|||
width: 100%;
|
||||
position: fixed;
|
||||
|
||||
min-height: 250px;
|
||||
min-width: 250px;
|
||||
//min-height: 250px;
|
||||
//min-width: 250px;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
justify-content: stretch;
|
||||
position: absolute;
|
||||
|
||||
right: 10px;
|
||||
|
|
Loading…
Reference in New Issue