Implementing a lot of new features again

canary
WolverinDEV 2018-11-04 13:54:18 +01:00
parent 89a29a8717
commit 48822c604a
14 changed files with 559 additions and 110 deletions

View File

@ -1,4 +1,12 @@
# Changelog:
* **04.11.18**
- Added basic music bot management (Create | Delete | Nickname/Description-change)
- Merged music bot pause and play. Added stop button
- Fixed voice "lamp" being on on channel switch
- Improved hostbanner reload (Not flicker anymore)
- Added sounds on servergroup assignment and on revoke as well for channel group changed
- Added client multiselect and multi actions
* **03.11.18**
- Reworked on the basic overlay sizes
- Added hostbanner to the UI

View File

@ -46,4 +46,12 @@ user.left.disconnect;User disconnected from your channel
user.left.banned;User in your channel was banned from the server
#Error
error.insufficient_permissions;insufficient permissions
error.insufficient_permissions;insufficient permissions
#Groups
group.server.assigned;Server group assigned
group.server.revoked;Server group revoked
group.channel.changed;Channel group changed
group.server.assigned.self;Server group assigned
group.server.revoked.self;Server group revoked
group.channel.changed.self;Channel group changed
1 #Sound test
46 group.server.assigned.self;Server group assigned
47 group.server.revoked.self;Server group revoked
48 group.channel.changed.self;Channel group changed
49
50
51
52
53
54
55
56
57

View File

@ -170,37 +170,55 @@ $ease: cubic-bezier(.45, 0, .55, 1);
justify-content: space-between;
vertical-align: center;
.button-container{
display: inline-block;
> div {
display: inline-block;
}
}
.button {
width: 10px;
height: 12px;
margin-left: 2px;
fill: none;
stroke: #4c4c4c;;
stroke-width: 0.5;
stroke-miterlimit: 10;
cursor: pointer;
color: white;
mix-blend-mode: difference;
//box-shadow: 20px 20px 20px 20px rgb(186, 0, 12);
svg {
fill: none;
stroke: #4c4c4c;;
stroke-width: 0.5;
stroke-miterlimit: 10;
cursor: pointer;
color: white;
mix-blend-mode: difference;
//box-shadow: 20px 20px 20px 20px rgb(186, 0, 12);
}
}
.button.active {
animation: bounce 500ms alternate;
transform: scale(1.3);
transition: transform 150ms;
svg {
animation: bounce 500ms alternate;
transform: scale(1.3);
transition: transform 150ms;
}
}
.button:hover {
animation: bounce 500ms alternate;
transform: scale(1.1);
transition: transform 150ms;
svg {
animation: bounce 500ms alternate;
transform: scale(1.1);
transition: transform 150ms;
}
}
.button.active:hover {
animation: bounce 500ms alternate;
transform: scale(1.5);
transition: transform 150ms;
svg {
animation: bounce 500ms alternate;
transform: scale(1.5);
transition: transform 150ms;
}
}
.timeline * {

View File

@ -1103,26 +1103,46 @@
</div>
<div class="controls-overlay">
<div class="timer">
<div>
<svg class="button button_play" x="0px" y="0px" viewBox="0 0 4.5 6.9" style="enable-background:new 0 0 4.5 6.9;">
<defs>
<filter id="shadow">
<feDropShadow dx="4" dy="8" stdDeviation="4"/>
</filter>
</defs>
<polyline style="filter:url(#shadow);" class="button" points="0.6,0.3 3.9,3.4 0.6,6.6 "></polyline>
</svg>
<svg class="button button_pause" x="0px" y="0px" viewBox="0 0 4.5 6.9" style="enable-background:new 0 0 4.5 6.9;">
<defs>
<filter id="shadow">
<feDropShadow dx="4" dy="8" stdDeviation="4"/>
</filter>
</defs>
<g style="filter:url(#shadow);">
<line x1="0.4" y1="0.1" x2="0.4" y2="6.8"></line>
<line x1="4.1" y1="0.1" x2="4.1" y2="6.8"></line>
</g>
</svg>
<div class="button-container">
<div class="container-play-pause">
<div class="button button_play">
<svg x="0px" y="0px" viewBox="0 0 4.5 6.9" style="enable-background:new 0 0 4.5 6.9;">
<defs>
<filter id="shadow_play" title="Start replaying">
<feDropShadow dx="4" dy="8" stdDeviation="4"/>
</filter>
</defs>
<polyline style="filter:url(#shadow_play);" class="button" points="0.6,0.3 3.9,3.4 0.6,6.6 "></polyline>
</svg>
</div>
<div class="button button_pause" title="Pause the current song">
<svg x="0px" y="0px" viewBox="0 0 4.5 6.9" style="enable-background:new 0 0 4.5 6.9;">
<defs>
<filter id="shadow_pause">
<feDropShadow dx="4" dy="8" stdDeviation="4"/>
</filter>
</defs>
<g style="filter:url(#shadow_pause);">
<line x1="0.4" y1="0.1" x2="0.4" y2="6.8"></line>
<line x1="4.1" y1="0.1" x2="4.1" y2="6.8"></line>
</g>
</svg>
</div>
</div>
<div>
<div class="button button_stop" title="Stop the music bot">
<svg x="0px" y="0px" viewBox="0 0 4.5 6.9" style="enable-background:new 0 0 4.5 6.9;">
<defs>
<filter id="shadow_stop">
<feDropShadow dx="4" dy="8" stdDeviation="4"/>
</filter>
</defs>
<g style="filter:url(#shadow_stop);">
<rect x="0.25" y="1.45" width="4" height="4" fill="black"></rect>
</g>
</svg>
</div>
</div>
</div>
<div class="timeline">
<div class="buffered"></div>
@ -1286,6 +1306,12 @@
<td>Local Volume:</td>
<td>{{>sound_volume}}%</td>
</tr>
{{if song_url}}
<tr>
<td>Currently replaying:</td>
<td><node key="song_url"></node></td>
</tr>
{{/if}}
<!-- TODO: Created by -->
</table>
<!-- Server groups -->

View File

@ -479,6 +479,10 @@ class ConnectionCommandHandler {
this["notifyclientpoke"] = this.handleNotifyClientPoke;
this["notifymusicplayerinfo"] = this.handleNotifyMusicPlayerInfo;
this["notifyservergroupclientadded"] = this.handleNotifyServerGroupClientAdd;
this["notifyservergroupclientdeleted"] = this.handleNotifyServerGroupClientRemove;
this["notifyclientchannelgroupchanged"] = this.handleNotifyClientChannelGroupChanged;
}
handleCommandResult(json) {
@ -802,14 +806,16 @@ class ConnectionCommandHandler {
console.error("Unknown client move (Channel from)!");
let self = client instanceof LocalClientEntry;
let current_clients;
if(self) {
chat.channelChat().name = channel_to.channelName();
for(let entry of client.channelTree.clientsByChannel(client.currentChannel()))
if(entry !== client) entry.getAudioController().stopAudio(true);
current_clients = client.channelTree.clientsByChannel(client.currentChannel())
this.connection._client.controlBar.updateVoice(channel_to);
}
tree.moveClient(client, channel_to);
for(const entry of current_clients || [])
if(entry !== client) entry.getAudioController().stopAudio(true);
const own_channel = this.connection._client.getClient().currentChannel();
if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
@ -842,7 +848,7 @@ class ConnectionCommandHandler {
channel_from ? channel_from.createChatTag(true) : undefined,
channel_to.createChatTag(true),
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
(json["reasonmsg"] || "").length > 0 ? " (" + json["msg"] + ")" : ""
json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
);
if(self) {
sound.play(Sound.CHANNEL_KICKED);
@ -1026,4 +1032,33 @@ class ConnectionCommandHandler {
sound.play(Sound.USER_POKED_SELF);
}
//TODO server chat message
handleNotifyServerGroupClientAdd(json) {
json = json[0];
const self = this.connection._client.getClient();
if(json["clid"] == self.clientId())
sound.play(Sound.GROUP_SERVER_ASSIGNED_SELF);
}
//TODO server chat message
handleNotifyServerGroupClientRemove(json) {
json = json[0];
const self = this.connection._client.getClient();
if(json["clid"] == self.clientId()) {
sound.play(Sound.GROUP_SERVER_REVOKED_SELF);
} else {
}
}
//TODO server chat message
handleNotifyClientChannelGroupChanged(json) {
json = json[0];
const self = this.connection._client.getClient();
if(json["clid"] == self.clientId())
sound.play(Sound.GROUP_CHANNEL_CHANGED_SELF);
}
}

View File

@ -44,7 +44,14 @@ enum Sound {
ERROR_INSUFFICIENT_PERMISSIONS = "error.insufficient_permissions",
MESSAGE_SEND = "message.send",
MESSAGE_RECEIVED = "message.received"
MESSAGE_RECEIVED = "message.received",
GROUP_SERVER_ASSIGNED = "group.server.assigned",
GROUP_SERVER_REVOKED = "group.server.revoked",
GROUP_CHANNEL_CHANGED = "group.channel.changed",
GROUP_SERVER_ASSIGNED_SELF = "group.server.assigned.self",
GROUP_SERVER_REVOKED_SELF = "group.server.revoked.self",
GROUP_CHANNEL_CHANGED_SELF = "group.channel.changed.self"
}
namespace sound {

View File

@ -293,14 +293,24 @@ class ChannelEntry {
this.channelTag().click(function () {
_this.channelTree.onSelect(_this);
});
this.channelTag().dblclick(() => this.joinChannel());
this.channelTag().dblclick(() => {
if($.isArray(this.channelTree.currently_selected)) { //Multiselect
return;
}
this.joinChannel()
});
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
this.channelTag().on("contextmenu", function (event) {
this.channelTag().on("contextmenu", (event) => {
event.preventDefault();
_this.channelTree.onSelect(_this);
if($.isArray(this.channelTree.currently_selected)) { //Multiselect
(this.channelTree.currently_selected_context_callback || ((_) => null))(event);
return;
}
_this.channelTree.onSelect(_this, true);
_this.showContextMenu(event.pageX, event.pageY, () => {
_this.channelTree.onSelect(undefined);
_this.channelTree.onSelect(undefined, true);
});
});
}
@ -395,6 +405,22 @@ class ChannelEntry {
}
},
MenuEntry.HR(),
{
type: MenuEntryType.ENTRY,
icon: "client-addon-collection",
name: "Create music bot",
callback: () => {
this.channelTree.client.serverConnection.sendCommand("musicbotcreate", {cid: this.channelId}).then(() => {
createInfoModal("Bot successfully created", "But has been successfully created.").open();
}).catch(error => {
if(error instanceof CommandResult) {
error = error.extra_message || error.message;
}
createErrorModal("Failed to create bot", "Failed to create the music bot:<br>" + error).open();
});
}
},
MenuEntry.HR(),
{
type: MenuEntryType.ENTRY,
icon: "client-channel_create_sub",

View File

@ -54,6 +54,7 @@ class ClientEntry {
protected _properties: ClientProperties;
protected lastVariableUpdate: number = 0;
protected _speaking: boolean = false;
private _listener_initialized: boolean;
channelTree: ChannelTree;
audioController: AudioController;
@ -91,28 +92,40 @@ class ClientEntry {
}
initializeListener(){
const _this = this;
if(this._listener_initialized) return;
this._listener_initialized = true;
this.tag.click(event => {
_this.channelTree.onSelect(_this);
this.channelTree.onSelect(this);
});
if(this.clientId() != this.channelTree.client.clientId && !(this instanceof MusicClientEntry))
this.tag.dblclick(event => {
if($.isArray(this.channelTree.currently_selected)) { //Multiselect
return;
}
this.chat(true).focus();
});
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
this.tag.on("contextmenu", function (event) {
this.tag.on("contextmenu", (event) => {
event.preventDefault();
_this.channelTree.onSelect(_this);
_this.showContextMenu(event.pageX, event.pageY, () => {
_this.channelTree.onSelect(undefined);
if($.isArray(this.channelTree.currently_selected)) { //Multiselect
(this.channelTree.currently_selected_context_callback || ((_) => null))(event);
return;
}
this.channelTree.onSelect(this, true);
this.showContextMenu(event.pageX, event.pageY, () => {
this.channelTree.onSelect(undefined, true);
});
return false;
});
}
this.tag.mousedown(event => {
if(event.which != 1) return; //Only the left button
this.channelTree.client_mover.activate(this, target => {
if(!target) return;
if(target == this._channel) return;
@ -296,7 +309,7 @@ class ClientEntry {
type: MenuEntryType.ENTRY,
icon: "client-kick_channel",
name: "Kick client from channel",
callback: function () {
callback: () => {
createInputModal("Kick client from channel", "Kick reason:<br>", text => true, result => {
if(result) {
console.log("Kicking client " + _this.clientNickName() + " from channel with reason " + result);
@ -313,7 +326,7 @@ class ClientEntry {
type: MenuEntryType.ENTRY,
icon: "client-kick_server",
name: "Kick client fom server",
callback: function () {
callback: () => {
createInputModal("Kick client from server", "Kick reason:<br>", text => true, result => {
if(result) {
console.log("Kicking client " + _this.clientNickName() + " from server with reason " + result);
@ -700,9 +713,11 @@ class LocalClientEntry extends ClientEntry {
super.initializeListener();
this.tag.find(".name").addClass("own_name");
const _self = this;
this.tag.dblclick(function () {
_self.openRename();
this.tag.dblclick(() => {
if($.isArray(this.channelTree.currently_selected)) { //Multiselect
return;
}
this.openRename();
});
}
@ -789,14 +804,34 @@ class MusicClientEntry extends ClientEntry {
{
name: "<b>Change bot name</b>",
icon: "client-change_nickname",
disabled: true,
callback: () => {},
disabled: false,
callback: () => {
createInputModal("Change music bots nickname", "New nickname:<br>", text => text.length >= 3 && text.length <= 31, result => {
if(result) {
this.channelTree.client.serverConnection.sendCommand("clientedit", {
clid: this.clientId(),
client_nickname: result
});
}
}, { width: 400, maxLength: 255 }).open();
},
type: MenuEntryType.ENTRY
}, {
name: "Change bot description",
icon: "client-edit",
disabled: true,
callback: () => {},
disabled: false,
callback: () => {
createInputModal("Change music bots description", "New description:<br>", text => true, result => {
if(typeof(result) === 'string') {
this.channelTree.client.serverConnection.sendCommand("clientedit", {
clid: this.clientId(),
client_description: result
});
}
}, { width: 400, maxLength: 255 }).open();
},
type: MenuEntryType.ENTRY
}, {
name: "Open music panel",
@ -804,6 +839,27 @@ class MusicClientEntry extends ClientEntry {
disabled: true,
callback: () => {},
type: MenuEntryType.ENTRY
}, {
name: "Quick url replay",
icon: "client-edit",
disabled: false,
callback: () => {
createInputModal("Please enter the URL", "URL:", text => true, result => {
if(result) {
this.channelTree.client.serverConnection.sendCommand("musicbotqueueadd", {
botid: this.properties.client_database_id,
type: "yt", //Its a hint not a force!
url: result
}).catch(error => {
if(error instanceof CommandResult) {
error = error.extra_message || error.message;
}
createErrorModal("Failed to replay url", "Failed to enqueue url:<br>" + error).open();
});
}
}, { width: 400, maxLength: 255 }).open();
},
type: MenuEntryType.ENTRY
},
MenuEntry.HR(),
...super.assignment_context(),
@ -830,7 +886,6 @@ class MusicClientEntry extends ClientEntry {
reasonid: ViewReasonId.VREASON_CHANNEL_KICK,
reasonmsg: result
});
}
}, { width: 400, maxLength: 255 }).open();
}
@ -853,9 +908,16 @@ class MusicClientEntry extends ClientEntry {
{
name: "Delete bot",
icon: "client-delete",
disabled: true,
disabled: false,
callback: () => {
//TODO
const tag = $.spawn("div").append(MessageHelper.formatMessage("Do you really want to delete {0}", this.createChatTag(false)));
Modals.spawnYesNo("Are you sure?", $.spawn("div").append(tag), result => {
if(result) {
this.channelTree.client.serverConnection.sendCommand("musicbotdelete", {
botid: this.properties.client_database_id
});
}
});
},
type: MenuEntryType.ENTRY
},

View File

@ -53,8 +53,10 @@ class ClientMover {
const d_y = this.origin_point.y - event.pageY;
this._active = Math.sqrt(d_x * d_x + d_y * d_y) > 5 * 5;
if(this._active)
if(this._active) {
ClientMover.move_element.show();
this.channel_tree.onSelect(this.selected_client, true);
}
}
const elements = document.elementsFromPoint(event.pageX, event.pageY);

View File

@ -128,17 +128,25 @@ class Hostbanner {
this.updater = undefined;
}
this.html_tag.empty();
const tag = this.generate_tag();
if(tag) {
this.html_tag.append(tag);
this.html_tag.prop("disabled", false);
} else
tag.then(element => {
this.html_tag.empty();
this.html_tag.append(element);
this.html_tag.prop("disabled", false);
}).catch(error => {
console.warn("Failed to load hostbanner: %o", error);
this.html_tag.empty();
this.html_tag.prop("disabled", true);
})
} else {
this.html_tag.empty();
this.html_tag.prop("disabled", true);
}
}
private generate_tag?() : JQuery<HTMLElement> {
private generate_tag?() : Promise<JQuery<HTMLElement>> {
if(!this.client.connected) return undefined;
const server = this.client.channelTree.server;
if(!server) return undefined;
@ -149,10 +157,19 @@ class Hostbanner {
properties["property_" + key] = server.properties[key];
const rendered = $("#tmpl_selected_hostbanner").renderTag(properties);
if(server.properties.virtualserver_hostbanner_gfx_interval > 0)
this.updater = setTimeout(() => this.update(), Math.min(server.properties.virtualserver_hostbanner_gfx_interval, 60) * 1000);
return rendered;
const image = rendered.find("img");
return new Promise<JQuery<HTMLElement>>((resolve, reject) => {
const node_image = image[0] as HTMLImageElement;
node_image.onload = () => {
console.debug("Hostbanner has been loaded");
if(server.properties.virtualserver_hostbanner_gfx_interval > 0)
this.updater = setTimeout(() => this.update(), Math.min(server.properties.virtualserver_hostbanner_gfx_interval, 60) * 1000);
resolve(rendered);
};
node_image.onerror = event => {
reject(event);
}
});
}
}
@ -354,6 +371,15 @@ function format_time(time: number) {
return (hours ? hours + ":" : "") + minutes + ':' + seconds;
}
enum MusicPlayerState {
SLEEPING,
LOADING,
PLAYING,
PAUSED,
STOPPED
}
class MusicInfoManager extends ClientInfoManager {
createFrame<_>(handle: InfoBar<_>, channel: MusicClientEntry, html_tag: JQuery<HTMLElement>) {
super.createFrame(handle, channel, html_tag);
@ -366,12 +392,12 @@ class MusicInfoManager extends ClientInfoManager {
let properties = super.buildProperties(bot);
{ //Render info frame
if(bot.properties.player_state != 2 && bot.properties.player_state != 3) {
if(bot.properties.player_state < MusicPlayerState.PLAYING) {
properties["music_player"] = $("#tmpl_music_frame_empty").renderTag().css("align-self", "center");
} else {
let frame = $.spawn("div").text("loading...") as JQuery<HTMLElement>;
properties["music_player"] = frame;
properties["song_url"] = $.spawn("a").text("loading...");
bot.requestPlayerInfo().then(info => {
let timestamp = Date.now();
@ -380,8 +406,11 @@ class MusicInfoManager extends ClientInfoManager {
let _frame = $("#tmpl_music_frame").renderTag({
song_name: info.player_title ? info.player_title :
info.song_url ? info.song_url : "No title or url",
song_url: info.song_url,
thumbnail: info.song_thumbnail && info.song_thumbnail.length > 0 ? info.song_thumbnail : undefined
}).css("align-self", "center");
properties["song_url"].text(info.song_url);
frame.replaceWith(_frame);
frame = _frame;
@ -389,8 +418,8 @@ class MusicInfoManager extends ClientInfoManager {
{
let button_play = frame.find(".button_play");
let button_pause = frame.find(".button_pause");
frame.find(".button_play").click(handler => {
let button_stop = frame.find('.button_stop');
button_play.click(handler => {
if(!button_play.hasClass("active")) {
this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
botid: bot.properties.client_database_id,
@ -400,10 +429,10 @@ class MusicInfoManager extends ClientInfoManager {
this.triggerUpdate();
});
}
button_pause.removeClass("active");
button_play.addClass("active");
button_pause.show();
button_play.hide();
});
frame.find(".button_pause").click(handler => {
button_pause.click(handler => {
if(!button_pause.hasClass("active")) {
this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
botid: bot.properties.client_database_id,
@ -413,14 +442,29 @@ class MusicInfoManager extends ClientInfoManager {
this.triggerUpdate();
});
}
button_play.removeClass("active");
button_pause.addClass("active");
button_play.show();
button_pause.hide();
});
button_stop.click(handler => {
this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
botid: bot.properties.client_database_id,
action: 0
}).then(updated => this.triggerUpdate()).catch(error => {
createErrorModal("Failed to execute stop", MessageHelper.formatMessage("Failed to execute stop.<br>{}", error)).open();
this.triggerUpdate();
});
});
if(bot.properties.player_state == 2)
button_play.addClass("active");
else if(bot.properties.player_state == 3)
button_pause.addClass("active");
if(bot.properties.player_state == 2) {
button_play.hide();
button_pause.show();
} else if(bot.properties.player_state == 3) {
button_pause.hide();
button_play.show();
} else if(bot.properties.player_state == 4) {
button_pause.hide();
button_play.show();
}
}
{ /* Button functions */

View File

@ -3,7 +3,7 @@
/// <reference path="../../client.ts" />
namespace Modals {
export function spawnBanClient(name: string, callback: (data: {
export function spawnBanClient(name: string | string[], callback: (data: {
length: number,
reason: string,
no_name: boolean,
@ -16,7 +16,7 @@ namespace Modals {
},
body: function () {
let tag = $("#tmpl_client_ban").renderTag({
client_name: name
client_name: $.isArray(name) ? '"' + name.join('", "') + '"' : name
});
let maxTime = 0; //globalClient.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).value;

View File

@ -105,17 +105,20 @@ class ServerEntry {
}
initializeListener(){
const _this = this;
this._htmlTag.click(function () {
_this.channelTree.onSelect(_this);
this._htmlTag.click(() => {
this.channelTree.onSelect(this);
});
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
this.htmlTag.on("contextmenu", function (event) {
this.htmlTag.on("contextmenu", (event) => {
event.preventDefault();
_this.channelTree.onSelect(_this);
_this.spawnContextMenu(event.pageX, event.pageY, () => { _this.channelTree.onSelect(undefined); });
if($.isArray(this.channelTree.currently_selected)) { //Multiselect
(this.channelTree.currently_selected_context_callback || ((_) => null))(event);
return;
}
this.channelTree.onSelect(this, true);
this.spawnContextMenu(event.pageX, event.pageY, () => { this.channelTree.onSelect(undefined, true); });
});
}
}

View File

@ -6,6 +6,11 @@
/// <reference path="client.ts" />
/// <reference path="modal/ModalCreateChannel.ts" />
let shift_pressed = false;
$(document).on('keyup keydown', function(e){
shift_pressed = e.shiftKey;
console.log(shift_pressed);
});
class ChannelTree {
client: TSClient;
htmlTree: JQuery;
@ -13,6 +18,8 @@ class ChannelTree {
channels: ChannelEntry[];
clients: ClientEntry[];
currently_selected: ClientEntry | ServerEntry | ChannelEntry | (ClientEntry | ServerEntry)[] = undefined;
currently_selected_context_callback: (event) => any = undefined;
readonly client_mover: ClientMover;
constructor(client, htmlTree) {
@ -24,13 +31,16 @@ class ChannelTree {
this.reset();
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
let _this = this;
this.htmlTree.parent().on("contextmenu", function (event) {
this.htmlTree.parent().on("contextmenu", (event) => {
if(event.isDefaultPrevented()) return;
event.preventDefault();
_this.onSelect(undefined);
_this.showContextMenu(event.pageX, event.pageY);
if($.isArray(this.currently_selected)) { //Multiselect
(this.currently_selected_context_callback || ((_) => null))(event);
} else {
this.onSelect(undefined);
this.showContextMenu(event.pageX, event.pageY);
}
});
}
@ -233,21 +243,72 @@ class ChannelTree {
}
findClient?(clientId: number) : ClientEntry {
for(let index = 0; index < this.clients.length; index++)
if(this.clients[index].clientId() == clientId) return this.clients[index];
for(let index = 0; index < this.clients.length; index++) {
if(this.clients[index].clientId() == clientId)
return this.clients[index];
}
return undefined;
}
find_client_by_dbid?(client_dbid: number) : ClientEntry {
for(let index = 0; index < this.clients.length; index++)
if(this.clients[index].properties.client_database_id == client_dbid) return this.clients[index];
for(let index = 0; index < this.clients.length; index++) {
if(this.clients[index].properties.client_database_id == client_dbid)
return this.clients[index];
}
return undefined;
}
onSelect(entry?: ChannelEntry | ClientEntry | ServerEntry) {
this.htmlTree.find(".selected").each(function (idx, e) {
$(e).removeClass("selected");
});
private static same_selected_type(a, b) {
if(a instanceof ChannelEntry)
return b instanceof ChannelEntry;
if(a instanceof ClientEntry)
return b instanceof ClientEntry;
if(a instanceof ServerEntry)
return b instanceof ServerEntry;
return a == b;
}
onSelect(entry?: ChannelEntry | ClientEntry | ServerEntry, enforce_single?: boolean) {
console.log(shift_pressed);
if(this.currently_selected && shift_pressed && entry instanceof ClientEntry) { //Currently we're only supporting client multiselects :D
if(!entry) return; //Nowhere
if($.isArray(this.currently_selected)) {
if(!ChannelTree.same_selected_type(this.currently_selected[0], entry)) return; //Not the same type
} else if(ChannelTree.same_selected_type(this.currently_selected, entry)) {
this.currently_selected = [this.currently_selected] as any;
}
if(entry instanceof ChannelEntry)
this.currently_selected_context_callback = this.callback_multiselect_channel.bind(this);
if(entry instanceof ClientEntry)
this.currently_selected_context_callback = this.callback_multiselect_client.bind(this);
} else
this.currently_selected = undefined;
if(!$.isArray(this.currently_selected) || enforce_single) {
this.currently_selected = entry;
this.htmlTree.find(".selected").each(function (idx, e) {
$(e).removeClass("selected");
});
} else {
for(const e of this.currently_selected)
if(e == entry) {
this.currently_selected.remove(e);
if(entry instanceof ChannelEntry)
(entry as ChannelEntry).rootTag().find("> .channelLine").removeClass("selected");
else if(entry instanceof ClientEntry)
(entry as ClientEntry).tag.removeClass("selected");
else if(entry instanceof ServerEntry)
(entry as ServerEntry).htmlTag.removeClass("selected");
if(this.currently_selected.length == 1)
this.currently_selected = this.currently_selected[0];
else if(this.currently_selected.length == 0)
this.currently_selected = undefined;
//Already selected
return;
}
this.currently_selected.push(entry as any);
}
if(entry instanceof ChannelEntry)
(entry as ChannelEntry).rootTag().find("> .channelLine").addClass("selected");
@ -255,7 +316,136 @@ class ChannelTree {
(entry as ClientEntry).tag.addClass("selected");
else if(entry instanceof ServerEntry)
(entry as ServerEntry).htmlTag.addClass("selected");
this.client.selectInfo.setCurrentSelected(entry);
this.client.selectInfo.setCurrentSelected($.isArray(this.currently_selected) ? undefined : entry);
}
private callback_multiselect_channel(event) {
console.log("Multiselect channel");
}
private callback_multiselect_client(event) {
console.log("Multiselect client");
const clients = this.currently_selected as ClientEntry[];
const music_only = clients.map(e => e instanceof MusicClientEntry ? 0 : 1).reduce((a, b) => a + b, 0) == 0;
const music_entry = clients.map(e => e instanceof MusicClientEntry ? 1 : 0).reduce((a, b) => a + b, 0) > 0;
const local_client = clients.map(e => e instanceof LocalClientEntry ? 1 : 0).reduce((a, b) => a + b, 0) > 0;
console.log("Music only: %o | Container music: %o | Container local: %o", music_entry, music_entry, local_client);
let entries: ContextMenuEntry[] = [];
if (!music_entry && !local_client) { //Music bots or local client cant be poked
entries.push({
type: MenuEntryType.ENTRY,
icon: "client-poke",
name: "Poke clients",
callback: () => {
createInputModal("Poke clients", "Poke message:<br>", text => true, result => {
if (typeof(result) === "string") {
for (const client of this.currently_selected as ClientEntry[])
this.client.serverConnection.sendCommand("clientpoke", {
clid: client.clientId(),
msg: result
});
}
}, {width: 400, maxLength: 512}).open();
}
});
}
entries.push({
type: MenuEntryType.ENTRY,
icon: "client-move_client_to_own_channel",
name: "Move clients to your channel",
callback: () => {
const target = this.client.getClient().currentChannel().getChannelId();
for(const client of clients)
this.client.serverConnection.sendCommand("clientmove", {
clid: client.clientId(),
cid: target
});
}
});
if (!local_client) {//local client cant be kicked and/or banned or kicked
entries.push(MenuEntry.HR());
entries.push({
type: MenuEntryType.ENTRY,
icon: "client-kick_channel",
name: "Kick clients from channel",
callback: () => {
createInputModal("Kick clients from channel", "Kick reason:<br>", text => true, result => {
if (result) {
for (const client of clients)
this.client.serverConnection.sendCommand("clientkick", {
clid: client.clientId(),
reasonid: ViewReasonId.VREASON_CHANNEL_KICK,
reasonmsg: result
});
}
}, {width: 400, maxLength: 255}).open();
}
});
if (!music_entry) { //Music bots cant be banned or kicked
entries.push({
type: MenuEntryType.ENTRY,
icon: "client-kick_server",
name: "Kick clients fom server",
callback: () => {
createInputModal("Kick clients from server", "Kick reason:<br>", text => true, result => {
if (result) {
for (const client of clients)
this.client.serverConnection.sendCommand("clientkick", {
clid: client.clientId(),
reasonid: ViewReasonId.VREASON_SERVER_KICK,
reasonmsg: result
});
}
}, {width: 400, maxLength: 255}).open();
}
}, {
type: MenuEntryType.ENTRY,
icon: "client-ban_client",
name: "Ban clients",
invalidPermission: !this.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
callback: () => {
Modals.spawnBanClient((clients).map(entry => entry.clientNickName()), (data) => {
for (const client of clients)
this.client.serverConnection.sendCommand("banclient", {
uid: client.properties.client_unique_identifier,
banreason: data.reason,
time: data.length
}, [data.no_ip ? "no-ip" : "", data.no_hwid ? "no-hardware-id" : "", data.no_name ? "no-nickname" : ""]).then(() => {
sound.play(Sound.USER_BANNED);
});
});
}
});
}
if(music_only) {
entries.push(MenuEntry.HR());
entries.push({
name: "Delete bots",
icon: "client-delete",
disabled: false,
callback: () => {
const param_string = clients.map((_, index) => "{" + index + "}").join(', ');
const param_values = clients.map(client => client.createChatTag(true));
const tag = $.spawn("div").append(...MessageHelper.formatMessage("Do you really want to delete " + param_string, ...param_values));
const tag_container = $.spawn("div").append(tag);
Modals.spawnYesNo("Are you sure?", tag_container, result => {
if(result) {
for(const client of clients)
this.client.serverConnection.sendCommand("musicbotdelete", {
botid: client.properties.client_database_id
});
}
});
},
type: MenuEntryType.ENTRY
});
}
}
spawn_context_menu(event.pageX, event.pageY, ...entries);
}
clientsByGroup(group: Group) : ClientEntry[] {

View File

@ -80,6 +80,8 @@ class AudioController {
private _codecCache: CodecClientCache[] = [];
private _timeIndex: number = 0;
private _latencyBufferLength: number = 3;
private _buffer_timeout: NodeJS.Timer;
allowBuffering: boolean = true;
//Events
@ -125,6 +127,7 @@ class AudioController {
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;
@ -183,14 +186,18 @@ class AudioController {
this.playingAudioCache = [];
}
this.testBufferQueue();
this.playQueue(); //Flush queue
}
private testBufferQueue() {
if(this.audioCache.length == 0 && this.playingAudioCache.length == 0) {
if(this.playerState != PlayerState.STOPPING) {
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("[Audio] Detected a buffer underflow!");
this.reset_buffer_timeout(true);
} else {
this.playerState = PlayerState.STOPPED;
this.onSilence();
@ -198,6 +205,19 @@ class AudioController {
}
}
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("[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) {