Several music bot update

This commit is contained in:
WolverinDEV 2018-08-10 21:30:58 +02:00
parent 6b80b8a098
commit af15c098ad
16 changed files with 715 additions and 107 deletions

View file

@ -4,10 +4,6 @@
width: 400px;
height: 400px;
user-select: none; }
.music-wrapper .container .right:hover {
z-index: 120; }
.music-wrapper .container .right:hover .flip-card {
transform: rotateY(-60deg); }
.music-wrapper .left, .music-wrapper .right {
position: absolute;
width: 50%;
@ -26,8 +22,7 @@
.music-wrapper .left .flip-card img,
.music-wrapper .left .static-card img, .music-wrapper .right .flip-card img,
.music-wrapper .right .static-card img {
width: calc(100% * 2);
height: 100%; }
width: calc(100% * 2); }
.music-wrapper .left .static-card, .music-wrapper .right .static-card {
border-right: none; }
.music-wrapper .left .flip-card, .music-wrapper .right .flip-card {
@ -51,6 +46,8 @@
left: 0; }
.music-wrapper .right {
right: 0; }
.music-wrapper .right:hover .flip-card {
transform: rotateY(-60deg); }
.music-wrapper .controls {
position: absolute;
right: 0;
@ -83,8 +80,7 @@
cursor: pointer;
background-color: #dcdcdc; }
.music-wrapper .controls label span {
background-repeat: no-repeat;
background-position: 16px 42px;
background: no-repeat 16px 42px;
width: 80px;
height: 125px;
display: block;
@ -95,20 +91,32 @@
box-shadow: inset 0px 0px 10px 5px rgba(120, 120, 120, 0.2);
border: 1px solid #fff; }
.music-wrapper .controls .btn-forward span {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAABP0lEQVRoQ+2YwW3DMAxFX4ZNT7knE7QrNB0iWSKZIZ2g9/aYS4oPxECPpUSKFkCdZEAW+Pit729vmHxsJq+fAshWsBQoBTo7UI9QZwO7by8FgBOwA76629mwgYcCD+AbOABHQNfDhhfAUvAFeAFuowi8AVT3HXgDXp/zUJYIgKVgqbAFrpEEkQCqW+dB52IP/ESARAMsNcuh5FRnb4hRAEvdAnC13NEAAnG13AwAV8vNBHCx3GyAbstdC0Cz5a4JoMlyC8Axfeqt/f5Mtf9+a69FgebclA2g5KrUqvSquXlkAujbQWn101z1nxsyABQllE4/PM7PaIBpw9y0cbrJGi1nIvIRarbGbIBua8wEcLHGDABXaxwNMP2vRUvD3Nd6uJB7UZYNC8DSrYi1pUBEVy17lgKWbkWsLQUiumrZ8xeQiV4xsW8UvQAAAABJRU5ErkJggg=="); }
background-size: calc(42px * 2) calc(42px * 2);
margin-left: 10px;
background: url("../../img/music/forward.svg") no-repeat center; }
.music-wrapper .controls .btn-rewind span {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAsElEQVRoQ+2YSw6AIAxEh9vqifS2utAF0YgtpBCS57raz0wHxqTJnzR5/aKB0QiCAAg0TgAKNQ6w+fUaBI5C1lXS/lPVImkrxLhqcgXfSWkABDL+QaHHMrLEku+CCYWg0PtEc7HCFcxBdk2Aq0TOOiiECqFCfk+EH8AP4Ae+9wY/gB8wqCoyiowio8jo2J+7BqHqF1LjyPpVZ8hEA4YhhYaAQOh4DR8HAcOQQkOmR+AEspRaMYlt9skAAAAASUVORK5CYII="); }
background-size: calc(42px * 2) calc(42px * 2);
margin-left: 10px;
background: url("../../img/music/rewind.svg") no-repeat center; }
.music-wrapper .controls .btn-settings span {
background-size: 42px 42px;
background-position: 22px 42px;
background-image: url("../../img/music/settings.svg"); }
background-size: calc(42px * 2) calc(42px * 2);
margin-left: 10px;
background: url("../../img/music/playlist.svg") no-repeat center; }
.music-wrapper .controls-overlay {
position: absolute;
display: block;
top: calc(100% - 40px);
top: calc(100% - 60px);
width: 100%;
height: 40px;
z-index: 100; }
height: 60px;
z-index: 100;
overflow-x: hidden;
transition: width 0.5s cubic-bezier(0.45, 0, 0.55, 1); }
.music-wrapper .controls-overlay .song {
margin-top: 5px;
margin-left: 20px;
height: 15px;
width: 360px;
font-family: "DejaVu Serif", serif; }
.music-wrapper .controls-overlay .timer {
margin-left: 20px;
height: 15px;
@ -125,18 +133,28 @@
stroke: #4c4c4c;
stroke-width: 0.5;
stroke-miterlimit: 10;
cursor: pointer; }
cursor: pointer;
color: white;
mix-blend-mode: difference; }
.music-wrapper .controls-overlay .timer .button.active {
animation: bounce 500ms alternate;
transform: scale(1.3);
transition: transform 150ms; }
.music-wrapper .controls-overlay .timer .button:hover {
animation: bounce 500ms alternate;
transform: scale(1.1);
transition: transform 150ms; }
.music-wrapper .controls-overlay .timer .button.active:hover {
animation: bounce 500ms alternate;
transform: scale(1.5);
transition: transform 150ms; }
.music-wrapper .controls-overlay .timer .timeline * {
border: gray 0;
border-radius: 8px; }
.music-wrapper .controls-overlay .timer .timeline {
width: 90%;
width: calc(100% - 100px);
height: 4px;
float: right;
float: left;
background: #DBE3E3;
position: relative;
align-self: center;
@ -159,6 +177,14 @@
top: -4px;
background: #303030;
cursor: pointer; }
.music-wrapper .controls-overlay .timer .time {
min-width: 38px;
margin-left: 5px;
position: relative;
align-self: center;
font-family: "fantasy"; }
.music-wrapper .controls-overlay.flipped {
width: calc(50% + 7px); }
.music-wrapper.empty {
border: 7px solid #dedede;

View file

@ -1,6 +1,6 @@
{
"version": 3,
"mappings": "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",
"mappings": "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",
"sources": ["info_plate.scss"],
"names": [],
"file": "info_plate.css"

View file

@ -8,16 +8,6 @@ $ease: cubic-bezier(.45, 0, .55, 1);
height: 400px;
user-select: none;
.container {
.right:hover {
.flip-card {
transform: rotateY(-60deg);
}
z-index: 120;
}
}
.left, .right {
position: absolute;
width: 50%;
@ -36,7 +26,7 @@ $ease: cubic-bezier(.45, 0, .55, 1);
img {
width: calc(100% * 2);
height: 100%;
//height: 100%;
}
}
@ -74,6 +64,12 @@ $ease: cubic-bezier(.45, 0, .55, 1);
.right {
right: 0;
}
.right:hover {
.flip-card {
transform: rotateY(-60deg);
}
//z-index: 120;
}
.controls {
position: absolute;
@ -113,8 +109,7 @@ $ease: cubic-bezier(.45, 0, .55, 1);
background-color: #dcdcdc;
span {
background-repeat: no-repeat;
background-position: 16px 42px;
background: no-repeat 16px 42px;
width: 80px;
height: 125px;
display: block;
@ -129,26 +124,42 @@ $ease: cubic-bezier(.45, 0, .55, 1);
border: 1px solid #fff;
}
//https://insidemartialartsmagazine.com.au/images/glyphicons/glyphicons/svg/individual_svg/
.btn-forward span {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAABP0lEQVRoQ+2YwW3DMAxFX4ZNT7knE7QrNB0iWSKZIZ2g9/aYS4oPxECPpUSKFkCdZEAW+Pit729vmHxsJq+fAshWsBQoBTo7UI9QZwO7by8FgBOwA76629mwgYcCD+AbOABHQNfDhhfAUvAFeAFuowi8AVT3HXgDXp/zUJYIgKVgqbAFrpEEkQCqW+dB52IP/ESARAMsNcuh5FRnb4hRAEvdAnC13NEAAnG13AwAV8vNBHCx3GyAbstdC0Cz5a4JoMlyC8Axfeqt/f5Mtf9+a69FgebclA2g5KrUqvSquXlkAujbQWn101z1nxsyABQllE4/PM7PaIBpw9y0cbrJGi1nIvIRarbGbIBua8wEcLHGDABXaxwNMP2vRUvD3Nd6uJB7UZYNC8DSrYi1pUBEVy17lgKWbkWsLQUiumrZ8xeQiV4xsW8UvQAAAABJRU5ErkJggg==");
background-size: calc(42px * 2) calc(42px * 2);
margin-left: 10px;
background: url("../../img/music/forward.svg") no-repeat center;
}
.btn-rewind span {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAsElEQVRoQ+2YSw6AIAxEh9vqifS2utAF0YgtpBCS57raz0wHxqTJnzR5/aKB0QiCAAg0TgAKNQ6w+fUaBI5C1lXS/lPVImkrxLhqcgXfSWkABDL+QaHHMrLEku+CCYWg0PtEc7HCFcxBdk2Aq0TOOiiECqFCfk+EH8AP4Ae+9wY/gB8wqCoyiowio8jo2J+7BqHqF1LjyPpVZ8hEA4YhhYaAQOh4DR8HAcOQQkOmR+AEspRaMYlt9skAAAAASUVORK5CYII=");
background-size: calc(42px * 2) calc(42px * 2);
margin-left: 10px;
background: url("../../img/music/rewind.svg") no-repeat center;
}
.btn-settings span {
background-size: 42px 42px;
background-position: 22px 42px;
background-image: url("../../img/music/settings.svg");
background-size: calc(42px * 2) calc(42px * 2);
margin-left: 10px;
background: url("../../img/music/playlist.svg") no-repeat center;
}
}
.controls-overlay {
position: absolute;
display: block;
top: calc(100% - 40px);
top: calc(100% - 60px);
width: 100%;
height: 40px;
height: 60px;
z-index: 100;
overflow-x: hidden;
transition: width $animtime $ease;
.song {
margin-top: 5px;
margin-left: 20px;
height: 15px;
width: 360px;
font-family: "DejaVu Serif", serif;
}
.timer {
margin-left: 20px;
@ -168,15 +179,30 @@ $ease: cubic-bezier(.45, 0, .55, 1);
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;
}
.button:hover {
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;
}
.timeline * {
border: gray 0;
border-radius: 8px;
@ -184,9 +210,9 @@ $ease: cubic-bezier(.45, 0, .55, 1);
//TODO box SHADOW
.timeline {
width: 90%;
width: calc(100% - 100px);
height: 4px;
float: right;
float: left;
background: #DBE3E3;
position: relative;
align-self: center;
@ -216,10 +242,22 @@ $ease: cubic-bezier(.45, 0, .55, 1);
cursor: pointer;
}
}
.time {
min-width: 38px;
margin-left: 5px;
position: relative;
align-self: center;
font-family: 'fantasy'
}
}
}
.controls-overlay.flipped {
width: calc(50% + 7px);
}
}
.music-wrapper.empty {
border: 7px solid #dedede;
display: flex;

41
css/scroll.css Normal file
View file

@ -0,0 +1,41 @@
.scroll-left {
height: 100%;
overflow: hidden;
position: relative;
}
.scroll-left p {
position: absolute;
width: max-content;
height: 100%;
margin: 0;
text-align: center;
/* Starting position */
-moz-transform:translateX(100%);
-webkit-transform:translateX(100%);
transform:translateX(100%);
/* Apply animation to this element */
-moz-animation: scroll-left 10s linear infinite;
-webkit-animation: scroll-left 10s linear infinite;
animation: scroll-left 10s linear infinite;
}
/* Move it (define the animation) */
@-moz-keyframes scroll-left {
0% { -moz-transform: translateX(100%); }
100% { -moz-transform: translateX(-100%); }
}
@-webkit-keyframes scroll-left {
0% { -webkit-transform: translateX(100%); }
100% { -webkit-transform: translateX(-100%); }
}
@keyframes scroll-left {
0% {
-moz-transform: translateX(100%); /* Browser bug fix */
-webkit-transform: translateX(100%); /* Browser bug fix */
transform: translateX(100%);
}
100% {
-moz-transform: translateX(-100%); /* Browser bug fix */
-webkit-transform: translateX(-100%); /* Browser bug fix */
transform: translateX(-100%);
}
}

View file

@ -30,6 +30,7 @@
<meta charset="UTF-8">
<title>TeaSpeak-Web</title>
<link rel="stylesheet" href="css/scroll.css" type="text/css">
<link rel="stylesheet" href="css/ts/tab.css" type="text/css">
<link rel="stylesheet" href="css/ts/chat.css" type="text/css">
<link rel="stylesheet" href="css/ts/client.css" type="text/css">
@ -177,6 +178,10 @@
</div>
<div id="contextMenu" class="contextMenu"></div>
<div style="background-color:white;">
<div style=" color: white; mix-blend-mode: difference;">And stay alive... XXXXXXX</div>
</div>
<div id="templates"></div>
<div id="music-test"></div>
<div style="height: 100px"></div>

View file

@ -190,7 +190,7 @@ class ServerConnection {
});
}
sendCommand(command: string, data: any = {}, logResult: boolean = true) : Promise<CommandResult> {
sendCommand(command: string, data: any = {}, flags: string[] = [], logResult: boolean = true) : Promise<CommandResult> {
const _this = this;
let result = new Promise<CommandResult>((resolve, failed) => {
let _data = $.isArray(data) ? data : [data];
@ -210,7 +210,8 @@ class ServerConnection {
this._socket.send(this.commandiefy({
"type": "command",
"command": command,
"data": _data
"data": _data,
"flags": flags
}));
});
return new Promise<CommandResult>((resolve, failed) => {
@ -219,10 +220,14 @@ class ServerConnection {
if(ex instanceof CommandResult) {
let res = ex;
if(!res.success) {
if(res.id == 2568) { //Permission error
chat.serverChat().appendError("insufficient client permissions. Failed on permission {}", this._client.permissions.resolveInfo(res.json["failed_permid"] as number).name);
} else {
chat.serverChat().appendError(res.extra_message.length == 0 ? res.message : res.extra_message);
}
}
} else if(typeof(ex) == "string") {
chat.serverChat().appendError("Command execution resuluts in " + ex);
chat.serverChat().appendError("Command execution results in " + ex);
} else {
console.error("Invalid promise result type: " + typeof (ex) + ". Result:");
console.error(ex);
@ -340,6 +345,8 @@ class ConnectionCommandHandler {
this["notifyclientupdated"] = this.handleNotifyClientUpdated;
this["notifyserveredited"] = this.handleNotifyServerEdited;
this["notifyserverupdated"] = this.handleNotifyServerUpdated;
this["notifymusicplayerinfo"] = this.handleNotifyMusicPlayerInfo;
}
handleCommandResult(json) {
@ -775,4 +782,16 @@ class ConnectionCommandHandler {
if(info.currentSelected instanceof ServerEntry)
info.update();
}
handleNotifyMusicPlayerInfo(json) {
json = json[0];
let bot = this.connection._client.channelTree.find_client_by_dbid(json["botid"]);
if(!bot || !(bot instanceof MusicClientEntry)) {
log.warn(LogCategory.CLIENT, "Got music player info for unknown or invalid bot! (ID: %i, Entry: %o)", json["botid"], bot);
return;
}
bot.handlePlayerInfo(json);
}
}

View file

@ -72,14 +72,78 @@ function main() {
}
}
let frame = $("#tmpl_music_frame").renderTag({
song_name: "Hello world song and i don't really know what i should write! XXXXXXXXXXXXX"
}).css("align-self", "center");
/* Play/Pause logic */
console.log(frame.find(".button_play"));
frame.find(".button_play").click(handler => {
frame.find(".button_play").addClass("active");
frame.find(".button_pause").removeClass("active");
});
frame.find(".button_pause").click(handler => {
frame.find(".button_pause").addClass("active");
frame.find(".button_play").removeClass("active");
});
/* Required flip card javascript */
frame.find(".right").mouseenter(() => {
frame.find(".controls-overlay").addClass("flipped");
});
frame.find(".right").mouseleave(() => {
frame.find(".controls-overlay").removeClass("flipped");
});
/* Slider */
frame.find(".timeline .slider").on('mousedown', ev => {
let timeline = frame.find(".timeline");
let time = frame.find(".time");
let slider = timeline.find(".slider");
let slider_old = slider.attr("index");
if(!slider_old || slider_old.length == 0) slider_old = "0";
slider.prop("editing", true);
let move_handler = (event: MouseEvent) => {
let max = timeline.width();
let current = event.pageX - timeline.offset().left - slider.width() / 2;
if(current < 0) current = 0;
else if(current > max) current = max;
time.text(Math.ceil(current / max * 100)); //FIXME!
slider.attr("index", current / max * 100);
slider.css("margin-left", current / max * 100 + "%");
};
$(document).on('mousemove', move_handler as any);
$(document).one('mouseup mouseleave mousedown', event => {
console.log("Event (%i | %s): %o", event.button, event.type, event);
if(event.type == "mousedown" && event.button != 2) return;
$(document).unbind("mousemove", move_handler as any);
if(event.type != "mousedown") {
slider.prop("editing", false);
console.log("Done!");
} else { //Restore old
event.preventDefault();
time.text(slider_old); //FIXME!
slider.attr("index", slider_old);
slider.css("margin-left", slider_old + "%");
}
});
ev.preventDefault();
return false;
});
$("#music-test").replaceWith(frame);
/*
let tag = $("#tmpl_music_frame").renderTag({
//thumbnail: "img/loading_image.svg"
});
tag.find(".timeline .slider").on('mousedown', () => {
});
$("#music-test").replaceWith(tag);

View file

@ -5,6 +5,11 @@ interface Array<T> {
pop_front(): T | undefined;
}
interface JSON {
map_to<T>(object: T, json: any, variables?: string | string[], validator?: (map_field: string, map_value: string) => boolean, variable_direction?: number) : T;
map_field_to<T>(object: T, value: any, field: string) : T;
}
interface JQuery {
render(values?: any) : string;
renderTag(values?: any) : JQuery;
@ -15,12 +20,60 @@ interface JQueryStatic<TElement extends Node = HTMLElement> {
views: any;
}
interface String {
format(...fmt): string;
format(arguments: string[]): string;
}
if(!JSON.map_to) {
JSON.map_to = function<T>(object: T, json: any, variables?: string | string[], validator?: (map_field: string, map_value: string) => boolean, variable_direction?: number) : T {
if(!validator) validator = (a, b) => true;
if(!variables) {
variables = [];
if(!variable_direction || variable_direction == 0) {
for(let field in json)
variables.push(field);
} else if(variable_direction == 1) {
for(let field in object)
variables.push(field);
}
} else if(!Array.isArray(variables)) {
variables = [variables];
}
for(let field of variables) {
if(!json[field]) {
console.trace("Json does not contains %s", field);
continue;
}
if(!validator(field, json[field])) {
console.trace("Validator results in false for %s", field);
continue;
}
JSON.map_field_to(object, json[field], field);
}
return object;
}
}
if(!JSON.map_field_to) {
JSON.map_field_to = function<T>(object: T, value: any, field: string) : T {
let field_type = typeof(object[field]);
if(field_type == "string" || field_type == "object" || field_type == "undefined")
object[field as string] = value;
else if(field_type == "number")
object[field as string] = parseFloat(value);
else if(field_type == "boolean")
object[field as string] = value == "1" || value == "true";
else console.warn("Invalid object type %s for entry %s", field_type, field);
return object;
}
}
if (!Array.prototype.remove) {
Array.prototype.remove = function<T>(elem?: T): boolean {
const index = this.indexOf(elem, 0);
@ -109,3 +162,14 @@ function formatDate(secs: number) : string {
return result.substr(0, result.length - 1);
}
function calculate_width(text: string) : number {
let element = $.spawn("div");
element.text(text)
.css("display", "none")
.css("margin", 0);
$("body").append(element);
let size = element.width();
element.detach();
return size;
}

View file

@ -301,13 +301,27 @@ class ChannelEntry {
name: "Edit channel",
invalidPermission: !channelModify,
callback: () => {
Modals.createChannelModal(this, undefined, this.channelTree.client.permissions, (changes?: ChannelProperties) => {
if(!changes) return;
Modals.createChannelModal(this, undefined, this.channelTree.client.permissions, (changes?, permissions?) => {
if(changes) {
changes["cid"] = this.channelId;
this.channelTree.client.serverConnection.sendCommand("channeledit", changes);
log.info(LogCategory.CHANNEL, "Changed channel properties of channel %s: %o", this.channelName(), changes);
}, permissions => {
//TODO
}
if(permissions && permissions.length > 0) {
let perms = [];
for(let perm of permissions) {
perms.push({
permvalue: perm.value,
permnegated: false,
permskip: false,
permid: perm.type.id
});
}
perms[0]["cid"] = this.channelId;
this.channelTree.client.serverConnection.sendCommand("channeladdperm", perms, ["continueonerror"]);
}
});
}
},
@ -391,13 +405,8 @@ class ChannelEntry {
for(let variable of variables) {
let key = variable.key;
let value = variable.value;
JSON.map_field_to(this.properties, value, variable.key);
if(typeof (this.properties[key]) == "number")
this.properties[key] = parseInt(value);
if(typeof (this.properties[key]) == "boolean")
this.properties[key] = value == "true" || value == "1";
else
this.properties[key] = value;
group.log("Updating property " + key + " = '%s' -> %o", value, this.properties[key]);
if(key == "channel_name") {

View file

@ -13,6 +13,8 @@ enum ClientType {
class ClientProperties {
client_type: ClientType = ClientType.CLIENT_VOICE; //TeamSpeaks type
client_type_exact: ClientType = ClientType.CLIENT_VOICE;
client_database_id: number = 0;
client_version: string = "";
client_platform: string = "";
client_nickname: string = "unknown";
@ -329,12 +331,8 @@ class ClientEntry {
let group = log.group(log.LogType.DEBUG, LogCategory.CLIENT, "Update properties (%i) of %s (%i)", variables.length, this.clientNickName(), this.clientId());
for(let variable of variables) {
if(typeof(this.properties[variable.key]) === "boolean")
this.properties[variable.key] = variable.value == "true" || variable.value == "1";
else if(typeof (this.properties[variable.key]) === "number")
this.properties[variable.key] = parseInt(variable.value);
else
this.properties[variable.key] = variable.value;
JSON.map_field_to(this._properties, variable.value, variable.key);
group.log("Updating client " + this.clientId() + ". Key " + variable.key + " Value: '" + variable.value + "' (" + typeof (this.properties[variable.key]) + ")");
if(variable.key == "client_nickname") {
this.tag.find(".name").text(variable.value);
@ -547,11 +545,37 @@ class LocalClientEntry extends ClientEntry {
}
class MusicClientProperties extends ClientProperties {
music_volume: number = 0;
music_track_id: number = 0;
player_state: number = 0;
player_volume: number = 0;
}
class MusicClientPlayerInfo {
botid: number = 0;
player_state: number = 0;
player_buffered_index: number = 0;
player_replay_index: number = 0;
player_max_index: number = 0;
player_seekable: boolean = false;
player_title: string = "";
player_description: string = "";
song_id: number = 0;
song_url: string = "";
song_invoker: number = 0;
song_loaded: boolean = false;
song_title: string = "";
song_thumbnail: string = "";
song_length: number = 0;
}
class MusicClientEntry extends ClientEntry {
private _info_promise: Promise<MusicClientPlayerInfo>;
private _info_promise_age: number = 0;
private _info_promise_resolve: any;
private _info_promise_reject: any;
constructor(clientId, clientName) {
super(clientId, clientName, new MusicClientProperties());
}
@ -598,4 +622,33 @@ class MusicClientEntry extends ClientEntry {
initializeListener(): void {
super.initializeListener();
}
handlePlayerInfo(json) {
if(json) {
let info = JSON.map_to(new MusicClientPlayerInfo(), json);
if(this._info_promise_resolve)
this._info_promise_resolve(info);
this._info_promise_reject = undefined;
}
if(this._info_promise) {
if(this._info_promise_reject)
this._info_promise_reject("timeout");
this._info_promise = undefined;
this._info_promise_age = undefined;
this._info_promise_reject = undefined;
this._info_promise_resolve = undefined;
}
}
requestPlayerInfo(max_age: number = 1000) : Promise<MusicClientPlayerInfo> {
if(this._info_promise && this._info_promise_age && Date.now() - max_age <= this._info_promise_age) return this._info_promise;
this._info_promise_age = Date.now();
this._info_promise = new Promise<MusicClientPlayerInfo>((resolve, reject) => {
this._info_promise_reject = reject;
this._info_promise_resolve = resolve;
});
this.channelTree.client.serverConnection.sendCommand("musicbotplayerinfo", {botid: this.properties.client_database_id });
return this._info_promise;
}
}

View file

@ -112,6 +112,19 @@ class ClientInfoManager extends InfoManager<ClientEntry> {
updateFrame(client: ClientEntry, html_tag: JQuery<HTMLElement>) {
this.resetIntervals();
html_tag.empty();
let properties = this.buildProperties(client);
let rendered = $("#tmpl_selected_client").renderTag([properties]);
rendered.find("node").each((index, element) => { $(element).replaceWith(properties[$(element).attr("key")]); });
html_tag.append(rendered);
this.registerInterval(setInterval(() => {
html_tag.find(".update_onlinetime").text(formatDate(client.calculateOnlineTime()));
}, 1000));
}
buildProperties(client: ClientEntry) : any {
let properties: any = {};
properties["client_name"] = client.createChatTag()[0];
@ -147,14 +160,7 @@ class ClientInfoManager extends InfoManager<ClientEntry> {
.attr("target", "_blank")
.text(client.properties.client_teaforum_id);
}
let rendered = $("#tmpl_selected_client").renderTag([properties]);
rendered.find("node").each((index, element) => { $(element).replaceWith(properties[$(element).attr("key")]); });
html_tag.append(rendered);
this.registerInterval(setInterval(() => {
html_tag.find(".update_onlinetime").text(formatDate(client.calculateOnlineTime()));
}, 1000));
return properties;
}
}
@ -244,26 +250,198 @@ class ChannelInfoManager extends InfoManager<ChannelEntry> {
}
}
class MusicInfoManager extends InfoManager<MusicClientEntry> {
function format_time(time: number) {
let hours: any = 0, minutes: any = 0, seconds: any = 0;
if(time >= 60 * 60) {
hours = Math.floor(time / (60 * 60));
time -= hours * 60 * 60;
}
if(time >= 60) {
minutes = Math.floor(time / 60);
time -= minutes * 60;
}
seconds = time;
if(hours > 9)
hours = hours.toString();
else if(hours > 0)
hours = '0' + hours.toString();
else hours = '';
if(minutes > 9)
minutes = minutes.toString();
else if(minutes > 0)
minutes = '0' + minutes.toString();
else
minutes = '00';
if(seconds > 9)
seconds = seconds.toString();
else if(seconds > 0)
seconds = '0' + seconds.toString();
else
seconds = '00';
return (hours ? hours + ":" : "") + minutes + ':' + seconds;
}
class MusicInfoManager extends ClientInfoManager {
createFrame<_>(handle: InfoBar<_>, channel: MusicClientEntry, html_tag: JQuery<HTMLElement>) {
super.createFrame(handle, channel, html_tag);
this.updateFrame(channel, html_tag);
}
updateFrame(bot: MusicClientEntry, html_tag: JQuery<HTMLElement>) {
html_tag.append("Im a music bot!");
let frame = $("#tmpl_music_frame" + (bot.properties.music_track_id == 0 ? "_empty" : "")).renderTag({
thumbnail: "img/loading_image.svg"
}).css("align-self", "center");
if(bot.properties.music_track_id == 0) {
this.resetIntervals();
html_tag.empty();
let properties = super.buildProperties(bot);
{ //Render info frame
if(bot.properties.player_state != 2 && bot.properties.player_state != 3) {
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;
bot.requestPlayerInfo().then(info => {
let timestamp = Date.now();
console.log(info);
let _frame = $("#tmpl_music_frame").renderTag({
song_name: info.player_title ? info.player_title :
info.song_url ? info.song_url : "No title or url",
thumbnail: info.song_thumbnail && info.song_thumbnail.length > 0 ? info.song_thumbnail : undefined
}).css("align-self", "center");
frame.replaceWith(_frame);
frame = _frame;
/* Play/Pause logic */
{
let button_play = frame.find(".button_play");
let button_pause = frame.find(".button_pause");
frame.find(".button_play").click(handler => {
if(!button_play.hasClass("active")) {
this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
botid: bot.properties.client_database_id,
action: 1
}).then(updated => this.triggerUpdate()).catch(error => {
createErrorModal("Failed to execute play", "Failed to execute play.<br>{}".format(error)).open();
this.triggerUpdate();
});
}
button_pause.removeClass("active");
button_play.addClass("active");
});
frame.find(".button_pause").click(handler => {
if(!button_pause.hasClass("active")) {
this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
botid: bot.properties.client_database_id,
action: 2
}).then(updated => this.triggerUpdate()).catch(error => {
createErrorModal("Failed to execute pause", "Failed to execute pause.<br>{}".format(error)).open();
this.triggerUpdate();
});
}
button_play.removeClass("active");
button_pause.addClass("active");
});
if(bot.properties.player_state == 2)
button_play.addClass("active");
else if(bot.properties.player_state == 3)
button_play.addClass("active");
}
html_tag.append(frame);
/* Required flip card javascript */
frame.find(".right").mouseenter(() => {
frame.find(".controls-overlay").addClass("flipped");
});
frame.find(".right").mouseleave(() => {
frame.find(".controls-overlay").removeClass("flipped");
});
/* Slider */
frame.find(".timeline .slider").on('mousedown', ev => {
let timeline = frame.find(".timeline");
let time = frame.find(".time");
let slider = timeline.find(".slider");
let slider_old = slider.css("margin-left");
let time_max = parseInt(timeline.attr("time-max"));
slider.prop("editing", true);
let target_timestamp = 0;
let move_handler = (event: MouseEvent) => {
let max = timeline.width();
let current = event.pageX - timeline.offset().left - slider.width() / 2;
if(current < 0) current = 0;
else if(current > max) current = max;
target_timestamp = current / max * time_max;
time.text(format_time(Math.floor(target_timestamp / 1000)));
slider.css("margin-left", current / max * 100 + "%");
};
let finish_handler = event => {
console.log("Event (%i | %s): %o", event.button, event.type, event);
if(event.type == "mousedown" && event.button != 2) return;
$(document).unbind("mousemove", move_handler as any);
$(document).unbind("mouseup mouseleave mousedown", finish_handler as any);
if(event.type != "mousedown") {
slider.prop("editing", false);
slider.prop("edited", true);
let current_timestamp = info.player_replay_index + Date.now() - timestamp;
this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
botid: bot.properties.client_database_id,
action: current_timestamp > target_timestamp ? 5 : 4,
units: current_timestamp < target_timestamp ? target_timestamp - current_timestamp : current_timestamp - target_timestamp
}).then(() => this.triggerUpdate()).catch(error => {
slider.prop("edited", false);
});
} else { //Restore old
event.preventDefault();
slider.css("margin-left", slider_old + "%");
}
};
$(document).on('mousemove', move_handler as any);
$(document).on('mouseup mouseleave mousedown', finish_handler as any);
ev.preventDefault();
return false;
});
{
frame.find(".timeline").attr("time-max", info.player_max_index);
let timeline = frame.find(".timeline");
let time_bar = timeline.find(".played");
let slider = timeline.find(".slider");
let player_time = _frame.find(".player_time");
let update_handler = () => {
let time_index = info.player_replay_index + Date.now() - timestamp;
time_bar.css("width", time_index / info.player_max_index * 100 + "%");
if(!slider.prop("editing") && !slider.prop("edited")) {
player_time.text(format_time(Math.floor(time_index / 1000)));
slider.css("margin-left", time_index / info.player_max_index * 100 + "%");
}
};
this.registerInterval(setInterval(update_handler, 1000));
update_handler();
}
});
}
}
let rendered = $("#tmpl_selected_music").renderTag([properties]);
rendered.find("node").each((index, element) => { $(element).replaceWith(properties[$(element).attr("key")]); });
html_tag.append(rendered);
}
available<V>(object: V): boolean {

View file

@ -1,7 +1,7 @@
/// <reference path="../../utils/modal.ts" />
namespace Modals {
export function createChannelModal(channel: ChannelEntry | undefined, parent: ChannelEntry | undefined, permissions: PermissionManager, callback: (ChannelProperties?: ChannelProperties) => void, callback_permission: (_: PermissionValue[]) => any) {
export function createChannelModal(channel: ChannelEntry | undefined, parent: ChannelEntry | undefined, permissions: PermissionManager, callback: (properties?: ChannelProperties, permissions?: PermissionValue[]) => any) {
let properties: ChannelProperties = { } as ChannelProperties; //The changes properties
const modal = createModal({
header: channel ? "Edit channel" : "Create channel",
@ -51,8 +51,7 @@ namespace Modals {
console.log("Updated permissions %o", updated);
}).click(() => {
modal.close();
callback(properties); //First may create the channel
callback_permission(updated);
callback(properties, updated); //First may create the channel
});
modal.htmlTag.find(".button_cancel").click(() => {

View file

@ -84,12 +84,8 @@ class ServerEntry {
let group = log.group(log.LogType.DEBUG, LogCategory.SERVER, "Update properties (%i)", variables.length);
for(let variable of variables) {
if(typeof(this.properties[variable.key]) === "boolean")
this.properties[variable.key] = variable.value == "true" || variable.value == "1";
else if(typeof (this.properties[variable.key]) === "number")
this.properties[variable.key] = parseInt(variable.value);
else
this.properties[variable.key] = variable.value;
JSON.map_field_to(this.properties, variable.value, variable.key);
group.log("Updating server " + this.properties.virtualserver_name + ". Key " + variable.key + " Value: '" + variable.value + "' (" + typeof (this.properties[variable.key]) + ")");
if(variable.key == "virtualserver_name") {
this.htmlTag.find(".name").text(variable.value);

View file

@ -129,6 +129,13 @@ class ChannelTree {
return undefined;
}
find_channel_by_name(name: string, parent?: ChannelEntry, force_parent: boolean = true) : ChannelEntry | undefined {
for(let index = 0; index < this.channels.length; index++)
if(this.channels[index].channelName() == name && (!force_parent || parent == this.channels[index].parent))
return this.channels[index];
return undefined;
}
moveChannel(channel: ChannelEntry, prevChannel: ChannelEntry, parent: ChannelEntry) {
if(prevChannel != null && prevChannel.parent != parent) {
console.error("Invalid channel move (different parents! (" + prevChannel.parent + "|" + parent + ")");
@ -205,10 +212,16 @@ class ChannelTree {
}
}
findClient(clientId) : ClientEntry {
findClient?(clientId: number) : ClientEntry {
for(let index = 0; index < this.clients.length; index++)
if(this.clients[index].clientId() == clientId) return this.clients[index];
return null;
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];
return undefined;
}
onSelect(entry?: ChannelEntry | ClientEntry | ServerEntry) {
@ -255,13 +268,35 @@ class ChannelTree {
}
spawnCreateChannel(parent?: ChannelEntry) {
Modals.createChannelModal(undefined, parent, this.client.permissions, (properties?: ChannelProperties) => {
Modals.createChannelModal(undefined, parent, this.client.permissions, (properties?, permissions?) => {
if(!properties) return;
properties["cpid"] = parent ? parent.channelId : 0;
log.debug(LogCategory.CHANNEL, "Creating new channel with properties: %o", properties);
this.client.serverConnection.sendCommand("channelcreate", properties);
}, permissions => {
//TODO
log.debug(LogCategory.CHANNEL, "Creating a new channel.\nProperties: %o\nPermissions: %o", properties);
this.client.serverConnection.sendCommand("channelcreate", properties).then(() => {
let channel = this.find_channel_by_name(properties.channel_name, parent, true);
if(!channel) {
log.error(LogCategory.CHANNEL, "Failed to resolve channel after creation. Could not apply permissions!");
return;
}
if(permissions && permissions.length > 0) {
let perms = [];
for(let perm of permissions) {
perms.push({
permvalue: perm.value,
permnegated: false,
permskip: false,
permid: perm.type.id
});
}
perms[0]["cid"] = channel.channelId;
return this.client.serverConnection.sendCommand("channeladdperm", perms, ["continueonerror"]).then(() => new Promise<ChannelEntry>(resolve => { resolve(channel); }));
}
return new Promise<ChannelEntry>(resolve => { resolve(channel); })
}).then(channel => {
chat.serverChat().appendMessage("Channel {} successfully created!", true, channel.createChatTag());
});
});
}
}

View file

@ -178,6 +178,7 @@ class VoiceConnection {
createSession() {
return;
this._ice_use_cache = true;
let config: RTCConfiguration = {};

View file

@ -79,6 +79,7 @@
Regular needed powers:
<table class="channel_perm_tbl">
<tr><td class="key">Join:</td><td><input type="number" min="0" value="0" class="value" permission="i_channel_needed_join_power"></td></tr>
<tr><td class="key">View:</td><td><input type="number" min="0" value="0" class="value" permission="i_channel_needed_view_power"></td></tr>
<tr><td class="key">Subscribe:</td><td><input type="number" min="0" value="0" class="value" permission="i_channel_needed_subscribe_power"></td></tr>
<tr><td class="key">Desc. view:</td><td><input type="number" min="0" value="0" class="value" permission="i_channel_needed_description_view_power"></td></tr>
<tr><td class="key">Modify:</td><td><input type="number" min="0" value="0" class="value" permission="i_channel_needed_modify_power"></td></tr>
@ -86,7 +87,7 @@
</table>
</div>
<div>
File transfare needed powers:
File transfer needed powers:
<table class="channel_perm_tbl">
<tr><td class="key">Join:</td><td><input type="number" min="0" value="0" class="value"></td></tr>
<tr><td class="key">Subscribe:</td><td><input type="number" min="0" value="0" class="value"></td></tr>
@ -96,7 +97,6 @@
</table>
</div>
</div>
TODO Implement!
</x-content>
</x-entry>
<x-entry>
@ -244,8 +244,17 @@
<script id="tmpl_music_frame" type="text/html">
<!-- First we want to define some variables if not defined yet. -->
{{if !thumbnail}}
{{* data.thumbnail = "img/music/no_thumbnail.svg"; }}
{{*
data.thumbnail = "img/music/no_thumbnail.svg";
}}
{{/if}}
{{*
if(!data.song_max_width) data.song_max_width = 300;
let width = calculate_width(data.song_name);
if(width > data.song_max_width)
data.song_use_scroller = true;
}}
<div class="music-wrapper">
<div class="container">
@ -268,7 +277,7 @@
<div class="controls-overlay">
<div class="timer">
<div>
<svg class="button" x="0px" y="0px" viewBox="0 0 4.5 6.9" style="enable-background:new 0 0 4.5 6.9;">
<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"/>
@ -276,7 +285,7 @@
</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" x="0px" y="0px" viewBox="0 0 4.5 6.9" style="enable-background:new 0 0 4.5 6.9;">
<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"/>
@ -293,6 +302,16 @@
<div class="played"></div>
<div class="slider"></div>
</div>
<div class="time player_time">--:--</div>
</div>
<div class="song">
{{if song_use_scroller}}
<div class="scroll-left">
<p class="name">{{>song_name}}</p>
</div>
{{else}}
<div class="name" style="">{{>song_name}}</div>
{{/if}}
</div>
</div>
</div>
@ -398,6 +417,67 @@
</div>
{{/if}}
</script>
<script id="tmpl_selected_music" type="text/html">
<table class="select_info_table">
<tr>
<td>Name:</td>
<td><node key="client_name"/></td>
</tr>
{{if property_client_description.length > 0}}
<tr>
<td>Description:</td>
<td>{{>property_client_description}}</td>
</tr>
{{/if}}
<tr>
<td>Livetime:</td>
<td class="update_onlinetime">{{:client_onlinetime}}</td>
</tr>
<!-- player_volume -->
<tr>
<td>Remote Volume:</td>
<td>{{*: data.property_player_volume * 100 }}%</td>
</tr>
<tr>
<td>Local Volume:</td>
<td>{{>sound_volume}}%</td>
</tr>
<!-- TODO: Created by -->
</table>
<!-- Server groups -->
<div style="display: flex; flex-direction: column;">
<div style="display:flex;margin-top:5px;align-items:center">
<div class="icon client-permission_server_groups"></div>
<div style="margin-left:3px;font-weight:bold">Server groups:</div>
</div>
{{for group_server}}
<div style="display: flex; margin-top: 1px; margin-left: 10px; align-items: center;">
<node key="group_{{:group_id}}_icon"/>
<div style="margin-left: 3px">{{:group_name}}</div>
</div>
{{/for}}
</div>
<!-- Channel group -->
<div style="display: flex; flex-direction: column; margin-bottom: 20px">
<div style="display:flex;margin-top:10px;align-items:center">
<div class="icon client-permission_channel"></div>
<div style="margin-left:3px;font-weight:bold">Channel group:</div>
</div>
<div style="display: flex; margin-top: 1px; margin-left: 10px; align-items: center;">
<node key="group_{{:group_channel}}_icon"/>
<div style="margin-left: 3px">{{*: data["group_" + data.group_channel + "_name"]}}</div>
</div>
</div>
<div>
<node key="music_player"/>
</div>
</script>
<script id="tmpl_selected_server" type="text/html">
<table class="select_info_table">
<tr>