Added a lot of new things

canary
WolverinDEV 2019-03-07 15:30:53 +01:00
parent bfb7304606
commit d9c0fa37f7
31 changed files with 963 additions and 754 deletions

View File

@ -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",

View File

@ -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

35
setup_windows.md Normal file
View File

@ -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

View File

@ -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}

View File

@ -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%;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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>

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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 {

View File

@ -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;
}

View File

@ -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

View File

@ -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 {

View File

@ -270,6 +270,7 @@ function main() {
clearTimeout(_resize_timeout);
_resize_timeout = setTimeout(() => {
globalClient.channelTree.handle_resized();
globalClient.selectInfo.handle_resize();
}, 1000);
});

View File

@ -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 = {};

View File

@ -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")

View File

@ -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 : () => {})())
);
}

View File

@ -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);

View File

@ -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

View File

@ -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 : () => {})())
);
}

View File

@ -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");
}
}

View File

@ -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] ";

2
vendor/bbcode vendored

@ -1 +1 @@
Subproject commit 9a1c31f27fcac129fa3503c2c1d2096c126d3fd2
Subproject commit 23f9aca6b6dc1ffccd20d6da04953776a1882f2b

View File

@ -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;