Several music bot update
This commit is contained in:
parent
6b80b8a098
commit
af15c098ad
16 changed files with 715 additions and 107 deletions
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
41
css/scroll.css
Normal 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%);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
68
js/main.ts
68
js/main.ts
|
@ -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);
|
||||
|
||||
|
|
66
js/proto.ts
66
js/proto.ts
|
@ -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;
|
||||
}
|
|
@ -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") {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -178,6 +178,7 @@ class VoiceConnection {
|
|||
|
||||
|
||||
createSession() {
|
||||
return;
|
||||
this._ice_use_cache = true;
|
||||
|
||||
let config: RTCConfiguration = {};
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue