Added a lot of new things
parent
bfb7304606
commit
d9c0fa37f7
|
@ -6,7 +6,8 @@
|
|||
"directories": {},
|
||||
"scripts": {
|
||||
"compile-sass": "sass --update .:.",
|
||||
"build-worker": "tsc -p shared/js/workers/tsconfig_worker_codec.json",
|
||||
"build-worker-codec": "tsc -p shared/js/workers/tsconfig_worker_codec.json",
|
||||
"build-worker-pow": "tsc -p shared/js/workers/tsconfig_worker_pow.json",
|
||||
"dtsgen": "node tools/dtsgen/index.js",
|
||||
"trgen": "node tools/trgen/index.js",
|
||||
"ttsc": "ttsc",
|
||||
|
|
|
@ -28,9 +28,14 @@ if [[ $? -ne 0 ]]; then
|
|||
fi
|
||||
|
||||
echo "Generating web workers"
|
||||
npm run build-worker
|
||||
npm run build-worker-codec
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Failed to build web workers"
|
||||
echo "Failed to build web worker codec"
|
||||
exit 1
|
||||
fi
|
||||
npm run build-worker-pow
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Failed to build web worker pow"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# Setup the develop environment on windows
|
||||
## 1.0 Requirements
|
||||
The following tools or applications are required to develop the web client:
|
||||
- [1.1 IDE](#11-ide)
|
||||
- [1.2 XAMPP (apache & php)](#12-xampp-with-a-web-server-and-php)
|
||||
- [1.3 NPM](#13-npm)
|
||||
- [1.4 Git bash](#14-git-bash)
|
||||
|
||||
### 1.1 IDE
|
||||
For developing TeaWeb you require and IDE.
|
||||
Preferable is PHPStorm from Jetbrains because the've already build in compiling on changes.
|
||||
Else you've to run the compiler to compile the TS or SCSS files to js e.g. css files.
|
||||
|
||||
### 1.2 XAMPP with a web server and PHP
|
||||
You require PHP (grater than 5) to setup and manage the project files.
|
||||
PHP is required for the index page as well.
|
||||
The web server is required for browsing your final environment and watch your result.
|
||||
The final environment will be found at `web/environemnt/development/`. More information about
|
||||
the file structure could be found [here (TODO: link me!)]().
|
||||
|
||||
### 1.3 NPM
|
||||
NPM min 6.X is required to develop this project.
|
||||
With NPM you could easily download all required dependencies by just typing `npm install`.
|
||||
IMPORTANT: NPM must be available within the PATH environment variable!
|
||||
|
||||
### 1.4 Git bash
|
||||
For using the `.sh` build scripts you require Git Bash.
|
||||
A minimum of 4.2 is recommend, but in general every version should work.
|
||||
|
||||
## 2.0 Development environment setup
|
||||
### 2.1 Native code (codecs) (Not required)
|
||||
If you dont want to develop the codec part or something related to the native
|
||||
webassembly part of TeaWeb you could skip this step and follow the steps in [2.1-b](#21-b-skip-native-code-setup)
|
||||
|
||||
### 2.1-b Skip native code setup
|
|
@ -37,6 +37,7 @@
|
|||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
cursor: pointer;
|
||||
margin-left: 0;
|
||||
|
||||
.server_type {
|
||||
|
@ -193,7 +194,15 @@
|
|||
}
|
||||
|
||||
/* all icons related to basic_icons */
|
||||
.clicon {width:16px;height:16px;background:url('../../img/ts/basic_icons.png') no-repeat;background-size: 16px 608px;}
|
||||
.clicon {
|
||||
width:16px;
|
||||
height:16px;
|
||||
background:url('../../img/ts/basic_icons.png') no-repeat;
|
||||
background-size: 16px 608px;
|
||||
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.host {background-position: 0 -448px}
|
||||
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
.select_info_table { }
|
||||
.select_info_table tr { }
|
||||
.select_info_table tr td { }
|
||||
.select_info_table tr td {
|
||||
&:nth-child(1) {
|
||||
font-weight: bold;
|
||||
padding-right: 5px;
|
||||
min-width: max(35%, 20px);
|
||||
}
|
||||
|
||||
.select_info_table tr td:nth-child(1) {
|
||||
font-weight: bold;
|
||||
padding-right: 5px;
|
||||
min-width: 20%;
|
||||
&:nth-child(2) {
|
||||
min-width: max(75%, 40px);
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.select_server {
|
||||
|
@ -17,21 +22,18 @@
|
|||
|
||||
.button-update {
|
||||
width: 100%;
|
||||
height: 23px;
|
||||
|
||||
&:disabled {
|
||||
color: red;
|
||||
pointer-events: none;
|
||||
}
|
||||
&:not(:disabled) {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
max-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
|
||||
.hostbanner {
|
||||
overflow: hidden;
|
||||
|
@ -39,18 +41,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
<div id="select_info" class="select_info" style="width: 100%; max-width: 100%">
|
||||
<div class="container-banner"></div>
|
||||
<div class="container-info"></div>
|
||||
</div>
|
||||
*/
|
||||
.select_info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
width: 100%;
|
||||
|
||||
> .close {
|
||||
z-index: 500;
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
> div {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -228,8 +228,11 @@ footer .container {
|
|||
}
|
||||
|
||||
$separator_thickness: 4px;
|
||||
$small_device: 500px;
|
||||
|
||||
.app {
|
||||
.container-app-main {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
@ -303,6 +306,38 @@ $separator_thickness: 4px;
|
|||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $small_device) {
|
||||
.container-app-main {
|
||||
.container-info {
|
||||
display: none;
|
||||
position: absolute;
|
||||
|
||||
width: 100%!important; /* override the seperator property */
|
||||
height: 100%;
|
||||
|
||||
z-index: 1000;
|
||||
|
||||
&.shown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.select_info {
|
||||
> .close {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-channel-chat + .container-seperator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.container-channel-chat {
|
||||
width: 100%!important; /* override the seperator property */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-seperator {
|
||||
background: lightgray;
|
||||
flex-grow: 0;
|
||||
|
@ -328,6 +363,7 @@ $separator_thickness: 4px;
|
|||
}
|
||||
|
||||
.icon-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
|
@ -354,8 +390,6 @@ $separator_thickness: 4px;
|
|||
}
|
||||
|
||||
html, body {
|
||||
min-height: 500px;
|
||||
min-width: 500px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -8,6 +8,9 @@
|
|||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
background: url('../../../img/client_icon_sprite.svg'), url('../../img/client_icon_sprite.svg') no-repeat;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="TeaSpeak Web Client, connect to any TeaSpeak server without installing anything." />
|
||||
<link rel="icon" href="img/favicon/teacup.png">
|
||||
|
||||
<?php
|
||||
if(!$WEB_CLIENT) {
|
||||
|
@ -190,7 +191,7 @@
|
|||
<footer style="<?php echo $footer_style; ?>">
|
||||
<div class="container" style="display: flex; flex-direction: row; align-content: space-between;">
|
||||
<div style="align-self: center; position: fixed; left: 5px;">Open source on <a href="https://github.com/TeaSpeak/TeaSpeak-Web" style="display: inline-block; position: relative">github.com</a></div>
|
||||
<div style="align-self: center;">TeaSpeak Web client (<?php echo $version; ?>) by WolverinDEV</div>
|
||||
<div style="align-self: center;">TeaSpeak Web (<?php echo $version; ?>) by WolverinDEV</div>
|
||||
<div style="align-self: center; position: fixed; right: 5px;"><?php echo $footer_forum; ?></div>
|
||||
</div>
|
||||
</footer>
|
||||
|
|
|
@ -133,8 +133,11 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="container-seperator vertical" seperator-id="seperator-main-info"></div>
|
||||
<div class="main_container container-info">
|
||||
<div id="select_info" class="select_info" style="width: 100%; max-width: 100%">
|
||||
<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>
|
||||
|
@ -1029,7 +1032,8 @@
|
|||
|
||||
<div class="group_box">
|
||||
<div class="header">{{tr "Microphone" /}}</div>
|
||||
<div class="content settings-microphone">
|
||||
<div class="content settings-microphone {{if !voice_available}}disabled{{/if}}">
|
||||
{{if voice_available}}
|
||||
<div class="form-row settings-device settings-device-microphone">
|
||||
<div class="form-group settings-device-select">
|
||||
<label for="select-settings-microphone-device" class="bmd-label-static">{{tr "Device:" /}}</label>
|
||||
|
@ -1088,6 +1092,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div>{{tr "Voice had been disabled" /}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="group_box">
|
||||
|
@ -2058,7 +2065,7 @@
|
|||
<tr>
|
||||
<td>{{tr "Name:" /}}</td>
|
||||
<td style="display: flex; flex-direction: row">
|
||||
<div style="margin-right: 3px" class="country flag-{{*:(data.property_client_country || 'xx').toLowerCase()}}"></div>
|
||||
<div style="margin-right: 3px" class="country flag-{{*:(data.property_client_country || 'xx').toLowerCase()}}" title="{{*:i18n.country_name(data.property_client_country || 'XX')}}"></div>
|
||||
<node key="client_name"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -2315,7 +2322,7 @@
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<button class="button-update btn_update">{{tr "Update info"/}}</button>
|
||||
<button class="button-update btn btn-success">{{tr "Update info"/}}</button>
|
||||
</div>
|
||||
</script>
|
||||
<script class="jsrender-template" id="tmpl_selected_channel" type="text/html">
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
Before Width: | Height: | Size: 75 KiB |
|
@ -50,7 +50,7 @@ enum ViewReasonId {
|
|||
class TSClient {
|
||||
channelTree: ChannelTree;
|
||||
serverConnection: connection.ServerConnection;
|
||||
voiceConnection: VoiceConnection;
|
||||
voiceConnection: VoiceConnection | undefined;
|
||||
fileManager: FileManager;
|
||||
selectInfo: InfoBar;
|
||||
permissions: PermissionManager;
|
||||
|
@ -69,10 +69,12 @@ class TSClient {
|
|||
this.fileManager = new FileManager(this);
|
||||
this.permissions = new PermissionManager(this);
|
||||
this.groups = new GroupManager(this);
|
||||
this.voiceConnection = new VoiceConnection(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() {
|
||||
|
@ -140,7 +142,8 @@ class TSClient {
|
|||
if(this.groups.serverGroups.length == 0)
|
||||
this.groups.requestGroups();
|
||||
this.controlBar.updateProperties();
|
||||
if(!this.voiceConnection.current_encoding_supported())
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -270,7 +273,8 @@ class TSClient {
|
|||
}
|
||||
|
||||
this.channelTree.reset();
|
||||
this.voiceConnection.dropSession();
|
||||
if(this.voiceConnection)
|
||||
this.voiceConnection.dropSession();
|
||||
if(this.serverConnection) this.serverConnection.disconnect();
|
||||
this.controlBar.update_connection_state();
|
||||
this.selectInfo.setCurrentSelected(null);
|
||||
|
|
|
@ -81,8 +81,12 @@ namespace connection {
|
|||
|
||||
handleCommandServerInit(json){
|
||||
//We could setup the voice channel
|
||||
console.log(tr("Setting up voice"));
|
||||
this.connection.client.voiceConnection.createSession();
|
||||
if( this.connection.client.voiceConnection) {
|
||||
console.log(tr("Setting up voice"));
|
||||
this.connection.client.voiceConnection.createSession();
|
||||
} else {
|
||||
console.log(tr("Skipping voice setup (No voice bridge available)"));
|
||||
}
|
||||
|
||||
|
||||
json = json[0]; //Only one bulk
|
||||
|
|
|
@ -28,20 +28,37 @@ namespace connection {
|
|||
abstract disconnect(reason?: string) : Promise<void>;
|
||||
|
||||
abstract support_voice() : boolean;
|
||||
abstract voice_connection() : AbstractVoiceConnection | undefined;
|
||||
abstract voice_connection() : voice.AbstractVoiceConnection | undefined;
|
||||
|
||||
abstract command_handler_boss() : AbstractCommandHandlerBoss;
|
||||
abstract send_command(command: string, data?: any | any[], options?: CommandOptions) : Promise<CommandResult>;
|
||||
}
|
||||
|
||||
export abstract class AbstractVoiceConnection {
|
||||
readonly connection: AbstractServerConnection;
|
||||
export namespace voice {
|
||||
export interface VoiceClient {
|
||||
client_id: number;
|
||||
|
||||
protected constructor(connection: AbstractServerConnection) {
|
||||
this.connection = connection;
|
||||
callback_playback: () => any;
|
||||
callback_timeout: () => any;
|
||||
callback_stopped: () => any;
|
||||
|
||||
get_volume() : number;
|
||||
set_volume(volume: number) : Promise<void>;
|
||||
}
|
||||
|
||||
abstract connected() : boolean;
|
||||
export abstract class AbstractVoiceConnection {
|
||||
readonly connection: AbstractServerConnection;
|
||||
|
||||
protected constructor(connection: AbstractServerConnection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
abstract connected() : boolean;
|
||||
|
||||
abstract register_client(client_id: number) : VoiceClient;
|
||||
abstract availible_clients() : VoiceClient[];
|
||||
abstract unregister_client(client: VoiceClient) : Promise<void>;
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerCommand {
|
||||
|
|
|
@ -202,7 +202,12 @@ namespace connection {
|
|||
arguments: json["data"]
|
||||
});
|
||||
group.end();
|
||||
} else if(json["type"] === "WebRTC") this.client.voiceConnection.handleControlPacket(json);
|
||||
} else if(json["type"] === "WebRTC") {
|
||||
if(this.client.voiceConnection)
|
||||
this.client.voiceConnection.handleControlPacket(json);
|
||||
else
|
||||
console.log(tr("Dropping WebRTC command packet, because we havent a bridge."))
|
||||
}
|
||||
else {
|
||||
console.log(tr("Unknown command type %o"), json["type"]);
|
||||
}
|
||||
|
@ -299,7 +304,7 @@ namespace connection {
|
|||
return false;
|
||||
}
|
||||
|
||||
voice_connection(): connection.AbstractVoiceConnection | undefined {
|
||||
voice_connection(): connection.voice.AbstractVoiceConnection | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -52,8 +52,9 @@ interface ContextMenuEntry {
|
|||
name: (() => string) | string;
|
||||
icon?: (() => string) | string | JQuery;
|
||||
disabled?: boolean;
|
||||
invalidPermission?: boolean;
|
||||
visible?: boolean;
|
||||
|
||||
invalidPermission?: boolean;
|
||||
sub_menu?: ContextMenuEntry[];
|
||||
}
|
||||
|
||||
|
@ -96,8 +97,11 @@ function generate_tag(entry: ContextMenuEntry) : JQuery {
|
|||
if(entry.disabled || entry.invalidPermission) tag.addClass("disabled");
|
||||
else {
|
||||
let menu = $.spawn("div").addClass("sub-menu").addClass("context-menu");
|
||||
for(let e of entry.sub_menu)
|
||||
for(const e of entry.sub_menu) {
|
||||
if(typeof(entry.visible) === 'boolean' && !entry.visible)
|
||||
continue;
|
||||
menu.append(generate_tag(e));
|
||||
}
|
||||
menu.appendTo(tag);
|
||||
}
|
||||
return tag;
|
||||
|
@ -111,7 +115,10 @@ function spawn_context_menu(x, y, ...entries: ContextMenuEntry[]) {
|
|||
|
||||
contextMenuCloseFn = undefined;
|
||||
|
||||
for(let entry of entries){
|
||||
for(const entry of entries){
|
||||
if(typeof(entry.visible) === 'boolean' && !entry.visible)
|
||||
continue;
|
||||
|
||||
if(entry.type == MenuEntryType.CLOSE) {
|
||||
contextMenuCloseFn = entry.callback;
|
||||
} else
|
||||
|
|
|
@ -53,7 +53,7 @@ namespace loader {
|
|||
DONE
|
||||
}
|
||||
|
||||
export let allow_cached_files: boolean = false;
|
||||
export let cache_tag: string | undefined;
|
||||
let current_stage: Stage = Stage.INITIALIZING;
|
||||
const tasks: {[key:number]:Task[]} = {};
|
||||
|
||||
|
@ -206,7 +206,7 @@ namespace loader {
|
|||
|
||||
document.getElementById("scripts").appendChild(tag);
|
||||
|
||||
tag.src = path + (allow_cached_files ? "" : "?_ts=" + Date.now());
|
||||
tag.src = path + (cache_tag || "");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -315,7 +315,7 @@ namespace loader {
|
|||
};
|
||||
|
||||
document.getElementById("style").appendChild(tag);
|
||||
tag.href = path + (allow_cached_files ? "" : "?_ts=" + Date.now());
|
||||
tag.href = path + (cache_tag || "");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -708,12 +708,11 @@ async function check_updates() {
|
|||
|
||||
if(!app_version) {
|
||||
/* TODO add warning */
|
||||
loader.allow_cached_files = false;
|
||||
loader.cache_tag = "?_ts=" + Date.now();
|
||||
return;
|
||||
}
|
||||
const cached_version = localStorage.getItem("cached_version");
|
||||
if(!cached_version || cached_version != app_version) {
|
||||
loader.allow_cached_files = false;
|
||||
loader.register_task(loader.Stage.LOADED, {
|
||||
priority: 0,
|
||||
name: "cached version updater",
|
||||
|
@ -721,11 +720,8 @@ async function check_updates() {
|
|||
localStorage.setItem("cached_version", app_version);
|
||||
}
|
||||
});
|
||||
/* loading screen */
|
||||
return;
|
||||
}
|
||||
|
||||
loader.allow_cached_files = true;
|
||||
loader.cache_tag = "?_version=" + app_version;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
|
|
|
@ -270,6 +270,7 @@ function main() {
|
|||
clearTimeout(_resize_timeout);
|
||||
_resize_timeout = setTimeout(() => {
|
||||
globalClient.channelTree.handle_resized();
|
||||
globalClient.selectInfo.handle_resize();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ class StaticSettings {
|
|||
class Settings extends StaticSettings {
|
||||
static readonly KEY_DISABLE_CONTEXT_MENU = "disableContextMenu";
|
||||
static readonly KEY_DISABLE_UNLOAD_DIALOG = "disableUnloadDialog";
|
||||
static readonly KEY_DISABLE_VOICE = "disableVoice";
|
||||
|
||||
private static readonly UPDATE_DIRECT: boolean = true;
|
||||
private cacheGlobal = {};
|
||||
|
|
|
@ -442,7 +442,21 @@ class ChannelEntry {
|
|||
flagDelete = this.channelTree.client.permissions.neededPermission(PermissionType.B_CHANNEL_DELETE_TEMPORARY).granted(1);
|
||||
}
|
||||
|
||||
let trigger_close = true;
|
||||
spawn_context_menu(x, y, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
name: tr("Show channel info"),
|
||||
callback: () => {
|
||||
trigger_close = false;
|
||||
this.channelTree.client.selectInfo.open_popover()
|
||||
},
|
||||
icon: "client-about",
|
||||
visible: this.channelTree.client.selectInfo.is_popover()
|
||||
}, {
|
||||
type: MenuEntryType.HR,
|
||||
visible: this.channelTree.client.selectInfo.is_popover(),
|
||||
name: ''
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-channel_switch",
|
||||
name: tr("<b>Switch to channel</b>"),
|
||||
|
@ -525,7 +539,7 @@ class ChannelEntry {
|
|||
invalidPermission: !channelCreate,
|
||||
callback: () => this.channelTree.spawnCreateChannel()
|
||||
},
|
||||
MenuEntry.CLOSE(on_close)
|
||||
MenuEntry.CLOSE(() => (trigger_close ? on_close : () => {})())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -639,7 +653,10 @@ 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.channelTree.client.voiceConnection.codecSupported(this.properties.channel_codec) ? $.fn.hide : $.fn.show).apply(this.channelTag().find(".icons .icon_no_sound"));
|
||||
this.channelTag().find(".icons .icon_no_sound").toggle(!(
|
||||
this.channelTree.client.voiceConnection &&
|
||||
this.channelTree.client.voiceConnection.codecSupported(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"));
|
||||
} else if(key == "channel_flag_password")
|
||||
|
|
|
@ -266,28 +266,40 @@ class ClientEntry {
|
|||
}
|
||||
|
||||
showContextMenu(x: number, y: number, on_close: () => void = undefined) {
|
||||
const _this = this;
|
||||
|
||||
let trigger_close = true;
|
||||
spawn_context_menu(x, y,
|
||||
{
|
||||
type: MenuEntryType.ENTRY,
|
||||
name: tr("Show client info"),
|
||||
callback: () => {
|
||||
trigger_close = false;
|
||||
this.channelTree.client.selectInfo.open_popover()
|
||||
},
|
||||
icon: "client-about",
|
||||
visible: this.channelTree.client.selectInfo.is_popover()
|
||||
}, {
|
||||
type: MenuEntryType.HR,
|
||||
visible: this.channelTree.client.selectInfo.is_popover(),
|
||||
name: ''
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-change_nickname",
|
||||
name: tr("<b>Open text chat</b>"),
|
||||
callback: function () {
|
||||
chat.activeChat = _this.chat(true);
|
||||
callback: () => {
|
||||
chat.activeChat = this.chat(true);
|
||||
chat.focus();
|
||||
}
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-poke",
|
||||
name: tr("Poke client"),
|
||||
callback: function () {
|
||||
callback: () => {
|
||||
createInputModal(tr("Poke client"), tr("Poke message:<br>"), text => true, result => {
|
||||
if(typeof(result) === "string") {
|
||||
//TODO tr
|
||||
console.log("Poking client " + _this.clientNickName() + " with message " + result);
|
||||
_this.channelTree.client.serverConnection.send_command("clientpoke", {
|
||||
clid: _this.clientId(),
|
||||
console.log("Poking client " + this.clientNickName() + " with message " + result);
|
||||
this.channelTree.client.serverConnection.send_command("clientpoke", {
|
||||
clid: this.clientId(),
|
||||
msg: result
|
||||
});
|
||||
|
||||
|
@ -298,13 +310,13 @@ class ClientEntry {
|
|||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-edit",
|
||||
name: tr("Change description"),
|
||||
callback: function () {
|
||||
callback: () => {
|
||||
createInputModal(tr("Change client description"), tr("New description:<br>"), text => true, result => {
|
||||
if(typeof(result) === "string") {
|
||||
//TODO tr
|
||||
console.log("Changing " + _this.clientNickName() + "'s description to " + result);
|
||||
_this.channelTree.client.serverConnection.send_command("clientedit", {
|
||||
clid: _this.clientId(),
|
||||
console.log("Changing " + this.clientNickName() + "'s description to " + result);
|
||||
this.channelTree.client.serverConnection.send_command("clientedit", {
|
||||
clid: this.clientId(),
|
||||
client_description: result
|
||||
});
|
||||
|
||||
|
@ -332,9 +344,9 @@ class ClientEntry {
|
|||
createInputModal(tr("Kick client from channel"), tr("Kick reason:<br>"), text => true, result => {
|
||||
if(result) {
|
||||
//TODO tr
|
||||
console.log("Kicking client " + _this.clientNickName() + " from channel with reason " + result);
|
||||
_this.channelTree.client.serverConnection.send_command("clientkick", {
|
||||
clid: _this.clientId(),
|
||||
console.log("Kicking client " + this.clientNickName() + " from channel with reason " + result);
|
||||
this.channelTree.client.serverConnection.send_command("clientkick", {
|
||||
clid: this.clientId(),
|
||||
reasonid: ViewReasonId.VREASON_CHANNEL_KICK,
|
||||
reasonmsg: result
|
||||
});
|
||||
|
@ -350,9 +362,9 @@ class ClientEntry {
|
|||
createInputModal(tr("Kick client from server"), tr("Kick reason:<br>"), text => true, result => {
|
||||
if(result) {
|
||||
//TODO tr
|
||||
console.log("Kicking client " + _this.clientNickName() + " from server with reason " + result);
|
||||
_this.channelTree.client.serverConnection.send_command("clientkick", {
|
||||
clid: _this.clientId(),
|
||||
console.log("Kicking client " + this.clientNickName() + " from server with reason " + result);
|
||||
this.channelTree.client.serverConnection.send_command("clientkick", {
|
||||
clid: this.clientId(),
|
||||
reasonid: ViewReasonId.VREASON_SERVER_KICK,
|
||||
reasonmsg: result
|
||||
});
|
||||
|
@ -411,7 +423,7 @@ class ClientEntry {
|
|||
});
|
||||
}
|
||||
},
|
||||
MenuEntry.CLOSE(on_close)
|
||||
MenuEntry.CLOSE(() => (trigger_close ? on_close : () => {})())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -908,8 +920,22 @@ class MusicClientEntry extends ClientEntry {
|
|||
}
|
||||
|
||||
showContextMenu(x: number, y: number, on_close: () => void = undefined): void {
|
||||
let trigger_close = true;
|
||||
spawn_context_menu(x, y,
|
||||
{
|
||||
type: MenuEntryType.ENTRY,
|
||||
name: tr("Show bot info"),
|
||||
callback: () => {
|
||||
trigger_close = false;
|
||||
this.channelTree.client.selectInfo.open_popover()
|
||||
},
|
||||
icon: "client-about",
|
||||
visible: this.channelTree.client.selectInfo.is_popover()
|
||||
}, {
|
||||
type: MenuEntryType.HR,
|
||||
visible: this.channelTree.client.selectInfo.is_popover(),
|
||||
name: ''
|
||||
}, {
|
||||
name: tr("<b>Change bot name</b>"),
|
||||
icon: "client-change_nickname",
|
||||
disabled: false,
|
||||
|
@ -1073,7 +1099,7 @@ class MusicClientEntry extends ClientEntry {
|
|||
},
|
||||
type: MenuEntryType.ENTRY
|
||||
},
|
||||
MenuEntry.CLOSE(on_close)
|
||||
MenuEntry.CLOSE(() => (trigger_close ? on_close : () => {})())
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -196,7 +196,8 @@ class ControlBar {
|
|||
|
||||
private updateMicrophoneRecordState() {
|
||||
let enabled = !this._muteInput && !this._muteOutput && !this._away;
|
||||
this.handle.voiceConnection.voiceRecorder.update(enabled);
|
||||
if(this.handle.voiceConnection)
|
||||
this.handle.voiceConnection.voiceRecorder.update(enabled);
|
||||
}
|
||||
|
||||
updateProperties() {
|
||||
|
@ -212,12 +213,13 @@ class ControlBar {
|
|||
}
|
||||
|
||||
updateVoice(targetChannel?: ChannelEntry) {
|
||||
if(!targetChannel) targetChannel = this.handle.getClient().currentChannel();
|
||||
if(!targetChannel)
|
||||
targetChannel = this.handle.getClient().currentChannel();
|
||||
let client = this.handle.getClient();
|
||||
|
||||
this.codec_supported = targetChannel ? this.handle.voiceConnection.codecSupported(targetChannel.properties.channel_codec) : true;
|
||||
this.support_record = this.handle.voiceConnection.voice_send_support();
|
||||
this.support_playback = this.handle.voiceConnection.voice_playback_support();
|
||||
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);
|
||||
|
|
|
@ -53,7 +53,8 @@ class InfoBar<AvailableTypes = ServerEntry | ChannelEntry | ClientEntry | undefi
|
|||
readonly handle: TSClient;
|
||||
|
||||
private current_selected?: AvailableTypes;
|
||||
private _htmlTag: JQuery<HTMLElement>;
|
||||
private _tag: JQuery<HTMLElement>;
|
||||
private _tag_content: JQuery<HTMLElement>;
|
||||
private _tag_info: JQuery<HTMLElement>;
|
||||
private _tag_banner: JQuery<HTMLElement>;
|
||||
|
||||
|
@ -63,9 +64,10 @@ class InfoBar<AvailableTypes = ServerEntry | ChannelEntry | ClientEntry | undefi
|
|||
|
||||
constructor(client: TSClient, htmlTag: JQuery<HTMLElement>) {
|
||||
this.handle = client;
|
||||
this._htmlTag = htmlTag;
|
||||
this._tag_info = htmlTag.find(".container-select-info");
|
||||
this._tag_banner = htmlTag.find(".container-banner");
|
||||
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.managers.push(new MusicInfoManager());
|
||||
this.managers.push(new ClientInfoManager());
|
||||
|
@ -73,10 +75,24 @@ class InfoBar<AvailableTypes = ServerEntry | ChannelEntry | ClientEntry | undefi
|
|||
this.managers.push(new ServerInfoManager());
|
||||
|
||||
this.banner_manager = new Hostbanner(client, this._tag_banner);
|
||||
|
||||
this._tag.find("button.close").on('click', () => {
|
||||
this._tag.toggleClass('shown', false);
|
||||
});
|
||||
}
|
||||
|
||||
handle_resize() {
|
||||
/* test if the popover isn't a popover anymore */
|
||||
if(this._tag.hasClass('shown')) {
|
||||
this._tag.removeClass('shown');
|
||||
if(this.is_popover())
|
||||
this._tag.addClass('shown');
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentSelected(entry: AvailableTypes) {
|
||||
if(this.current_selected == entry) return;
|
||||
|
||||
if(this._current_manager) {
|
||||
(this._current_manager as InfoManager<AvailableTypes>).finalizeFrame(this.current_selected, this._tag_info);
|
||||
this._current_manager = null;
|
||||
|
@ -112,7 +128,15 @@ class InfoBar<AvailableTypes = ServerEntry | ChannelEntry | ClientEntry | undefi
|
|||
|
||||
current_manager() { return this._current_manager; }
|
||||
|
||||
html_tag() { return this._htmlTag; }
|
||||
html_tag() { return this._tag_content; }
|
||||
|
||||
is_popover() : boolean {
|
||||
return !this._tag.is(':visible') || this._tag.hasClass('shown');
|
||||
}
|
||||
|
||||
open_popover() {
|
||||
this._tag.toggleClass('shown', true);
|
||||
}
|
||||
}
|
||||
|
||||
class Hostbanner {
|
||||
|
@ -299,8 +323,12 @@ class ServerInfoManager extends InfoManager<ServerEntry> {
|
|||
|
||||
|
||||
{
|
||||
let requestUpdate = rendered.find(".btn_update");
|
||||
requestUpdate.prop("disabled", !server.shouldUpdateProperties());
|
||||
const disabled = !server.shouldUpdateProperties();
|
||||
let requestUpdate = rendered.find(".button-update");
|
||||
requestUpdate
|
||||
.prop("disabled", disabled)
|
||||
.toggleClass('btn-success', !disabled)
|
||||
.toggleClass('btn-danger', disabled);
|
||||
|
||||
requestUpdate.click(() => {
|
||||
server.updateProperties();
|
||||
|
@ -308,7 +336,10 @@ class ServerInfoManager extends InfoManager<ServerEntry> {
|
|||
});
|
||||
|
||||
this.registerTimer(setTimeout(function () {
|
||||
requestUpdate.prop("disabled", false);
|
||||
requestUpdate
|
||||
.prop("disabled", false)
|
||||
.toggleClass('btn-success', true)
|
||||
.toggleClass('btn-danger', false);
|
||||
}, server.nextInfoRequest - Date.now()));
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -135,7 +135,21 @@ class ServerEntry {
|
|||
}
|
||||
|
||||
spawnContextMenu(x: number, y: number, on_close: () => void = () => {}) {
|
||||
let trigger_close = true;
|
||||
spawn_context_menu(x, y, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
name: tr("Show server info"),
|
||||
callback: () => {
|
||||
trigger_close = false;
|
||||
this.channelTree.client.selectInfo.open_popover()
|
||||
},
|
||||
icon: "client-about",
|
||||
visible: this.channelTree.client.selectInfo.is_popover()
|
||||
}, {
|
||||
type: MenuEntryType.HR,
|
||||
visible: this.channelTree.client.selectInfo.is_popover(),
|
||||
name: ''
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-virtualserver_edit",
|
||||
name: tr("Edit"),
|
||||
|
@ -162,7 +176,7 @@ class ServerEntry {
|
|||
createInfoModal(tr("Buddy invite URL"), tr("Your buddy invite URL:<br>") + url + tr("<bt>This has been copied to your clipboard.")).open();
|
||||
}
|
||||
},
|
||||
MenuEntry.CLOSE(on_close)
|
||||
MenuEntry.CLOSE(() => (trigger_close ? on_close : () => {})())
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -144,6 +144,7 @@ class VoiceConnection {
|
|||
private vpacketId: number = 0;
|
||||
private chunkVPacketId: number = 0;
|
||||
private send_task: NodeJS.Timer;
|
||||
private _tag_favicon: JQuery;
|
||||
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
|
@ -171,6 +172,7 @@ class VoiceConnection {
|
|||
});
|
||||
|
||||
this.send_task = setInterval(this.sendNextVoicePacket.bind(this), 20);
|
||||
this._tag_favicon = $("head link[rel='icon']");
|
||||
}
|
||||
|
||||
native_encoding_supported() : boolean {
|
||||
|
@ -463,6 +465,8 @@ class VoiceConnection {
|
|||
|
||||
if(this.dataChannel)
|
||||
this.sendVoicePacket(new Uint8Array(0), this.current_channel_codec()); //TODO Use channel codec!
|
||||
|
||||
this._tag_favicon.attr('href', "img/favicon/teacup.png");
|
||||
}
|
||||
|
||||
private handleVoiceStarted() {
|
||||
|
@ -470,5 +474,6 @@ class VoiceConnection {
|
|||
|
||||
if(this.client && this.client.getClient())
|
||||
this.client.getClient().speaking = true;
|
||||
this._tag_favicon.attr('href', "img/favicon/speaking.png");
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
declare namespace WebAssembly {
|
||||
export function instantiateStreaming(stream: Promise<Response>, imports?: any) : Promise<ResultObject>;
|
||||
}
|
||||
declare function postMessage(message: any): void;
|
||||
|
||||
const prefix = "[POWWorker] ";
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 9a1c31f27fcac129fa3503c2c1d2096c126d3fd2
|
||||
Subproject commit 23f9aca6b6dc1ffccd20d6da04953776a1882f2b
|
|
@ -1,17 +1,27 @@
|
|||
html, body {
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
|
||||
min-height: 250px;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
|
||||
right: 10px;
|
||||
left: 10px;
|
||||
bottom: 40px;
|
||||
top: 10px;
|
||||
|
||||
.app {
|
||||
width: 100%;
|
||||
height: calc(100% - 50px);
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
|
||||
display: flex; flex-direction: column; resize: both;
|
||||
|
|
Loading…
Reference in New Issue