Implementing a lot of new features again
parent
89a29a8717
commit
48822c604a
|
@ -1,4 +1,12 @@
|
||||||
# Changelog:
|
# 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**
|
* **03.11.18**
|
||||||
- Reworked on the basic overlay sizes
|
- Reworked on the basic overlay sizes
|
||||||
- Added hostbanner to the UI
|
- Added hostbanner to the UI
|
||||||
|
|
|
@ -46,4 +46,12 @@ user.left.disconnect;User disconnected from your channel
|
||||||
user.left.banned;User in your channel was banned from the server
|
user.left.banned;User in your channel was banned from the server
|
||||||
|
|
||||||
#Error
|
#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
|
|
|
@ -170,37 +170,55 @@ $ease: cubic-bezier(.45, 0, .55, 1);
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
vertical-align: center;
|
vertical-align: center;
|
||||||
|
|
||||||
|
.button-container{
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
fill: none;
|
|
||||||
stroke: #4c4c4c;;
|
|
||||||
stroke-width: 0.5;
|
|
||||||
stroke-miterlimit: 10;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
color: white;
|
svg {
|
||||||
mix-blend-mode: difference;
|
|
||||||
//box-shadow: 20px 20px 20px 20px rgb(186, 0, 12);
|
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 {
|
.button.active {
|
||||||
animation: bounce 500ms alternate;
|
svg {
|
||||||
transform: scale(1.3);
|
animation: bounce 500ms alternate;
|
||||||
transition: transform 150ms;
|
transform: scale(1.3);
|
||||||
|
transition: transform 150ms;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:hover {
|
.button:hover {
|
||||||
animation: bounce 500ms alternate;
|
svg {
|
||||||
transform: scale(1.1);
|
animation: bounce 500ms alternate;
|
||||||
transition: transform 150ms;
|
transform: scale(1.1);
|
||||||
|
transition: transform 150ms;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.active:hover {
|
.button.active:hover {
|
||||||
animation: bounce 500ms alternate;
|
svg {
|
||||||
transform: scale(1.5);
|
animation: bounce 500ms alternate;
|
||||||
transition: transform 150ms;
|
transform: scale(1.5);
|
||||||
|
transition: transform 150ms;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline * {
|
.timeline * {
|
||||||
|
|
|
@ -1103,26 +1103,46 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="controls-overlay">
|
<div class="controls-overlay">
|
||||||
<div class="timer">
|
<div class="timer">
|
||||||
<div>
|
<div class="button-container">
|
||||||
<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;">
|
<div class="container-play-pause">
|
||||||
<defs>
|
<div class="button button_play">
|
||||||
<filter id="shadow">
|
<svg x="0px" y="0px" viewBox="0 0 4.5 6.9" style="enable-background:new 0 0 4.5 6.9;">
|
||||||
<feDropShadow dx="4" dy="8" stdDeviation="4"/>
|
<defs>
|
||||||
</filter>
|
<filter id="shadow_play" title="Start replaying">
|
||||||
</defs>
|
<feDropShadow dx="4" dy="8" stdDeviation="4"/>
|
||||||
<polyline style="filter:url(#shadow);" class="button" points="0.6,0.3 3.9,3.4 0.6,6.6 "></polyline>
|
</filter>
|
||||||
</svg>
|
</defs>
|
||||||
<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;">
|
<polyline style="filter:url(#shadow_play);" class="button" points="0.6,0.3 3.9,3.4 0.6,6.6 "></polyline>
|
||||||
<defs>
|
</svg>
|
||||||
<filter id="shadow">
|
</div>
|
||||||
<feDropShadow dx="4" dy="8" stdDeviation="4"/>
|
<div class="button button_pause" title="Pause the current song">
|
||||||
</filter>
|
<svg x="0px" y="0px" viewBox="0 0 4.5 6.9" style="enable-background:new 0 0 4.5 6.9;">
|
||||||
</defs>
|
<defs>
|
||||||
<g style="filter:url(#shadow);">
|
<filter id="shadow_pause">
|
||||||
<line x1="0.4" y1="0.1" x2="0.4" y2="6.8"></line>
|
<feDropShadow dx="4" dy="8" stdDeviation="4"/>
|
||||||
<line x1="4.1" y1="0.1" x2="4.1" y2="6.8"></line>
|
</filter>
|
||||||
</g>
|
</defs>
|
||||||
</svg>
|
<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>
|
||||||
<div class="timeline">
|
<div class="timeline">
|
||||||
<div class="buffered"></div>
|
<div class="buffered"></div>
|
||||||
|
@ -1286,6 +1306,12 @@
|
||||||
<td>Local Volume:</td>
|
<td>Local Volume:</td>
|
||||||
<td>{{>sound_volume}}%</td>
|
<td>{{>sound_volume}}%</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{{if song_url}}
|
||||||
|
<tr>
|
||||||
|
<td>Currently replaying:</td>
|
||||||
|
<td><node key="song_url"></node></td>
|
||||||
|
</tr>
|
||||||
|
{{/if}}
|
||||||
<!-- TODO: Created by -->
|
<!-- TODO: Created by -->
|
||||||
</table>
|
</table>
|
||||||
<!-- Server groups -->
|
<!-- Server groups -->
|
||||||
|
|
|
@ -479,6 +479,10 @@ class ConnectionCommandHandler {
|
||||||
this["notifyclientpoke"] = this.handleNotifyClientPoke;
|
this["notifyclientpoke"] = this.handleNotifyClientPoke;
|
||||||
|
|
||||||
this["notifymusicplayerinfo"] = this.handleNotifyMusicPlayerInfo;
|
this["notifymusicplayerinfo"] = this.handleNotifyMusicPlayerInfo;
|
||||||
|
|
||||||
|
this["notifyservergroupclientadded"] = this.handleNotifyServerGroupClientAdd;
|
||||||
|
this["notifyservergroupclientdeleted"] = this.handleNotifyServerGroupClientRemove;
|
||||||
|
this["notifyclientchannelgroupchanged"] = this.handleNotifyClientChannelGroupChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCommandResult(json) {
|
handleCommandResult(json) {
|
||||||
|
@ -802,14 +806,16 @@ class ConnectionCommandHandler {
|
||||||
console.error("Unknown client move (Channel from)!");
|
console.error("Unknown client move (Channel from)!");
|
||||||
|
|
||||||
let self = client instanceof LocalClientEntry;
|
let self = client instanceof LocalClientEntry;
|
||||||
|
let current_clients;
|
||||||
if(self) {
|
if(self) {
|
||||||
chat.channelChat().name = channel_to.channelName();
|
chat.channelChat().name = channel_to.channelName();
|
||||||
for(let entry of client.channelTree.clientsByChannel(client.currentChannel()))
|
current_clients = client.channelTree.clientsByChannel(client.currentChannel())
|
||||||
if(entry !== client) entry.getAudioController().stopAudio(true);
|
|
||||||
this.connection._client.controlBar.updateVoice(channel_to);
|
this.connection._client.controlBar.updateVoice(channel_to);
|
||||||
}
|
}
|
||||||
|
|
||||||
tree.moveClient(client, 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();
|
const own_channel = this.connection._client.getClient().currentChannel();
|
||||||
if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
|
if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
|
||||||
|
@ -842,7 +848,7 @@ class ConnectionCommandHandler {
|
||||||
channel_from ? channel_from.createChatTag(true) : undefined,
|
channel_from ? channel_from.createChatTag(true) : undefined,
|
||||||
channel_to.createChatTag(true),
|
channel_to.createChatTag(true),
|
||||||
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
|
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
|
||||||
(json["reasonmsg"] || "").length > 0 ? " (" + json["msg"] + ")" : ""
|
json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
|
||||||
);
|
);
|
||||||
if(self) {
|
if(self) {
|
||||||
sound.play(Sound.CHANNEL_KICKED);
|
sound.play(Sound.CHANNEL_KICKED);
|
||||||
|
@ -1026,4 +1032,33 @@ class ConnectionCommandHandler {
|
||||||
|
|
||||||
sound.play(Sound.USER_POKED_SELF);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -44,7 +44,14 @@ enum Sound {
|
||||||
ERROR_INSUFFICIENT_PERMISSIONS = "error.insufficient_permissions",
|
ERROR_INSUFFICIENT_PERMISSIONS = "error.insufficient_permissions",
|
||||||
|
|
||||||
MESSAGE_SEND = "message.send",
|
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 {
|
namespace sound {
|
||||||
|
|
|
@ -293,14 +293,24 @@ class ChannelEntry {
|
||||||
this.channelTag().click(function () {
|
this.channelTag().click(function () {
|
||||||
_this.channelTree.onSelect(_this);
|
_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)) {
|
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
|
||||||
this.channelTag().on("contextmenu", function (event) {
|
this.channelTag().on("contextmenu", (event) => {
|
||||||
event.preventDefault();
|
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.showContextMenu(event.pageX, event.pageY, () => {
|
||||||
_this.channelTree.onSelect(undefined);
|
_this.channelTree.onSelect(undefined, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -395,6 +405,22 @@ class ChannelEntry {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MenuEntry.HR(),
|
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,
|
type: MenuEntryType.ENTRY,
|
||||||
icon: "client-channel_create_sub",
|
icon: "client-channel_create_sub",
|
||||||
|
|
|
@ -54,6 +54,7 @@ class ClientEntry {
|
||||||
protected _properties: ClientProperties;
|
protected _properties: ClientProperties;
|
||||||
protected lastVariableUpdate: number = 0;
|
protected lastVariableUpdate: number = 0;
|
||||||
protected _speaking: boolean = false;
|
protected _speaking: boolean = false;
|
||||||
|
private _listener_initialized: boolean;
|
||||||
|
|
||||||
channelTree: ChannelTree;
|
channelTree: ChannelTree;
|
||||||
audioController: AudioController;
|
audioController: AudioController;
|
||||||
|
@ -91,28 +92,40 @@ class ClientEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeListener(){
|
initializeListener(){
|
||||||
const _this = this;
|
if(this._listener_initialized) return;
|
||||||
|
this._listener_initialized = true;
|
||||||
|
|
||||||
this.tag.click(event => {
|
this.tag.click(event => {
|
||||||
_this.channelTree.onSelect(_this);
|
this.channelTree.onSelect(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
if(this.clientId() != this.channelTree.client.clientId && !(this instanceof MusicClientEntry))
|
if(this.clientId() != this.channelTree.client.clientId && !(this instanceof MusicClientEntry))
|
||||||
this.tag.dblclick(event => {
|
this.tag.dblclick(event => {
|
||||||
|
if($.isArray(this.channelTree.currently_selected)) { //Multiselect
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.chat(true).focus();
|
this.chat(true).focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
|
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
|
||||||
this.tag.on("contextmenu", function (event) {
|
this.tag.on("contextmenu", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
_this.channelTree.onSelect(_this);
|
if($.isArray(this.channelTree.currently_selected)) { //Multiselect
|
||||||
_this.showContextMenu(event.pageX, event.pageY, () => {
|
(this.channelTree.currently_selected_context_callback || ((_) => null))(event);
|
||||||
_this.channelTree.onSelect(undefined);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.channelTree.onSelect(this, true);
|
||||||
|
this.showContextMenu(event.pageX, event.pageY, () => {
|
||||||
|
this.channelTree.onSelect(undefined, true);
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tag.mousedown(event => {
|
this.tag.mousedown(event => {
|
||||||
|
if(event.which != 1) return; //Only the left button
|
||||||
|
|
||||||
this.channelTree.client_mover.activate(this, target => {
|
this.channelTree.client_mover.activate(this, target => {
|
||||||
if(!target) return;
|
if(!target) return;
|
||||||
if(target == this._channel) return;
|
if(target == this._channel) return;
|
||||||
|
@ -296,7 +309,7 @@ class ClientEntry {
|
||||||
type: MenuEntryType.ENTRY,
|
type: MenuEntryType.ENTRY,
|
||||||
icon: "client-kick_channel",
|
icon: "client-kick_channel",
|
||||||
name: "Kick client from channel",
|
name: "Kick client from channel",
|
||||||
callback: function () {
|
callback: () => {
|
||||||
createInputModal("Kick client from channel", "Kick reason:<br>", text => true, result => {
|
createInputModal("Kick client from channel", "Kick reason:<br>", text => true, result => {
|
||||||
if(result) {
|
if(result) {
|
||||||
console.log("Kicking client " + _this.clientNickName() + " from channel with reason " + result);
|
console.log("Kicking client " + _this.clientNickName() + " from channel with reason " + result);
|
||||||
|
@ -313,7 +326,7 @@ class ClientEntry {
|
||||||
type: MenuEntryType.ENTRY,
|
type: MenuEntryType.ENTRY,
|
||||||
icon: "client-kick_server",
|
icon: "client-kick_server",
|
||||||
name: "Kick client fom server",
|
name: "Kick client fom server",
|
||||||
callback: function () {
|
callback: () => {
|
||||||
createInputModal("Kick client from server", "Kick reason:<br>", text => true, result => {
|
createInputModal("Kick client from server", "Kick reason:<br>", text => true, result => {
|
||||||
if(result) {
|
if(result) {
|
||||||
console.log("Kicking client " + _this.clientNickName() + " from server with reason " + result);
|
console.log("Kicking client " + _this.clientNickName() + " from server with reason " + result);
|
||||||
|
@ -700,9 +713,11 @@ class LocalClientEntry extends ClientEntry {
|
||||||
super.initializeListener();
|
super.initializeListener();
|
||||||
this.tag.find(".name").addClass("own_name");
|
this.tag.find(".name").addClass("own_name");
|
||||||
|
|
||||||
const _self = this;
|
this.tag.dblclick(() => {
|
||||||
this.tag.dblclick(function () {
|
if($.isArray(this.channelTree.currently_selected)) { //Multiselect
|
||||||
_self.openRename();
|
return;
|
||||||
|
}
|
||||||
|
this.openRename();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -789,14 +804,34 @@ class MusicClientEntry extends ClientEntry {
|
||||||
{
|
{
|
||||||
name: "<b>Change bot name</b>",
|
name: "<b>Change bot name</b>",
|
||||||
icon: "client-change_nickname",
|
icon: "client-change_nickname",
|
||||||
disabled: true,
|
disabled: false,
|
||||||
callback: () => {},
|
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
|
type: MenuEntryType.ENTRY
|
||||||
}, {
|
}, {
|
||||||
name: "Change bot description",
|
name: "Change bot description",
|
||||||
icon: "client-edit",
|
icon: "client-edit",
|
||||||
disabled: true,
|
disabled: false,
|
||||||
callback: () => {},
|
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
|
type: MenuEntryType.ENTRY
|
||||||
}, {
|
}, {
|
||||||
name: "Open music panel",
|
name: "Open music panel",
|
||||||
|
@ -804,6 +839,27 @@ class MusicClientEntry extends ClientEntry {
|
||||||
disabled: true,
|
disabled: true,
|
||||||
callback: () => {},
|
callback: () => {},
|
||||||
type: MenuEntryType.ENTRY
|
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(),
|
MenuEntry.HR(),
|
||||||
...super.assignment_context(),
|
...super.assignment_context(),
|
||||||
|
@ -830,7 +886,6 @@ class MusicClientEntry extends ClientEntry {
|
||||||
reasonid: ViewReasonId.VREASON_CHANNEL_KICK,
|
reasonid: ViewReasonId.VREASON_CHANNEL_KICK,
|
||||||
reasonmsg: result
|
reasonmsg: result
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}, { width: 400, maxLength: 255 }).open();
|
}, { width: 400, maxLength: 255 }).open();
|
||||||
}
|
}
|
||||||
|
@ -853,9 +908,16 @@ class MusicClientEntry extends ClientEntry {
|
||||||
{
|
{
|
||||||
name: "Delete bot",
|
name: "Delete bot",
|
||||||
icon: "client-delete",
|
icon: "client-delete",
|
||||||
disabled: true,
|
disabled: false,
|
||||||
callback: () => {
|
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
|
type: MenuEntryType.ENTRY
|
||||||
},
|
},
|
||||||
|
|
|
@ -53,8 +53,10 @@ class ClientMover {
|
||||||
const d_y = this.origin_point.y - event.pageY;
|
const d_y = this.origin_point.y - event.pageY;
|
||||||
this._active = Math.sqrt(d_x * d_x + d_y * d_y) > 5 * 5;
|
this._active = Math.sqrt(d_x * d_x + d_y * d_y) > 5 * 5;
|
||||||
|
|
||||||
if(this._active)
|
if(this._active) {
|
||||||
ClientMover.move_element.show();
|
ClientMover.move_element.show();
|
||||||
|
this.channel_tree.onSelect(this.selected_client, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const elements = document.elementsFromPoint(event.pageX, event.pageY);
|
const elements = document.elementsFromPoint(event.pageX, event.pageY);
|
||||||
|
|
|
@ -128,17 +128,25 @@ class Hostbanner {
|
||||||
this.updater = undefined;
|
this.updater = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.html_tag.empty();
|
|
||||||
const tag = this.generate_tag();
|
const tag = this.generate_tag();
|
||||||
|
|
||||||
if(tag) {
|
if(tag) {
|
||||||
this.html_tag.append(tag);
|
tag.then(element => {
|
||||||
this.html_tag.prop("disabled", false);
|
this.html_tag.empty();
|
||||||
} else
|
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);
|
this.html_tag.prop("disabled", true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private generate_tag?() : JQuery<HTMLElement> {
|
private generate_tag?() : Promise<JQuery<HTMLElement>> {
|
||||||
if(!this.client.connected) return undefined;
|
if(!this.client.connected) return undefined;
|
||||||
const server = this.client.channelTree.server;
|
const server = this.client.channelTree.server;
|
||||||
if(!server) return undefined;
|
if(!server) return undefined;
|
||||||
|
@ -149,10 +157,19 @@ class Hostbanner {
|
||||||
properties["property_" + key] = server.properties[key];
|
properties["property_" + key] = server.properties[key];
|
||||||
|
|
||||||
const rendered = $("#tmpl_selected_hostbanner").renderTag(properties);
|
const rendered = $("#tmpl_selected_hostbanner").renderTag(properties);
|
||||||
|
const image = rendered.find("img");
|
||||||
if(server.properties.virtualserver_hostbanner_gfx_interval > 0)
|
return new Promise<JQuery<HTMLElement>>((resolve, reject) => {
|
||||||
this.updater = setTimeout(() => this.update(), Math.min(server.properties.virtualserver_hostbanner_gfx_interval, 60) * 1000);
|
const node_image = image[0] as HTMLImageElement;
|
||||||
return rendered;
|
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;
|
return (hours ? hours + ":" : "") + minutes + ':' + seconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum MusicPlayerState {
|
||||||
|
SLEEPING,
|
||||||
|
LOADING,
|
||||||
|
|
||||||
|
PLAYING,
|
||||||
|
PAUSED,
|
||||||
|
STOPPED
|
||||||
|
}
|
||||||
|
|
||||||
class MusicInfoManager extends ClientInfoManager {
|
class MusicInfoManager extends ClientInfoManager {
|
||||||
createFrame<_>(handle: InfoBar<_>, channel: MusicClientEntry, html_tag: JQuery<HTMLElement>) {
|
createFrame<_>(handle: InfoBar<_>, channel: MusicClientEntry, html_tag: JQuery<HTMLElement>) {
|
||||||
super.createFrame(handle, channel, html_tag);
|
super.createFrame(handle, channel, html_tag);
|
||||||
|
@ -366,12 +392,12 @@ class MusicInfoManager extends ClientInfoManager {
|
||||||
|
|
||||||
let properties = super.buildProperties(bot);
|
let properties = super.buildProperties(bot);
|
||||||
{ //Render info frame
|
{ //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");
|
properties["music_player"] = $("#tmpl_music_frame_empty").renderTag().css("align-self", "center");
|
||||||
} else {
|
} else {
|
||||||
let frame = $.spawn("div").text("loading...") as JQuery<HTMLElement>;
|
let frame = $.spawn("div").text("loading...") as JQuery<HTMLElement>;
|
||||||
properties["music_player"] = frame;
|
properties["music_player"] = frame;
|
||||||
|
properties["song_url"] = $.spawn("a").text("loading...");
|
||||||
|
|
||||||
bot.requestPlayerInfo().then(info => {
|
bot.requestPlayerInfo().then(info => {
|
||||||
let timestamp = Date.now();
|
let timestamp = Date.now();
|
||||||
|
@ -380,8 +406,11 @@ class MusicInfoManager extends ClientInfoManager {
|
||||||
let _frame = $("#tmpl_music_frame").renderTag({
|
let _frame = $("#tmpl_music_frame").renderTag({
|
||||||
song_name: info.player_title ? info.player_title :
|
song_name: info.player_title ? info.player_title :
|
||||||
info.song_url ? info.song_url : "No title or url",
|
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
|
thumbnail: info.song_thumbnail && info.song_thumbnail.length > 0 ? info.song_thumbnail : undefined
|
||||||
}).css("align-self", "center");
|
}).css("align-self", "center");
|
||||||
|
properties["song_url"].text(info.song_url);
|
||||||
|
|
||||||
frame.replaceWith(_frame);
|
frame.replaceWith(_frame);
|
||||||
frame = _frame;
|
frame = _frame;
|
||||||
|
|
||||||
|
@ -389,8 +418,8 @@ class MusicInfoManager extends ClientInfoManager {
|
||||||
{
|
{
|
||||||
let button_play = frame.find(".button_play");
|
let button_play = frame.find(".button_play");
|
||||||
let button_pause = frame.find(".button_pause");
|
let button_pause = frame.find(".button_pause");
|
||||||
|
let button_stop = frame.find('.button_stop');
|
||||||
frame.find(".button_play").click(handler => {
|
button_play.click(handler => {
|
||||||
if(!button_play.hasClass("active")) {
|
if(!button_play.hasClass("active")) {
|
||||||
this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
|
this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
|
||||||
botid: bot.properties.client_database_id,
|
botid: bot.properties.client_database_id,
|
||||||
|
@ -400,10 +429,10 @@ class MusicInfoManager extends ClientInfoManager {
|
||||||
this.triggerUpdate();
|
this.triggerUpdate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
button_pause.removeClass("active");
|
button_pause.show();
|
||||||
button_play.addClass("active");
|
button_play.hide();
|
||||||
});
|
});
|
||||||
frame.find(".button_pause").click(handler => {
|
button_pause.click(handler => {
|
||||||
if(!button_pause.hasClass("active")) {
|
if(!button_pause.hasClass("active")) {
|
||||||
this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
|
this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
|
||||||
botid: bot.properties.client_database_id,
|
botid: bot.properties.client_database_id,
|
||||||
|
@ -413,14 +442,29 @@ class MusicInfoManager extends ClientInfoManager {
|
||||||
this.triggerUpdate();
|
this.triggerUpdate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
button_play.removeClass("active");
|
button_play.show();
|
||||||
button_pause.addClass("active");
|
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)
|
if(bot.properties.player_state == 2) {
|
||||||
button_play.addClass("active");
|
button_play.hide();
|
||||||
else if(bot.properties.player_state == 3)
|
button_pause.show();
|
||||||
button_pause.addClass("active");
|
} 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 */
|
{ /* Button functions */
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
/// <reference path="../../client.ts" />
|
/// <reference path="../../client.ts" />
|
||||||
|
|
||||||
namespace Modals {
|
namespace Modals {
|
||||||
export function spawnBanClient(name: string, callback: (data: {
|
export function spawnBanClient(name: string | string[], callback: (data: {
|
||||||
length: number,
|
length: number,
|
||||||
reason: string,
|
reason: string,
|
||||||
no_name: boolean,
|
no_name: boolean,
|
||||||
|
@ -16,7 +16,7 @@ namespace Modals {
|
||||||
},
|
},
|
||||||
body: function () {
|
body: function () {
|
||||||
let tag = $("#tmpl_client_ban").renderTag({
|
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;
|
let maxTime = 0; //globalClient.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).value;
|
||||||
|
|
|
@ -105,17 +105,20 @@ class ServerEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeListener(){
|
initializeListener(){
|
||||||
const _this = this;
|
this._htmlTag.click(() => {
|
||||||
|
this.channelTree.onSelect(this);
|
||||||
this._htmlTag.click(function () {
|
|
||||||
_this.channelTree.onSelect(_this);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
|
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
|
||||||
this.htmlTag.on("contextmenu", function (event) {
|
this.htmlTag.on("contextmenu", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
_this.channelTree.onSelect(_this);
|
if($.isArray(this.channelTree.currently_selected)) { //Multiselect
|
||||||
_this.spawnContextMenu(event.pageX, event.pageY, () => { _this.channelTree.onSelect(undefined); });
|
(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); });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,11 @@
|
||||||
/// <reference path="client.ts" />
|
/// <reference path="client.ts" />
|
||||||
/// <reference path="modal/ModalCreateChannel.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 {
|
class ChannelTree {
|
||||||
client: TSClient;
|
client: TSClient;
|
||||||
htmlTree: JQuery;
|
htmlTree: JQuery;
|
||||||
|
@ -13,6 +18,8 @@ class ChannelTree {
|
||||||
channels: ChannelEntry[];
|
channels: ChannelEntry[];
|
||||||
clients: ClientEntry[];
|
clients: ClientEntry[];
|
||||||
|
|
||||||
|
currently_selected: ClientEntry | ServerEntry | ChannelEntry | (ClientEntry | ServerEntry)[] = undefined;
|
||||||
|
currently_selected_context_callback: (event) => any = undefined;
|
||||||
readonly client_mover: ClientMover;
|
readonly client_mover: ClientMover;
|
||||||
|
|
||||||
constructor(client, htmlTree) {
|
constructor(client, htmlTree) {
|
||||||
|
@ -24,13 +31,16 @@ class ChannelTree {
|
||||||
this.reset();
|
this.reset();
|
||||||
|
|
||||||
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
|
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
|
||||||
let _this = this;
|
this.htmlTree.parent().on("contextmenu", (event) => {
|
||||||
this.htmlTree.parent().on("contextmenu", function (event) {
|
|
||||||
if(event.isDefaultPrevented()) return;
|
if(event.isDefaultPrevented()) return;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
_this.onSelect(undefined);
|
if($.isArray(this.currently_selected)) { //Multiselect
|
||||||
_this.showContextMenu(event.pageX, event.pageY);
|
(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 {
|
findClient?(clientId: number) : ClientEntry {
|
||||||
for(let index = 0; index < this.clients.length; index++)
|
for(let index = 0; index < this.clients.length; index++) {
|
||||||
if(this.clients[index].clientId() == clientId) return this.clients[index];
|
if(this.clients[index].clientId() == clientId)
|
||||||
|
return this.clients[index];
|
||||||
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
find_client_by_dbid?(client_dbid: number) : ClientEntry {
|
find_client_by_dbid?(client_dbid: number) : ClientEntry {
|
||||||
for(let index = 0; index < this.clients.length; index++)
|
for(let index = 0; index < this.clients.length; index++) {
|
||||||
if(this.clients[index].properties.client_database_id == client_dbid) return this.clients[index];
|
if(this.clients[index].properties.client_database_id == client_dbid)
|
||||||
|
return this.clients[index];
|
||||||
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelect(entry?: ChannelEntry | ClientEntry | ServerEntry) {
|
private static same_selected_type(a, b) {
|
||||||
this.htmlTree.find(".selected").each(function (idx, e) {
|
if(a instanceof ChannelEntry)
|
||||||
$(e).removeClass("selected");
|
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)
|
if(entry instanceof ChannelEntry)
|
||||||
(entry as ChannelEntry).rootTag().find("> .channelLine").addClass("selected");
|
(entry as ChannelEntry).rootTag().find("> .channelLine").addClass("selected");
|
||||||
|
@ -255,7 +316,136 @@ class ChannelTree {
|
||||||
(entry as ClientEntry).tag.addClass("selected");
|
(entry as ClientEntry).tag.addClass("selected");
|
||||||
else if(entry instanceof ServerEntry)
|
else if(entry instanceof ServerEntry)
|
||||||
(entry as ServerEntry).htmlTag.addClass("selected");
|
(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[] {
|
clientsByGroup(group: Group) : ClientEntry[] {
|
||||||
|
|
|
@ -80,6 +80,8 @@ class AudioController {
|
||||||
private _codecCache: CodecClientCache[] = [];
|
private _codecCache: CodecClientCache[] = [];
|
||||||
private _timeIndex: number = 0;
|
private _timeIndex: number = 0;
|
||||||
private _latencyBufferLength: number = 3;
|
private _latencyBufferLength: number = 3;
|
||||||
|
private _buffer_timeout: NodeJS.Timer;
|
||||||
|
|
||||||
allowBuffering: boolean = true;
|
allowBuffering: boolean = true;
|
||||||
|
|
||||||
//Events
|
//Events
|
||||||
|
@ -125,6 +127,7 @@ class AudioController {
|
||||||
switch (this.playerState) {
|
switch (this.playerState) {
|
||||||
case PlayerState.PREBUFFERING:
|
case PlayerState.PREBUFFERING:
|
||||||
case PlayerState.BUFFERING:
|
case PlayerState.BUFFERING:
|
||||||
|
this.reset_buffer_timeout(true); //Reset timeout, we got a new buffer
|
||||||
if(this.audioCache.length <= this._latencyBufferLength) {
|
if(this.audioCache.length <= this._latencyBufferLength) {
|
||||||
if(this.playerState == PlayerState.BUFFERING) {
|
if(this.playerState == PlayerState.BUFFERING) {
|
||||||
if(this.allowBuffering) break;
|
if(this.allowBuffering) break;
|
||||||
|
@ -183,14 +186,18 @@ class AudioController {
|
||||||
this.playingAudioCache = [];
|
this.playingAudioCache = [];
|
||||||
}
|
}
|
||||||
this.testBufferQueue();
|
this.testBufferQueue();
|
||||||
|
this.playQueue(); //Flush queue
|
||||||
}
|
}
|
||||||
|
|
||||||
private testBufferQueue() {
|
private testBufferQueue() {
|
||||||
if(this.audioCache.length == 0 && this.playingAudioCache.length == 0) {
|
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;
|
this.playerState = PlayerState.BUFFERING;
|
||||||
if(!this.allowBuffering)
|
if(!this.allowBuffering)
|
||||||
console.warn("[Audio] Detected a buffer underflow!");
|
console.warn("[Audio] Detected a buffer underflow!");
|
||||||
|
this.reset_buffer_timeout(true);
|
||||||
} else {
|
} else {
|
||||||
this.playerState = PlayerState.STOPPED;
|
this.playerState = PlayerState.STOPPED;
|
||||||
this.onSilence();
|
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; }
|
get volume() : number { return this._volume; }
|
||||||
|
|
||||||
set volume(val: number) {
|
set volume(val: number) {
|
||||||
|
|
Loading…
Reference in New Issue