Property and channel tree improvements

This commit is contained in:
WolverinDEV 2018-04-30 23:57:21 +02:00
parent 09229a8e9d
commit 4689cdd4e0
21 changed files with 1056 additions and 235 deletions

View file

@ -0,0 +1,249 @@
$animtime: .5s;
$ease: cubic-bezier(.45, 0, .55, 1);
.music-wrapper {
display: flex;
position: relative;
width: 400px;
height: 400px;
user-select: none;
.container {
.right:hover {
.flip-card {
transform: rotateY(-60deg);
}
z-index: 120;
}
}
.left, .right {
position: absolute;
width: 50%;
height: 100%;
perspective-origin: 50% 50%;
perspective: 1200px;
.flip-card,
.static-card {
background: white;
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
border: 7px solid #dedede;
img {
width: calc(100% * 2);
height: 100%;
}
}
.static-card {
border-right: none;
}
.flip-card {
border-left: none;
transform-origin: 0% 50%;
transition: transform $animtime $ease;
transform: rotateY(0);
&:before {
position: absolute;
content: '';
width: 100%;
height: 100%;
top: 0;
right: -20px;
box-shadow: 29px 0px 52px 6px rgba(186, 186, 186, 1);
}
img {
position: absolute;
background-color: #fff;
right: 0;
}
}
}
.left {
left: 0;
}
.right {
right: 0;
}
.controls {
position: absolute;
right: 0;
width: 50%;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
cursor: pointer;
&:after {
position: absolute;
content: '';
right: 0;
top: 0;
width: 100%;
height: 100%;
box-shadow: inset 20px 0px 37px -10px rgba(0, 0, 0, 0.75);
pointer-events: none;
transition: width $animtime $ease;
}
input[type="radio"] {
position: absolute;
left: -1000px;
}
label {
flex-grow: 1;
display: block;
width: 100%;
border-top: 1px #e6e6e6 solid;
border-bottom: 1px #9c9c9c solid;
box-sizing: border-box;
cursor: pointer;
background-color: #dcdcdc;
span {
background-repeat: no-repeat;
background-position: 16px 42px;
width: 80px;
height: 125px;
display: block;
pointer-events: none;
}
}
input:checked + label,
label:active {
background-color: #BCBCBC;
box-shadow: inset 0px 0px 10px 5px rgba(120, 120, 120, 0.2);
border: 1px solid #fff;
}
.btn-forward span {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAABP0lEQVRoQ+2YwW3DMAxFX4ZNT7knE7QrNB0iWSKZIZ2g9/aYS4oPxECPpUSKFkCdZEAW+Pit729vmHxsJq+fAshWsBQoBTo7UI9QZwO7by8FgBOwA76629mwgYcCD+AbOABHQNfDhhfAUvAFeAFuowi8AVT3HXgDXp/zUJYIgKVgqbAFrpEEkQCqW+dB52IP/ESARAMsNcuh5FRnb4hRAEvdAnC13NEAAnG13AwAV8vNBHCx3GyAbstdC0Cz5a4JoMlyC8Axfeqt/f5Mtf9+a69FgebclA2g5KrUqvSquXlkAujbQWn101z1nxsyABQllE4/PM7PaIBpw9y0cbrJGi1nIvIRarbGbIBua8wEcLHGDABXaxwNMP2vRUvD3Nd6uJB7UZYNC8DSrYi1pUBEVy17lgKWbkWsLQUiumrZ8xeQiV4xsW8UvQAAAABJRU5ErkJggg==");
}
.btn-rewind span {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAsElEQVRoQ+2YSw6AIAxEh9vqifS2utAF0YgtpBCS57raz0wHxqTJnzR5/aKB0QiCAAg0TgAKNQ6w+fUaBI5C1lXS/lPVImkrxLhqcgXfSWkABDL+QaHHMrLEku+CCYWg0PtEc7HCFcxBdk2Aq0TOOiiECqFCfk+EH8AP4Ae+9wY/gB8wqCoyiowio8jo2J+7BqHqF1LjyPpVZ8hEA4YhhYaAQOh4DR8HAcOQQkOmR+AEspRaMYlt9skAAAAASUVORK5CYII=");
}
.btn-settings span {
background-size: 42px 42px;
background-position: 22px 42px;
background-image: url("../../img/music/settings.svg");
}
}
.controls-overlay {
position: absolute;
display: block;
top: calc(100% - 40px);
width: 100%;
height: 40px;
z-index: 100;
.timer {
margin-left: 20px;
height: 15px;
z-index: 200;
width: 360px;
display: inline-flex;
justify-content: space-between;
vertical-align: center;
.button {
width: 10px;
height: 12px;
margin-left: 2px;
fill: none;
stroke: #4c4c4c;;
stroke-width: 0.5;
stroke-miterlimit: 10;
cursor: pointer;
//box-shadow: 20px 20px 20px 20px rgb(186, 0, 12);
}
.button:hover {
animation: bounce 500ms alternate;
transform: scale(1.1);
transition: transform 150ms;
}
.timeline * {
border: gray 0;
border-radius: 8px;
}
//TODO box SHADOW
.timeline {
width: 90%;
height: 4px;
float: right;
background: #DBE3E3;
position: relative;
align-self: center;
border: gray 0;
border-radius: 8px;
.buffered {
position: absolute;
width: 80%;
height: 100%;
background: #a0a0a0;
}
.played {
position: absolute;
width: 60%;
height: 100%;
background: #1fe2e3;
}
.slider {
position: absolute;
width: 4px;
height: 12px;
top: -4px;
background: #303030;
cursor: pointer;
}
}
}
}
}
.music-wrapper.empty {
border: 7px solid #dedede;
display: flex;
flex-direction: column;
background: white;
}
.music-wrapper.empty img {
margin: 5px;
-webkit-animation: rotation 5s infinite linear;
}
@-webkit-keyframes rotation {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(359deg);
}
}
.music-wrapper.empty a {
text-align: center;
margin: 5px;
margin-top: 20px;
font-size: 20px;
font-family: Arial;
}

View file

@ -30,11 +30,12 @@
#chat .message { #chat .message {
width: 100%; width: 100%;
display: inline-flex; display: inline-block;
} }
#chat .message *{ #chat .message *{
display: inline-flex; display: inline-block;
vertical-align: top;
} }
#chat .chats { #chat .chats {

View file

@ -14,6 +14,7 @@ class AudioController {
this._volume = 1; this._volume = 1;
this._codecCache = []; this._codecCache = [];
this._timeIndex = 0; this._timeIndex = 0;
this._latencyBufferLength = 3;
this.allowBuffering = true; this.allowBuffering = true;
this.speakerContext = AudioController.globalContext; this.speakerContext = AudioController.globalContext;
this.onSpeaking = function () { }; this.onSpeaking = function () { };
@ -37,7 +38,7 @@ class AudioController {
playBuffer(buffer) { playBuffer(buffer) {
if (buffer.sampleRate != this.speakerContext.sampleRate) if (buffer.sampleRate != this.speakerContext.sampleRate)
console.warn("[AudioController] Source sample rate isn't equal to playback sample rate! (" + buffer.sampleRate + " | " + this.speakerContext.sampleRate + ")"); console.warn("[AudioController] Source sample rate isn't equal to playback sample rate! (" + buffer.sampleRate + " | " + this.speakerContext.sampleRate + ")");
this.applayVolume(buffer); this.applyVolume(buffer);
this.audioCache.push(buffer); this.audioCache.push(buffer);
if (this.playerState == PlayerState.STOPPED || this.playerState == PlayerState.STOPPING) { if (this.playerState == PlayerState.STOPPED || this.playerState == PlayerState.STOPPING) {
console.log("[Audio] Starting new playback"); console.log("[Audio] Starting new playback");
@ -47,7 +48,7 @@ class AudioController {
switch (this.playerState) { switch (this.playerState) {
case PlayerState.PREBUFFERING: case PlayerState.PREBUFFERING:
case PlayerState.BUFFERING: case PlayerState.BUFFERING:
if (this.audioCache.length < 3) { if (this.audioCache.length <= this._latencyBufferLength) {
if (this.playerState == PlayerState.BUFFERING) { if (this.playerState == PlayerState.BUFFERING) {
if (this.allowBuffering) if (this.allowBuffering)
break; break;
@ -119,9 +120,9 @@ class AudioController {
return; return;
this._volume = val; this._volume = val;
for (let buffer of this.audioCache) for (let buffer of this.audioCache)
this.applayVolume(buffer); this.applyVolume(buffer);
} }
applayVolume(buffer) { applyVolume(buffer) {
for (let channel = 0; channel < buffer.numberOfChannels; channel++) { for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
let data = buffer.getChannelData(channel); let data = buffer.getChannelData(channel);
for (let sample = 0; sample < data.length; sample++) { for (let sample = 0; sample < data.length; sample++) {
@ -721,7 +722,6 @@ class CodecPool {
else { else {
this.entries[index].instance.initialise().then((flag) => { this.entries[index].instance.initialise().then((flag) => {
//TODO test success flag //TODO test success flag
console.error(flag);
this.ownCodec(clientId, false).then(resolve).catch(reject); this.ownCodec(clientId, false).then(resolve).catch(reject);
}).catch(error => { }).catch(error => {
console.error("Could not initialize codec!\nError: %o", error); console.error("Could not initialize codec!\nError: %o", error);
@ -1409,7 +1409,7 @@ class ChannelEntry {
} }
createChatTag(braces = false) { createChatTag(braces = false) {
let tag = $.spawn("div"); let tag = $.spawn("div");
tag.css("display", "table"); tag.css("display", "inline-block");
tag.css("cursor", "pointer"); tag.css("cursor", "pointer");
tag.css("font-weight", "bold"); tag.css("font-weight", "bold");
tag.css("color", "darkblue"); tag.css("color", "darkblue");
@ -1417,10 +1417,15 @@ class ChannelEntry {
tag.text("\"" + this.channelName() + "\""); tag.text("\"" + this.channelName() + "\"");
else else
tag.text(this.channelName()); tag.text(this.channelName());
tag.attr("oncontextmenu", "chat_channel_contextmenu(this, ...arguments);"); tag.contextmenu(event => {
if (event.isDefaultPrevented())
return;
event.preventDefault();
this.showContextMenu(event.pageX, event.pageY);
});
tag.attr("channelId", this.channelId); tag.attr("channelId", this.channelId);
tag.attr("channelName", this.channelName()); tag.attr("channelName", this.channelName());
return tag.wrap("<p/>").parent(); return tag;
} }
channelType() { channelType() {
if (this.properties.channel_flag_permanent == true) if (this.properties.channel_flag_permanent == true)
@ -1546,6 +1551,8 @@ class ClientProperties {
this.client_input_hardware = false; this.client_input_hardware = false;
this.client_input_muted = false; this.client_input_muted = false;
this.client_is_channel_commander = false; this.client_is_channel_commander = false;
this.client_teaforum_id = 0;
this.client_teaforum_name = "";
} }
} }
class ClientEntry { class ClientEntry {
@ -1676,8 +1683,16 @@ class ClientEntry {
type: MenuEntryType.ENTRY, type: MenuEntryType.ENTRY,
icon: "client-ban_client", icon: "client-ban_client",
name: "Ban client", name: "Ban client",
disabled: true, invalidPermission: !this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
callback: () => { } callback: () => {
Modals.spawnBanClient(this.properties.client_nickname, (duration, reason) => {
this.channelTree.client.serverConnection.sendCommand("banclient", {
uid: this.properties.client_unique_identifier,
banreason: reason,
time: duration
});
});
}
}, MenuEntry.HR(), { }, MenuEntry.HR(), {
type: MenuEntryType.ENTRY, type: MenuEntryType.ENTRY,
icon: "client-volume", icon: "client-volume",
@ -1708,19 +1723,30 @@ class ClientEntry {
} }
static chatTag(id, name, uid, braces = false) { static chatTag(id, name, uid, braces = false) {
let tag = $.spawn("div"); let tag = $.spawn("div");
tag.css("cursor", "pointer"); tag.css("cursor", "pointer")
tag.css("font-weight", "bold"); .css("font-weight", "bold")
tag.css("color", "darkblue"); .css("color", "darkblue")
tag.css("display", "table"); .css("display", "inline-block")
.css("margin", 0);
if (braces) if (braces)
tag.text("\"" + name + "\""); tag.text("\"" + name + "\"");
else else
tag.text(name); tag.text(name);
tag.attr("oncontextmenu", "chat_client_contextmenu(this, ...arguments);"); tag.contextmenu(event => {
if (event.isDefaultPrevented())
return;
event.preventDefault();
let client = globalClient.channelTree.findClient(id);
if (!client)
return;
if (client.properties.client_unique_identifier != uid)
return;
client.showContextMenu(event.pageX, event.pageY);
});
tag.attr("clientId", id); tag.attr("clientId", id);
tag.attr("clientUid", uid); tag.attr("clientUid", uid);
tag.attr("clientName", name); tag.attr("clientName", name);
return tag.wrap("<p/>").parent(); return tag;
} }
createChatTag(braces = false) { createChatTag(braces = false) {
return ClientEntry.chatTag(this.clientId(), this.clientNickName(), this.clientUid(), braces); return ClientEntry.chatTag(this.clientId(), this.clientNickName(), this.clientUid(), braces);
@ -1865,7 +1891,7 @@ class ClientEntry {
this.audioController = undefined; this.audioController = undefined;
} }
calculateOnlineTime() { calculateOnlineTime() {
return new Date().getTime() / 1000 - this.properties.client_lastconnected; return Date.now() / 1000 - this.properties.client_lastconnected;
} }
avatarId() { avatarId() {
function str2ab(str) { function str2ab(str) {
@ -2988,30 +3014,11 @@ if (typeof (customElements) !== "undefined") {
customElements.define('x-properties', X_Properties, { extends: 'div' }); customElements.define('x-properties', X_Properties, { extends: 'div' });
customElements.define('x-property', X_Property, { extends: 'div' }); customElements.define('x-property', X_Property, { extends: 'div' });
} }
class Settings { class StaticSettings {
constructor() { static get instance() {
this.cacheGlobal = {}; if (!this._instance)
this.cacheServer = {}; this._instance = new StaticSettings(true);
this.updated = false; return this._instance;
this._staticPropsTag = $("#properties");
this.cacheGlobal = JSON.parse(localStorage.getItem("settings.global"));
if (!this.cacheGlobal)
this.cacheGlobal = {};
const _this = this;
this.saveWorker = setInterval(() => {
if (_this.updated)
_this.save();
}, 5 * 1000);
this.initializeStatic();
}
initializeStatic() {
location.search.substr(1).split("&").forEach(part => {
let item = part.split("=");
$("<x-property></x-property>")
.attr("key", item[0])
.attr("value", item[1])
.appendTo(this._staticPropsTag);
});
} }
static transformStO(input, _default) { static transformStO(input, _default) {
if (typeof input === "undefined") if (typeof input === "undefined")
@ -3037,24 +3044,68 @@ class Settings {
return undefined; return undefined;
return JSON.stringify(input); return JSON.stringify(input);
} }
constructor(_reserved = undefined) {
if (_reserved && !StaticSettings._instance) {
this._staticPropsTag = $("#properties");
this.initializeStatic();
}
else {
this._handle = StaticSettings.instance;
}
}
initializeStatic() {
location.search.substr(1).split("&").forEach(part => {
let item = part.split("=");
$("<x-property></x-property>")
.attr("key", item[0])
.attr("value", item[1])
.appendTo(this._staticPropsTag);
});
}
static(key, _default) {
if (this._handle)
return this._handle.static(key, _default);
let result = this._staticPropsTag.find("[key='" + key + "']");
console.log("%d | %o", result.length, result);
return StaticSettings.transformStO(result.length > 0 ? decodeURIComponent(result.last().attr("value")) : undefined, _default);
}
deleteStatic(key) {
if (this._handle) {
this._handle.deleteStatic(key);
return;
}
let result = this._staticPropsTag.find("[key='" + key + "']");
if (result.length != 0)
result.detach();
}
}
class Settings extends StaticSettings {
constructor() {
super();
this.cacheGlobal = {};
this.cacheServer = {};
this.updated = false;
this.cacheGlobal = JSON.parse(localStorage.getItem("settings.global"));
if (!this.cacheGlobal)
this.cacheGlobal = {};
this.saveWorker = setInterval(() => {
if (this.updated)
this.save();
}, 5 * 1000);
}
global(key, _default) { global(key, _default) {
let result = this.cacheGlobal[key]; let result = this.cacheGlobal[key];
return Settings.transformStO(result, _default); return StaticSettings.transformStO(result, _default);
} }
server(key, _default) { server(key, _default) {
let result = this.cacheServer[key]; let result = this.cacheServer[key];
return Settings.transformStO(result, _default); return StaticSettings.transformStO(result, _default);
}
static(key, _default) {
let result = this._staticPropsTag.find("[key='" + key + "']");
console.log("%d | %o", result.length, result);
return Settings.transformStO(result.length > 0 ? decodeURIComponent(result.last().attr("value")) : undefined, _default);
} }
changeGlobal(key, value) { changeGlobal(key, value) {
if (this.cacheGlobal[key] == value) if (this.cacheGlobal[key] == value)
return; return;
this.updated = true; this.updated = true;
this.cacheGlobal[key] = Settings.transformOtS(value); this.cacheGlobal[key] = StaticSettings.transformOtS(value);
if (Settings.UPDATE_DIRECT) if (Settings.UPDATE_DIRECT)
this.save(); this.save();
} }
@ -3062,7 +3113,7 @@ class Settings {
if (this.cacheServer[key] == value) if (this.cacheServer[key] == value)
return; return;
this.updated = true; this.updated = true;
this.cacheServer[key] = Settings.transformOtS(value); this.cacheServer[key] = StaticSettings.transformOtS(value);
if (Settings.UPDATE_DIRECT) if (Settings.UPDATE_DIRECT)
this.save(); this.save();
} }
@ -3090,11 +3141,6 @@ class Settings {
let global = JSON.stringify(this.cacheGlobal); let global = JSON.stringify(this.cacheGlobal);
localStorage.setItem("settings.global", global); localStorage.setItem("settings.global", global);
} }
deleteStatic(key) {
let result = this._staticPropsTag.find("[key='" + key + "']");
if (result.length != 0)
result.detach();
}
} }
Settings.KEY_DISABLE_CONTEXT_MENU = "disableContextMenu"; Settings.KEY_DISABLE_CONTEXT_MENU = "disableContextMenu";
Settings.KEY_DISABLE_UNLOAD_DIALOG = "disableUnloadDialog"; Settings.KEY_DISABLE_UNLOAD_DIALOG = "disableUnloadDialog";
@ -3108,12 +3154,16 @@ class InfoBar {
this._htmlTag = htmlTag; this._htmlTag = htmlTag;
} }
createInfoTable(infos) { createInfoTable(infos) {
let table = $("<table/>"); let table = $.spawn("table");
for (let e in infos) { for (let key in infos) {
console.log("Display info " + e); console.log("Display info " + key);
let entry = $("<tr/>"); let entry = $.spawn("tr");
entry.append("<td class='info_key'>" + e + ":</td>"); entry.append($.spawn("td").addClass("info_key").html(key + ":"));
entry.append("<td>" + infos[e] + "</td>"); let value = $.spawn("td");
console.log(infos[key]);
console.log(MessageHelper.formatElement(infos[key]));
MessageHelper.formatElement(infos[key]).forEach(e => e.appendTo(value));
entry.append(value);
table.append(entry); table.append(entry);
} }
return table; return table;
@ -3187,7 +3237,7 @@ class InfoBar {
else if (this._currentSelected instanceof ChannelEntry) { else if (this._currentSelected instanceof ChannelEntry) {
let props = this._currentSelected.properties; let props = this._currentSelected.properties;
this._htmlTag.append(this.createInfoTable({ this._htmlTag.append(this.createInfoTable({
"Name": this._currentSelected.createChatTag().html(), "Name": this._currentSelected.createChatTag(),
"Topic": this._currentSelected.properties.channel_topic, "Topic": this._currentSelected.properties.channel_topic,
"Codec": this._currentSelected.properties.channel_codec, "Codec": this._currentSelected.properties.channel_codec,
"Codec Quality": this._currentSelected.properties.channel_codec_quality, "Codec Quality": this._currentSelected.properties.channel_codec_quality,
@ -3198,19 +3248,22 @@ class InfoBar {
})); }));
} }
else if (this._currentSelected instanceof ClientEntry) { else if (this._currentSelected instanceof ClientEntry) {
this._currentSelected.updateVariables(); this._currentSelected.updateClientVariables();
let version = this._currentSelected.properties.client_version; let version = this._currentSelected.properties.client_version;
if (!version) if (!version)
version = ""; version = "";
let infos = { let infos = {
"Name": this._currentSelected.createChatTag().html(), "Name": this._currentSelected.createChatTag(),
"Description": this._currentSelected.properties.client_description, "Description": this._currentSelected.properties.client_description,
"Version": "<a title='" + ChatMessage.formatMessage(version) + "'>" + version.split(" ")[0] + "</a>" + " on " + this._currentSelected.properties.client_platform, "Version": MessageHelper.formatMessage("{0} on {1}", $.spawn("a").attr("title", version).text(version.split(" ")[0]), this._currentSelected.properties.client_platform),
"Online since": "<a class='online'>" + formatDate(this._currentSelected.calculateOnlineTime()) + "</a>", "Online since": $.spawn("a").addClass("online").text(formatDate(this._currentSelected.calculateOnlineTime())),
"Volume": this._currentSelected.audioController.volume * 100 + " %" "Volume": this._currentSelected.audioController.volume * 100 + " %"
}; };
if (this._currentSelected.properties["client_teaforum_id"] > 0) { if (this._currentSelected.properties.client_teaforum_id > 0) {
infos["TeaSpeak Account"] = "<a href='//forum.teaspeak.de/index.php?members/{1}/' target='_blank'>{0}</a>".format(this._currentSelected.properties["client_teaforum_name"], this._currentSelected.properties["client_teaforum_id"]); infos["TeaSpeak Account"] = $.spawn("a")
.attr("href", "//forum.teaspeak.de/index.php?members/" + this._currentSelected.properties.client_teaforum_id)
.attr("target", "_blank")
.text(this._currentSelected.properties.client_teaforum_id);
} }
this._htmlTag.append(this.createInfoTable(infos)); this._htmlTag.append(this.createInfoTable(infos));
{ {
@ -3264,13 +3317,17 @@ class InfoBar {
.css("margin-left", "10px") .css("margin-left", "10px")
.css("align-items", "center"); .css("align-items", "center");
this.handle.fileManager.icons.generateTag(group.properties.iconid).appendTo(groupTag); this.handle.fileManager.icons.generateTag(group.properties.iconid).appendTo(groupTag);
$.spawn("div").text(group.name).css("margin-left", "3px").appendTo(groupTag); $.spawn("div").text(group.name)
.css("margin-left", "3px").appendTo(groupTag);
groupTag.appendTo(channelGroup); groupTag.appendTo(channelGroup);
} }
this._htmlTag.append(channelGroup); this._htmlTag.append(channelGroup);
} }
if (this._currentSelected.properties.client_flag_avatar.length > 0) if (this._currentSelected.properties.client_flag_avatar.length > 0)
this.handle.fileManager.avatars.generateTag(this._currentSelected).appendTo(this._htmlTag); this.handle.fileManager.avatars.generateTag(this._currentSelected)
.css("margin-top", "20px")
.css("max-height", "90%")
.css("max-width", "100%").appendTo(this._htmlTag);
this.intervals.push(setInterval(this.updateClientTimings.bind(this), 1000)); this.intervals.push(setInterval(this.updateClientTimings.bind(this), 1000));
} }
} }
@ -4628,14 +4685,14 @@ class AvatarManager {
let img = $.spawn("img"); let img = $.spawn("img");
img.attr("alt", ""); img.attr("alt", "");
let avatar = this.resolveCached(client); let avatar = this.resolveCached(client);
avatar = undefined;
if (avatar) { if (avatar) {
img.attr("src", "data:image/png;base64," + avatar.base64); img.attr("src", "data:image/png;base64," + avatar.base64);
tag.append(img); tag.append(img);
} }
else { else {
img.attr("src", "file://null"); let loader = $.spawn("img");
let loader = $.spawn("div"); loader.attr("src", "img/loading_image.svg").css("width", "75%");
loader.addClass("avatar_loading");
tag.append(loader); tag.append(loader);
this.loadAvatar(client).then(avatar => { this.loadAvatar(client).then(avatar => {
img.attr("src", "data:image/png;base64," + avatar.base64); img.attr("src", "data:image/png;base64," + avatar.base64);
@ -4649,7 +4706,7 @@ class AvatarManager {
}).catch(reason => { }).catch(reason => {
console.error("Could not load avatar for " + client.clientNickName() + ". Reason: " + reason); console.error("Could not load avatar for " + client.clientNickName() + ". Reason: " + reason);
//TODO Broken image //TODO Broken image
loader.removeClass("avatar_loading").addClass("icon client-warning").attr("tag", "Could not load avatar " + client.clientNickName()); loader.addClass("icon client-warning").attr("tag", "Could not load avatar " + client.clientNickName());
}); });
} }
return tag; return tag;
@ -4748,6 +4805,73 @@ var ChatType;
ChatType[ChatType["CHANNEL"] = 2] = "CHANNEL"; ChatType[ChatType["CHANNEL"] = 2] = "CHANNEL";
ChatType[ChatType["CLIENT"] = 3] = "CLIENT"; ChatType[ChatType["CLIENT"] = 3] = "CLIENT";
})(ChatType || (ChatType = {})); })(ChatType || (ChatType = {}));
var MessageHelper;
(function (MessageHelper) {
function htmlEscape(message) {
const div = document.createElement('div');
div.innerText = message;
message = div.innerHTML;
return message.replace(/ /g, '&nbsp;');
}
MessageHelper.htmlEscape = htmlEscape;
function formatElement(object) {
if ($.isArray(object)) {
let result = [];
for (let element of object)
result.push(...this.formatElement(element));
return result;
}
else if (typeof (object) == "string") {
if (object.length == 0)
return [];
return [$.spawn("a").html(this.htmlEscape(object))];
}
else if (typeof (object) === "object") {
if (object instanceof jQuery)
return [object];
return this.formatElement("<unknwon object>");
}
else if (typeof (object) === "function")
return this.formatElement(object());
else if (typeof (object) === "undefined")
return this.formatElement("<undefined>");
return this.formatElement("<unknown object type " + typeof object + ">");
}
MessageHelper.formatElement = formatElement;
function formatMessage(pattern, ...objects) {
let begin = 0, found = 0;
let result = [];
do {
found = pattern.indexOf('{', found);
if (found == -1 || pattern.length <= found + 1) {
result.push(...this.formatElement(pattern.substr(begin)));
break;
}
if (found > 0 && pattern[found - 1] == '\\') {
//TODO remove the escape!
found++;
continue;
}
result.push(...this.formatElement(pattern.substr(begin, found - begin))); //Append the text
let number;
let offset = 0;
while ("0123456789".includes(pattern[found + 1 + offset]))
offset++;
number = parseInt(offset > 0 ? pattern.substr(found + 1, offset) : "0");
if (pattern[found + offset + 1] != '}') {
found++;
continue;
}
if (objects.length < number)
console.warn("Message to format contains invalid index (" + number + ")");
result.push(...this.formatElement(objects[number]));
begin = found = found + 2 + offset;
console.log("Offset: " + offset + " Number: " + number);
} while (found++);
return result;
}
MessageHelper.formatMessage = formatMessage;
})(MessageHelper || (MessageHelper = {}));
class ChatMessage { class ChatMessage {
constructor(message) { constructor(message) {
this.date = new Date(); this.date = new Date();
@ -4768,27 +4892,12 @@ class ChatMessage {
dateTag.text("<" + this.num(this.date.getUTCHours()) + ":" + this.num(this.date.getUTCMinutes()) + ":" + this.num(this.date.getUTCSeconds()) + "> "); dateTag.text("<" + this.num(this.date.getUTCHours()) + ":" + this.num(this.date.getUTCMinutes()) + ":" + this.num(this.date.getUTCSeconds()) + "> ");
dateTag.css("margin-right", "4px"); dateTag.css("margin-right", "4px");
dateTag.css("color", "dodgerblue"); dateTag.css("color", "dodgerblue");
let messageTag = $.spawn("div");
messageTag.html(this.message);
messageTag.css("color", "blue");
this._htmlTag = tag; this._htmlTag = tag;
tag.append(dateTag); tag.append(dateTag);
tag.append(messageTag); this.message.forEach(e => e.appendTo(tag));
tag.hide(); tag.hide();
return tag; return tag;
} }
static formatMessage(message) {
/*
message = message
.replace(/ /g, '&nbsp;')
.replace(/\n/g, "<br/>");
*/
const div = document.createElement('div');
div.innerText = message;
message = div.innerHTML;
console.log(message + "->" + div.innerHTML);
return message;
}
} }
class ChatEntry { class ChatEntry {
constructor(handle, type, key) { constructor(handle, type, key) {
@ -4800,25 +4909,15 @@ class ChatEntry {
this.onClose = function () { return true; }; this.onClose = function () { return true; };
} }
appendError(message, ...args) { appendError(message, ...args) {
this.appendMessage("<a style='color: red'>{0}</a>".format(ChatMessage.formatMessage(message).format(...args)), false); let entries = MessageHelper.formatMessage(message, ...args);
entries.forEach(e => e.css("color", "red"));
this.pushChatMessage(new ChatMessage(entries));
} }
appendMessage(message, fmt = true, ...args) { appendMessage(message, fmt = true, ...args) {
let parms = []; this.pushChatMessage(new ChatMessage(MessageHelper.formatMessage(message, ...args)));
for (let index = 2; index < arguments.length; index++) {
if (typeof arguments[index] == "string")
arguments[index] = ChatMessage.formatMessage(arguments[index]);
else if (arguments[index] instanceof jQuery)
arguments[index] = arguments[index].html();
else {
console.error("Invalid type " + typeof arguments[index] + "|" + arguments[index].prototype);
arguments[index] = arguments[index].toString();
} }
parms.push(arguments[index]); pushChatMessage(entry) {
} this.history.push(entry);
let msg = fmt ? ChatMessage.formatMessage(message) : message;
msg = msg.format(parms);
let elm = new ChatMessage(msg);
this.history.push(elm);
while (this.history.length > 100) { while (this.history.length > 100) {
let elm = this.history.pop_front(); let elm = this.history.pop_front();
elm.htmlTag.animate({ opacity: 0 }, 200, function () { elm.htmlTag.animate({ opacity: 0 }, 200, function () {
@ -4829,8 +4928,8 @@ class ChatEntry {
let box = $(this.handle.htmlTag).find(".messages"); let box = $(this.handle.htmlTag).find(".messages");
let mbox = $(this.handle.htmlTag).find(".message_box"); let mbox = $(this.handle.htmlTag).find(".message_box");
let bottom = box.scrollTop() + box.height() + 1 >= mbox.height(); let bottom = box.scrollTop() + box.height() + 1 >= mbox.height();
mbox.append(elm.htmlTag); mbox.append(entry.htmlTag);
elm.htmlTag.show().css("opacity", "0").animate({ opacity: 1 }, 100); entry.htmlTag.show().css("opacity", "0").animate({ opacity: 1 }, 100);
if (bottom) if (bottom)
box.scrollTop(mbox.height()); box.scrollTop(mbox.height());
} }
@ -5341,6 +5440,88 @@ var Modals;
Identity isnt valid! Identity isnt valid!
</div> </div>
*/ */
/// <reference path="../../utils/modal.ts" />
/// <reference path="../../proto.ts" />
/// <reference path="../../client.ts" />
var Modals;
(function (Modals) {
function spawnBanClient(name, callback) {
const connectModal = createModal({
header: function () {
return "Ban client";
},
body: function () {
let tag = $("#tmpl_client_ban").tmpl({
client_name: name
});
let maxTime = 0; //globalClient.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).value;
let unlimited = maxTime == 0 || maxTime == -1;
if (unlimited)
maxTime = 0;
let banTag = tag.find(".ban_duration_type");
let durationTag = tag.find(".ban_duration");
banTag.find("option[value=\"sec\"]").prop("disabled", !unlimited && 1 > maxTime)
.attr("duration-scale", 1)
.attr("duration-max", maxTime);
banTag.find("option[value=\"min\"]").prop("disabled", !unlimited && 60 > maxTime)
.attr("duration-scale", 60)
.attr("duration-max", maxTime / 60);
banTag.find("option[value=\"hours\"]").prop("disabled", !unlimited && 60 * 60 > maxTime)
.attr("duration-scale", 60 * 60)
.attr("duration-max", maxTime / (60 * 60));
banTag.find("option[value=\"days\"]").prop("disabled", !unlimited && 60 * 60 * 24 > maxTime)
.attr("duration-scale", 60 * 60 * 24)
.attr("duration-max", maxTime / (60 * 60 * 24));
banTag.find("option[value=\"perm\"]").prop("disabled", !unlimited)
.attr("duration-scale", 0);
durationTag.change(() => banTag.trigger('change'));
banTag.change(event => {
let element = $(event.target.selectedOptions.item(0));
if (element.val() !== "perm") {
durationTag.prop("disabled", false);
let current = durationTag.val();
let max = parseInt(element.attr("duration-max"));
if (max > 0 && current > max)
durationTag.val(max);
else if (current <= 0)
durationTag.val(1);
durationTag.attr("max", max);
}
else {
durationTag.prop("disabled", true);
}
});
return tag;
},
footer: function () {
let tag = $.spawn("div");
tag.css("text-align", "right");
tag.css("margin-top", "3px");
tag.css("margin-bottom", "6px");
tag.addClass("modal-button-group");
let buttonCancel = $.spawn("button");
buttonCancel.text("Cancel");
buttonCancel.on("click", () => connectModal.close());
tag.append(buttonCancel);
let buttonOk = $.spawn("button");
buttonOk.text("OK").addClass("btn_success");
tag.append(buttonOk);
return tag;
},
width: 450
});
connectModal.open();
connectModal.htmlTag.find(".btn_success").on('click', () => {
connectModal.close();
let length = connectModal.htmlTag.find(".ban_duration").val();
let duration = connectModal.htmlTag.find(".ban_duration_type option:selected");
console.log(duration);
console.log(length + "*" + duration.attr("duration-scale"));
callback(length * parseInt(duration.attr("duration-scale")), connectModal.htmlTag.find(".ban_reason").val());
});
}
Modals.spawnBanClient = spawnBanClient;
})(Modals || (Modals = {}));
/// <reference path="Codec.ts"/> /// <reference path="Codec.ts"/>
class BasicCodec { class BasicCodec {
constructor(codecSampleRate) { constructor(codecSampleRate) {
@ -5561,6 +5742,7 @@ class CodecWrapper extends BasicCodec {
/// <reference path="utils/modal.ts" /> /// <reference path="utils/modal.ts" />
/// <reference path="ui/modal/ModalConnect.ts" /> /// <reference path="ui/modal/ModalConnect.ts" />
/// <reference path="ui/modal/ModalCreateChannel.ts" /> /// <reference path="ui/modal/ModalCreateChannel.ts" />
/// <reference path="ui/modal/ModalBanClient.ts" />
/// <reference path="codec/CodecWrapper.ts" /> /// <reference path="codec/CodecWrapper.ts" />
/// <reference path="settings.ts" /> /// <reference path="settings.ts" />
/// <reference path="log.ts" /> /// <reference path="log.ts" />
@ -5569,7 +5751,9 @@ let globalClient;
let chat; let chat;
let forumIdentity; let forumIdentity;
function main() { function main() {
//console.log(ChatEntry.formatMessage("Hello World '{0}' | '{1}' | {12} X", "XXX"));
//localhost:63343/Web-Client/index.php?disableUnloadDialog=1&default_connect_type=forum&default_connect_url=localhost //localhost:63343/Web-Client/index.php?disableUnloadDialog=1&default_connect_type=forum&default_connect_url=localhost
//disableUnloadDialog=1&default_connect_type=forum&default_connect_url=localhost&loader_ignore_age=1
AudioController.initializeAudioController(); AudioController.initializeAudioController();
if (!TSIdentityHelper.setup()) { if (!TSIdentityHelper.setup()) {
console.error("Could not setup the TeamSpeak identity parser!"); console.error("Could not setup the TeamSpeak identity parser!");
@ -5595,7 +5779,7 @@ function main() {
//Modals.spawnSettingsModal(); //Modals.spawnSettingsModal();
//Modals.createChannelModal(undefined); //Modals.createChannelModal(undefined);
if (settings.static("default_connect_url")) { if (settings.static("default_connect_url")) {
if (settings.static("default_connect_type", "forum")) { if (settings.static("default_connect_type", "forum") == "forum") {
globalClient.startConnection(settings.static("default_connect_url"), forumIdentity); globalClient.startConnection(settings.static("default_connect_url"), forumIdentity);
} }
else else
@ -5683,10 +5867,10 @@ class ServerEntry {
tag.attr("id", "server"); tag.attr("id", "server");
tag.addClass("server"); tag.addClass("server");
tag.append($.spawn("div").addClass("server_type icon client-server_green")); tag.append($.spawn("div").addClass("server_type icon client-server_green"));
tag.append("<a class='name'>" + this.properties.virtualserver_name + "</a>"); tag.append($.spawn("a").addClass("name").text(this.properties.virtualserver_name));
const serverIcon = $("<span/>"); const serverIcon = $("<span/>");
//we cant spawn an icon on creation :) //we cant spawn an icon on creation :)
serverIcon.append("<div class='icon_property icon_empty'></div>"); serverIcon.append($.spawn("div").addClass("icon_property icon_empty"));
tag.append(serverIcon); tag.append(serverIcon);
return this._htmlTag = tag; return this._htmlTag = tag;
} }
@ -5728,7 +5912,7 @@ class ServerEntry {
this.channelTree.client.serverConnection.sendCommand("servergetvariables"); this.channelTree.client.serverConnection.sendCommand("servergetvariables");
} }
shouldUpdateProperties() { shouldUpdateProperties() {
return this.nextInfoRequest < new Date().getTime(); return this.nextInfoRequest < Date.now();
} }
calculateUptime() { calculateUptime() {
if (this.properties.virtualserver_uptime == 0 || this.lastInfoRequest == 0) if (this.properties.virtualserver_uptime == 0 || this.lastInfoRequest == 0)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -103,4 +103,5 @@
id="path13674" /><path id="path13674" /><path
d="M104.397,50.073L118.944,50.073L118.944,54.193L109.293,54.193L109.293,60.077L118.17,60.077L118.17,64.057L109.293,64.057L109.293,70.538L119.05,70.538L119.05,74.657L104.398,74.657L104.398,50.073L104.397,50.073Z" d="M104.397,50.073L118.944,50.073L118.944,54.193L109.293,54.193L109.293,60.077L118.17,60.077L118.17,64.057L109.293,64.057L109.293,70.538L119.05,70.538L119.05,74.657L104.398,74.657L104.398,50.073L104.397,50.073Z"
style="fill:#a9aaac;fill-rule:nonzero;fill-opacity:1" style="fill:#a9aaac;fill-rule:nonzero;fill-opacity:1"
id="path13676" /></g></g></g></g></svg> id="path13676" /></g></g></g></g>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -30,6 +30,7 @@
<link rel="stylesheet" href="css/general.css" type="text/css"> <link rel="stylesheet" href="css/general.css" type="text/css">
<link rel="stylesheet" href="css/modals.css" type="text/css"> <link rel="stylesheet" href="css/modals.css" type="text/css">
<link rel="stylesheet" href="css/loader.css" type="text/css"> <link rel="stylesheet" href="css/loader.css" type="text/css">
<link rel="stylesheet" href="css/music/info_plate.css" type="text/css">
<!-- PHP generated properies --> <!-- PHP generated properies -->
<!-- localhost:63342/TeaSpeak-Web/index.php?_ijt=o48hmliefjoa8cer8v7mpl98pj&connect_default_host=192.168.43.141 --> <!-- localhost:63342/TeaSpeak-Web/index.php?_ijt=o48hmliefjoa8cer8v7mpl98pj&connect_default_host=192.168.43.141 -->
@ -105,6 +106,7 @@
<div style="height: 45px; width: 100%; border-radius: 2px 0px 0px 0px; border-bottom-width: 0px; background-color: lightgrey" class="main_container"> <div style="height: 45px; width: 100%; border-radius: 2px 0px 0px 0px; border-bottom-width: 0px; background-color: lightgrey" class="main_container">
<div id="control_bar" class="control_bar"> <div id="control_bar" class="control_bar">
<div class="button btn_connect"><div class="icon_x32 client-connect"></div></div> <div class="button btn_connect"><div class="icon_x32 client-connect"></div></div>
<!--<div class="button btn_disconnect"><div class="icon_x32 client-disconnect"></div></div>-->
<div style="border-left:2px solid gray;height: auto; margin-left: 5px; margin-right: 5px"></div> <div style="border-left:2px solid gray;height: auto; margin-left: 5px; margin-right: 5px"></div>
<div class="button btn_client_away"><div class="icon_x32 client-away"></div></div> <div class="button btn_client_away"><div class="icon_x32 client-away"></div></div>
<div class="button btn_mute_input"><div class="icon_x32 client-input_muted"></div></div> <div class="button btn_mute_input"><div class="icon_x32 client-input_muted"></div></div>
@ -117,9 +119,7 @@
<div style="flex-direction: row; height: 100%; width: 100%; display: flex"> <div style="flex-direction: row; height: 100%; width: 100%; display: flex">
<div style="width: 60%; flex-direction: column;"> <div style="width: 60%; flex-direction: column;">
<div style="height: 60%; border-radius: 0px 0px 0px 0px; border-right-width: 0px; overflow: auto; overflow-x: visible" class="main_container"> <div style="height: 60%; border-radius: 0px 0px 0px 0px; border-right-width: 0px; overflow: auto; overflow-x: visible" class="main_container">
<div class="channelTree" id="channelTree"> <div class="channelTree" id="channelTree"></div>
<div class="server l"><div class="icon client-server_green"></div> TeaSpeak web!</div>
</div>
</div> <!-- Channel tree --> </div> <!-- Channel tree -->
<div style="height: 40%; border-radius: 0px 0px 0px 2px; border-top-width: 0px; border-right-width: 0px;" class="main_container"> <div style="height: 40%; border-radius: 0px 0px 0px 2px; border-top-width: 0px; border-right-width: 0px;" class="main_container">
<div id="chat"> <div id="chat">
@ -136,7 +136,7 @@
</div> <!-- Chat window --> </div> <!-- Chat window -->
</div> </div>
<div style="width: 40%; border-radius: 0px 0px 2px 0px;" class="main_container"> <div style="width: 40%; border-radius: 0px 0px 2px 0px;" class="main_container">
<div id="select_info" class="select_info"> <div id="select_info" class="select_info" style="width: 100%; max-width: 100%">
</div> </div>
</div> <!-- Selection info --> </div> <!-- Selection info -->
</div> </div>
@ -148,6 +148,9 @@
<div id="scripts"> <div id="scripts">
<script src="js/load.js"></script> <script src="js/load.js"></script>
</div> </div>
<div id="music-test"></div>
<div style="height: 100px"></div>
</body> </body>
<footer> <footer>
<div class="container"> <div class="container">

View file

@ -425,14 +425,13 @@ class AvatarManager {
img.attr("alt", ""); img.attr("alt", "");
let avatar = this.resolveCached(client); let avatar = this.resolveCached(client);
avatar = undefined;
if(avatar) { if(avatar) {
img.attr("src", "data:image/png;base64," + avatar.base64); img.attr("src", "data:image/png;base64," + avatar.base64);
tag.append(img); tag.append(img);
} else { } else {
img.attr("src", "file://null"); let loader = $.spawn("img");
loader.attr("src", "img/loading_image.svg").css("width", "75%");
let loader = $.spawn("div");
loader.addClass("avatar_loading");
tag.append(loader); tag.append(loader);
this.loadAvatar(client).then(avatar => { this.loadAvatar(client).then(avatar => {
@ -448,7 +447,7 @@ class AvatarManager {
}).catch(reason => { }).catch(reason => {
console.error("Could not load avatar for " + client.clientNickName() + ". Reason: " + reason); console.error("Could not load avatar for " + client.clientNickName() + ". Reason: " + reason);
//TODO Broken image //TODO Broken image
loader.removeClass("avatar_loading").addClass("icon client-warning").attr("tag", "Could not load avatar " + client.clientNickName()); loader.addClass("icon client-warning").attr("tag", "Could not load avatar " + client.clientNickName());
}); });
} }

View file

@ -16,13 +16,17 @@ class InfoBar {
private createInfoTable(infos: any) : JQuery<HTMLElement> { private createInfoTable(infos: any) : JQuery<HTMLElement> {
let table = $("<table/>"); let table = $.spawn("table");
for(let e in infos) { for(let key in infos) {
console.log("Display info " + e); console.log("Display info " + key);
let entry = $("<tr/>"); let entry = $.spawn("tr");
entry.append("<td class='info_key'>" + e + ":</td>"); entry.append($.spawn("td").addClass("info_key").html(key + ":"));
entry.append("<td>" + infos[e] + "</td>"); let value = $.spawn("td");
console.log(infos[key]);
console.log( MessageHelper.formatElement(infos[key]));
MessageHelper.formatElement(infos[key]).forEach(e => e.appendTo(value));
entry.append(value);
table.append(entry); table.append(entry);
} }
@ -106,7 +110,7 @@ class InfoBar {
} else if(this._currentSelected instanceof ChannelEntry) { } else if(this._currentSelected instanceof ChannelEntry) {
let props = this._currentSelected.properties; let props = this._currentSelected.properties;
this._htmlTag.append(this.createInfoTable({ this._htmlTag.append(this.createInfoTable({
"Name": this._currentSelected.createChatTag().html(), "Name": this._currentSelected.createChatTag(),
"Topic": this._currentSelected.properties.channel_topic, "Topic": this._currentSelected.properties.channel_topic,
"Codec": this._currentSelected.properties.channel_codec, "Codec": this._currentSelected.properties.channel_codec,
"Codec Quality": this._currentSelected.properties.channel_codec_quality, "Codec Quality": this._currentSelected.properties.channel_codec_quality,
@ -115,20 +119,35 @@ class InfoBar {
"Subscription Status": "unknown", "Subscription Status": "unknown",
"Voice Data Encryption": "unknown" "Voice Data Encryption": "unknown"
})); }));
} else if(this._currentSelected instanceof ClientEntry) { } else if(this._currentSelected instanceof MusicClientEntry) {
this._currentSelected.updateVariables(); this._htmlTag.append("Im a music bot!");
let frame = $("#tmpl_music_frame" + (this._currentSelected.properties.music_track_id == 0 ? "_empty" : "")).tmpl({
thumbnail: "img/loading_image.svg"
}).css("align-self", "center");
if(this._currentSelected.properties.music_track_id == 0) {
} else {
}
this._htmlTag.append(frame);
//TODO
} else if(this._currentSelected instanceof ClientEntry) { this._currentSelected.updateClientVariables();
let version: string = this._currentSelected.properties.client_version; let version: string = this._currentSelected.properties.client_version;
if(!version) version = ""; if(!version) version = "";
let infos = { let infos = {
"Name": this._currentSelected.createChatTag().html(), "Name": this._currentSelected.createChatTag(),
"Description": this._currentSelected.properties.client_description, "Description": this._currentSelected.properties.client_description,
"Version": "<a title='" + ChatMessage.formatMessage(version) + "'>" + version.split(" ")[0] + "</a>" + " on " + this._currentSelected.properties.client_platform, "Version": MessageHelper.formatMessage("{0} on {1}", $.spawn("a").attr("title", version).text(version.split(" ")[0]), this._currentSelected.properties.client_platform),
"Online since": "<a class='online'>" + formatDate(this._currentSelected.calculateOnlineTime()) + "</a>", "Online since": $.spawn("a").addClass("online").text(formatDate(this._currentSelected.calculateOnlineTime())),
"Volume": this._currentSelected.audioController.volume * 100 + " %" "Volume": this._currentSelected.audioController.volume * 100 + " %"
}; };
if(this._currentSelected.properties["client_teaforum_id"] > 0) { if(this._currentSelected.properties.client_teaforum_id > 0) {
infos["TeaSpeak Account"] = "<a href='//forum.teaspeak.de/index.php?members/{1}/' target='_blank'>{0}</a>".format(this._currentSelected.properties["client_teaforum_name"], this._currentSelected.properties["client_teaforum_id"]); infos["TeaSpeak Account"] = $.spawn("a")
.attr("href", "//forum.teaspeak.de/index.php?members/" + this._currentSelected.properties.client_teaforum_id)
.attr("target", "_blank")
.text(this._currentSelected.properties.client_teaforum_id);
} }
this._htmlTag.append(this.createInfoTable(infos)); this._htmlTag.append(this.createInfoTable(infos));
@ -169,7 +188,8 @@ class InfoBar {
let channelGroup = $.spawn("div"); let channelGroup = $.spawn("div");
channelGroup channelGroup
.css("display", "flex") .css("display", "flex")
.css("flex-direction", "column"); .css("flex-direction", "column")
.css("margin-bottom", "20px");
let header = $.spawn("div"); let header = $.spawn("div");
header header
@ -189,15 +209,42 @@ class InfoBar {
.css("margin-left", "10px") .css("margin-left", "10px")
.css("align-items", "center"); .css("align-items", "center");
this.handle.fileManager.icons.generateTag(group.properties.iconid).appendTo(groupTag); this.handle.fileManager.icons.generateTag(group.properties.iconid).appendTo(groupTag);
$.spawn("div").text(group.name).css("margin-left", "3px").appendTo(groupTag); $.spawn("div").text(group.name)
.css("margin-left", "3px").appendTo(groupTag);
groupTag.appendTo(channelGroup); groupTag.appendTo(channelGroup);
} }
this._htmlTag.append(channelGroup); this._htmlTag.append(channelGroup);
} }
{
if(this._currentSelected.properties.client_flag_avatar.length > 0) if(this._currentSelected.properties.client_flag_avatar.length > 0)
this.handle.fileManager.avatars.generateTag(this._currentSelected).appendTo(this._htmlTag); this.handle.fileManager.avatars.generateTag(this._currentSelected)
.css("max-height", "90%")
.css("max-width", "100%").appendTo(this._htmlTag);
}
{
let spawnTag = (type: string, description: string) : JQuery => {
return $.spawn("div").css("display", "inline-flex")
.append($.spawn("div").addClass("icon_x32 client-" + type).css("margin-right", "5px"))
.append($.spawn("a").text(description).css("align-self", "center"));
};
if(!this._currentSelected.properties.client_output_hardware)
spawnTag("hardware_output_muted", "Speakers/Headphones disabled").appendTo(this._htmlTag);
if(!this._currentSelected.properties.client_input_hardware)
spawnTag("hardware_input_muted", "Microphone disabled").appendTo(this._htmlTag);
if(this._currentSelected.properties.client_output_muted)
spawnTag("output_muted", "Speakers/Headphones Muted").appendTo(this._htmlTag);
if(this._currentSelected.properties.client_input_muted)
spawnTag("input_muted", "Microphone Muted").appendTo(this._htmlTag);
}
this.intervals.push(setInterval(this.updateClientTimings.bind(this),1000)); this.intervals.push(setInterval(this.updateClientTimings.bind(this),1000));
} }
} }

View file

@ -5,12 +5,79 @@ enum ChatType {
CLIENT CLIENT
} }
namespace MessageHelper {
export function htmlEscape(message: string) : string {
const div = document.createElement('div');
div.innerText = message;
message = div.innerHTML;
return message.replace(/ /g, '&nbsp;');
}
export function formatElement(object: any) : JQuery[] {
if($.isArray(object)) {
let result = [];
for(let element of object)
result.push(...this.formatElement(element));
return result;
} else if(typeof(object) == "string") {
if(object.length == 0) return [];
return [$.spawn("a").html(this.htmlEscape(object))];
} else if(typeof(object) === "object") {
if(object instanceof jQuery)
return [object];
return this.formatElement("<unknwon object>");
} else if(typeof(object) === "function") return this.formatElement(object());
else if(typeof(object) === "undefined") return this.formatElement("<undefined>");
return this.formatElement("<unknown object type " + typeof object + ">");
}
export function formatMessage(pattern: string, ...objects: any[]) : JQuery[] {
let begin = 0, found = 0;
let result: JQuery[] = [];
do {
found = pattern.indexOf('{', found);
if(found == -1 || pattern.length <= found + 1) {
result.push(...this.formatElement(pattern.substr(begin)));
break;
}
if(found > 0 && pattern[found - 1] == '\\') {
//TODO remove the escape!
found++;
continue;
}
result.push(...this.formatElement(pattern.substr(begin, found - begin))); //Append the text
let number;
let offset = 0;
while ("0123456789".includes(pattern[found + 1 + offset])) offset++;
number = parseInt(offset > 0 ? pattern.substr(found + 1, offset) : "0");
if(pattern[found + offset + 1] != '}') {
found++;
continue;
}
if(objects.length < number)
console.warn("Message to format contains invalid index (" + number + ")");
result.push(...this.formatElement(objects[number]));
begin = found = found + 2 + offset;
console.log("Offset: " + offset + " Number: " + number);
} while(found++);
return result;
}
}
class ChatMessage { class ChatMessage {
date: Date; date: Date;
message: string; message: JQuery[];
private _htmlTag: JQuery<HTMLElement>; private _htmlTag: JQuery<HTMLElement>;
constructor(message) { constructor(message: JQuery[]) {
this.date = new Date(); this.date = new Date();
this.message = message; this.message = message;
} }
@ -32,29 +99,12 @@ class ChatMessage {
dateTag.css("margin-right", "4px"); dateTag.css("margin-right", "4px");
dateTag.css("color", "dodgerblue"); dateTag.css("color", "dodgerblue");
let messageTag = $.spawn("div");
messageTag.html(this.message);
messageTag.css("color", "blue");
this._htmlTag = tag; this._htmlTag = tag;
tag.append(dateTag); tag.append(dateTag);
tag.append(messageTag); this.message.forEach(e => e.appendTo(tag));
tag.hide(); tag.hide();
return tag; return tag;
} }
static formatMessage(message: string) : string {
/*
message = message
.replace(/ /g, '&nbsp;')
.replace(/\n/g, "<br/>");
*/
const div = document.createElement('div');
div.innerText = message;
message = div.innerHTML;
console.log(message + "->" + div.innerHTML);
return message;
}
} }
class ChatEntry { class ChatEntry {
@ -81,24 +131,17 @@ class ChatEntry {
} }
appendError(message: string, ...args) { appendError(message: string, ...args) {
this.appendMessage("<a style='color: red'>{0}</a>".format(ChatMessage.formatMessage(message).format(...args)), false); let entries = MessageHelper.formatMessage(message, ...args);
entries.forEach(e => e.css("color", "red"));
this.pushChatMessage(new ChatMessage(entries));
} }
appendMessage(message : string, fmt: boolean = true, ...args) { appendMessage(message : string, fmt: boolean = true, ...args) {
let parms: any[] = []; this.pushChatMessage(new ChatMessage(MessageHelper.formatMessage(message, ...args)));
for(let index = 2; index < arguments.length; index++) {
if(typeof arguments[index] == "string") arguments[index] = ChatMessage.formatMessage(arguments[index]);
else if(arguments[index] instanceof jQuery) arguments[index] = arguments[index].html();
else {
console.error("Invalid type " + typeof arguments[index] + "|" + arguments[index].prototype);
arguments[index] = arguments[index].toString();
} }
parms.push(arguments[index]);
} private pushChatMessage(entry: ChatMessage) {
let msg : string = fmt ? ChatMessage.formatMessage(message) : message; this.history.push(entry);
msg = msg.format(parms);
let elm = new ChatMessage(msg);
this.history.push(elm);
while(this.history.length > 100) { while(this.history.length > 100) {
let elm = this.history.pop_front(); let elm = this.history.pop_front();
elm.htmlTag.animate({opacity: 0}, 200, function () { elm.htmlTag.animate({opacity: 0}, 200, function () {
@ -109,8 +152,8 @@ class ChatEntry {
let box = $(this.handle.htmlTag).find(".messages"); let box = $(this.handle.htmlTag).find(".messages");
let mbox = $(this.handle.htmlTag).find(".message_box"); let mbox = $(this.handle.htmlTag).find(".message_box");
let bottom : boolean = box.scrollTop() + box.height() + 1 >= mbox.height(); let bottom : boolean = box.scrollTop() + box.height() + 1 >= mbox.height();
mbox.append(elm.htmlTag); mbox.append(entry.htmlTag);
elm.htmlTag.show().css("opacity", "0").animate({opacity: 1}, 100); entry.htmlTag.show().css("opacity", "0").animate({opacity: 1}, 100);
if(bottom) box.scrollTop(mbox.height()); if(bottom) box.scrollTop(mbox.height());
} else { } else {
this.unread = true; this.unread = true;

View file

@ -1,5 +1,6 @@
/// <reference path="ui/channel.ts" /> /// <reference path="ui/channel.ts" />
/// <reference path="client.ts" /> /// <reference path="client.ts" />
/// <reference path="ui/MusicClient.ts" />
class CommandResult { class CommandResult {
success: boolean; success: boolean;
@ -466,7 +467,11 @@ class ConnectionCommandHandler {
client = tree.findClient(json["clid"]); client = tree.findClient(json["clid"]);
if(!client) { if(!client) {
if(parseInt(json["client_type_exact"]) == ClientType.CLIENT_MUSIC) {
client = new MusicClientEntry(parseInt(json["clid"]), json["client_nickname"]);
} else {
client = new ClientEntry(parseInt(json["clid"]), json["client_nickname"]); client = new ClientEntry(parseInt(json["clid"]), json["client_nickname"]);
}
client = tree.insertClient(client, channel); client = tree.insertClient(client, channel);
} else { } else {
if(client == this.connection._client.getClient()) if(client == this.connection._client.getClient())
@ -500,6 +505,9 @@ class ConnectionCommandHandler {
} }
client.updateVariables(...updates); client.updateVariables(...updates);
if(client instanceof LocalClientEntry)
this.connection._client.controlBar.updateVoice();
} }
handleCommandClientLeftView(json) { handleCommandClientLeftView(json) {
@ -575,22 +583,24 @@ class ConnectionCommandHandler {
if(!channel_from) //Not critical if(!channel_from) //Not critical
console.error("Unknown client move (Channel from)!"); console.error("Unknown client move (Channel from)!");
if(client instanceof LocalClientEntry) { let self = client instanceof LocalClientEntry;
if(self) {
chat.channelChat().name = channel_to.channelName(); chat.channelChat().name = channel_to.channelName();
for(let entry of client.channelTree.clientsByChannel(client.currentChannel())) for(let entry of client.channelTree.clientsByChannel(client.currentChannel()))
if(entry !== client) entry.getAudioController().stopAudio(true); if(entry !== client) entry.getAudioController().stopAudio(true);
this.connection._client.controlBar.updateVoice(channel_to);
} }
tree.moveClient(client, channel_to); tree.moveClient(client, channel_to);
if(json["reasonid"] == ViewReasonId.VREASON_MOVED) { if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
chat.serverChat().appendMessage("{0} was moved from channel {1} to {2} by {3}", true, chat.serverChat().appendMessage(self ? "You was moved by {3} from channel {1} to {2}" : "{0} was moved from channel {1} to {2} by {3}", true,
client.createChatTag(true), client.createChatTag(true),
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"])
); );
} else if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) { } else if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
chat.serverChat().appendMessage("{0} switched from channel {1} to {2}", true, chat.serverChat().appendMessage(self ? "You switched from channel {1} to {2}" : "{0} switched from channel {1} to {2}", true,
client.createChatTag(true), client.createChatTag(true),
channel_from ? channel_from.createChatTag(true) : undefined, channel_from ? channel_from.createChatTag(true) : undefined,
channel_to.createChatTag(true) channel_to.createChatTag(true)

View file

@ -145,8 +145,10 @@ function loadDebug() {
"js/ui/modal/ModalCreateChannel.js", "js/ui/modal/ModalCreateChannel.js",
"js/ui/modal/ModalConnect.js", "js/ui/modal/ModalConnect.js",
"js/ui/modal/ModalChangeVolume.js", "js/ui/modal/ModalChangeVolume.js",
"js/ui/modal/ModalBanClient.js",
"js/ui/channel.js", "js/ui/channel.js",
"js/ui/client.js", "js/ui/client.js",
"js/ui/MusicClient.js",
"js/ui/server.js", "js/ui/server.js",
"js/ui/view.js", "js/ui/view.js",
"js/ui/ControlBar.js", "js/ui/ControlBar.js",

View file

@ -4,6 +4,7 @@
/// <reference path="utils/modal.ts" /> /// <reference path="utils/modal.ts" />
/// <reference path="ui/modal/ModalConnect.ts" /> /// <reference path="ui/modal/ModalConnect.ts" />
/// <reference path="ui/modal/ModalCreateChannel.ts" /> /// <reference path="ui/modal/ModalCreateChannel.ts" />
/// <reference path="ui/modal/ModalBanClient.ts" />
/// <reference path="codec/CodecWrapper.ts" /> /// <reference path="codec/CodecWrapper.ts" />
/// <reference path="settings.ts" /> /// <reference path="settings.ts" />
/// <reference path="log.ts" /> /// <reference path="log.ts" />
@ -16,6 +17,7 @@ let forumIdentity: TeaForumIdentity;
function main() { function main() {
//localhost:63343/Web-Client/index.php?disableUnloadDialog=1&default_connect_type=forum&default_connect_url=localhost //localhost:63343/Web-Client/index.php?disableUnloadDialog=1&default_connect_type=forum&default_connect_url=localhost
//disableUnloadDialog=1&default_connect_type=forum&default_connect_url=localhost&loader_ignore_age=1
AudioController.initializeAudioController(); AudioController.initializeAudioController();
if(!TSIdentityHelper.setup()) { console.error("Could not setup the TeamSpeak identity parser!"); return; } if(!TSIdentityHelper.setup()) { console.error("Could not setup the TeamSpeak identity parser!"); return; }
@ -43,11 +45,19 @@ function main() {
//Modals.createChannelModal(undefined); //Modals.createChannelModal(undefined);
if(settings.static("default_connect_url")) { if(settings.static("default_connect_url")) {
if(settings.static("default_connect_type", "forum")) { if(settings.static("default_connect_type", "forum") == "forum") {
globalClient.startConnection(settings.static("default_connect_url"), forumIdentity); globalClient.startConnection(settings.static("default_connect_url"), forumIdentity);
} else } else
Modals.spawnConnectModal(settings.static("default_connect_url")); Modals.spawnConnectModal(settings.static("default_connect_url"));
} }
/*
$("#music-test").replaceWith($("#tmpl_music_frame_empty").tmpl({
thumbnail: "img/loading_image.svg"
}));
*/
Modals.spawnSettingsModal();
} }
app.loadedListener.push(() => main()); app.loadedListener.push(() => main());

View file

@ -17,6 +17,8 @@ class ControlBar {
private _away: boolean; private _away: boolean;
private _awayMessage: string; private _awayMessage: string;
private _codecNotSupported: boolean = true;
readonly handle: TSClient; readonly handle: TSClient;
htmlTag: JQuery; htmlTag: JQuery;
@ -142,6 +144,24 @@ class ControlBar {
}); });
} }
updateVoice(targetChannel?: ChannelEntry) {
if(!targetChannel)
targetChannel = this.handle.getClient().currentChannel();
let voiceSupport = this.handle.voiceConnection.codecSupported(targetChannel.properties.channel_codec);
if(voiceSupport == this._codecNotSupported) return;
this._codecNotSupported = voiceSupport;
this.htmlTag.find(".btn_mute_input").prop("disabled", !this._codecNotSupported);
this.htmlTag.find(".btn_mute_output").prop("disabled", !this._codecNotSupported);
this.handle.serverConnection.sendCommand("clientupdate", {
client_input_hardware: this._codecNotSupported,
client_output_hardware: this._codecNotSupported
});
if(!this._codecNotSupported)
createErrorModal("Channel codec unsupported", "This channel has an unsupported codec.<br>You cant speak or listen to anybody!").open();
}
private onOpenSettings() { private onOpenSettings() {
Modals.spawnSettingsModal(); Modals.spawnSettingsModal();
} }

View file

@ -0,0 +1,53 @@
/// <reference path="client.ts" />
class MusicClientProperties extends ClientProperties {
music_volume: number = 0;
music_track_id: number = 0;
}
class MusicClientEntry extends ClientEntry {
constructor(clientId, clientName) {
super(clientId, clientName, new MusicClientProperties());
}
get properties() : MusicClientProperties {
return this._properties as MusicClientProperties;
}
showContextMenu(x: number, y: number, on_close: () => void = undefined): void {
spawnMenu(x, y,
{
name: "<b>Change bot name</b>",
icon: "client-change_nickname",
disabled: true,
callback: () => {},
type: MenuEntryType.ENTRY
}, {
name: "Change bot description",
icon: "client-edit",
disabled: true,
callback: () => {},
type: MenuEntryType.ENTRY
}, {
name: "Open music panel",
icon: "client-edit",
disabled: true,
callback: () => {},
type: MenuEntryType.ENTRY
},
MenuEntry.HR(),
{
name: "Delete bot",
icon: "client-delete",
disabled: true,
callback: () => {},
type: MenuEntryType.ENTRY
},
MenuEntry.CLOSE(on_close)
);
}
initializeListener(): void {
super.initializeListener();
}
}

View file

@ -447,7 +447,7 @@ class ChannelEntry {
createChatTag(braces: boolean = false) : JQuery { createChatTag(braces: boolean = false) : JQuery {
let tag = $.spawn("div"); let tag = $.spawn("div");
tag.css("display", "table"); tag.css("display", "inline-block");
tag.css("cursor", "pointer"); tag.css("cursor", "pointer");
tag.css("font-weight", "bold"); tag.css("font-weight", "bold");
tag.css("color", "darkblue"); tag.css("color", "darkblue");
@ -455,10 +455,15 @@ class ChannelEntry {
tag.text("\"" + this.channelName() + "\""); tag.text("\"" + this.channelName() + "\"");
else else
tag.text(this.channelName()); tag.text(this.channelName());
tag.attr("oncontextmenu", "chat_channel_contextmenu(this, ...arguments);"); tag.contextmenu(event => {
if(event.isDefaultPrevented()) return;
event.preventDefault();
this.showContextMenu(event.pageX, event.pageY);
});
tag.attr("channelId", this.channelId); tag.attr("channelId", this.channelId);
tag.attr("channelName", this.channelName()); tag.attr("channelName", this.channelName());
return tag.wrap("<p/>").parent(); return tag;
} }
channelType() : ChannelType { channelType() : ChannelType {

View file

@ -1,7 +1,18 @@
/// <reference path="channel.ts" /> /// <reference path="channel.ts" />
/// <reference path="modal/ModalChangeVolume.ts" /> /// <reference path="modal/ModalChangeVolume.ts" />
enum ClientType {
CLIENT_VOICE,
CLIENT_QUERY,
CLIENT_INTERNAL,
CLIENT_WEB,
CLIENT_MUSIC,
CLIENT_UNDEFINED
}
class ClientProperties { class ClientProperties {
client_type: ClientType = ClientType.CLIENT_VOICE; //TeamSpeaks type
client_type_exact: ClientType = ClientType.CLIENT_VOICE;
client_version: string = ""; client_version: string = "";
client_platform: string = ""; client_platform: string = "";
client_nickname: string = "unknown"; client_nickname: string = "unknown";
@ -14,32 +25,37 @@ class ClientProperties {
client_flag_avatar: string = ""; client_flag_avatar: string = "";
client_output_muted: boolean = false;
client_away_message: string = ""; client_away_message: string = "";
client_away: boolean = false; client_away: boolean = false;
client_input_hardware: boolean = false; client_input_hardware: boolean = false;
client_output_hardware: boolean = false;
client_input_muted: boolean = false; client_input_muted: boolean = false;
client_output_muted: boolean = false;
client_is_channel_commander: boolean = false; client_is_channel_commander: boolean = false;
client_teaforum_id: number = 0;
client_teaforum_name: string = "";
} }
class ClientEntry { class ClientEntry {
private _clientId: number; protected _clientId: number;
private _channel: ChannelEntry; protected _channel: ChannelEntry;
private _tag: JQuery<HTMLElement>; protected _tag: JQuery<HTMLElement>;
properties: ClientProperties = new ClientProperties(); protected _properties: ClientProperties;
private lastVariableUpdate: number = 0; protected lastVariableUpdate: number = 0;
private _speaking: boolean = false; protected _speaking: boolean = false;
channelTree: ChannelTree; channelTree: ChannelTree;
audioController: AudioController; audioController: AudioController;
constructor(clientId, clientName) { constructor(clientId, clientName, properties: ClientProperties = new ClientProperties()) {
this._properties = properties;
this._properties.client_nickname = clientName;
this._clientId = clientId; this._clientId = clientId;
this.properties.client_nickname = clientName;
this.channelTree = null; this.channelTree = null;
this._channel = null; this._channel = null;
this.audioController = new AudioController(); this.audioController = new AudioController();
@ -55,6 +71,10 @@ class ClientEntry {
this.audioController.initialize(); this.audioController.initialize();
} }
get properties() : ClientProperties {
return this._properties;
}
currentChannel() { return this._channel; } currentChannel() { return this._channel; }
clientNickName(){ return this.properties.client_nickname; } clientNickName(){ return this.properties.client_nickname; }
clientUid(){ return this.properties.client_unique_identifier; } clientUid(){ return this.properties.client_unique_identifier; }
@ -175,8 +195,16 @@ class ClientEntry {
type: MenuEntryType.ENTRY, type: MenuEntryType.ENTRY,
icon: "client-ban_client", icon: "client-ban_client",
name: "Ban client", name: "Ban client",
disabled: true, invalidPermission: !this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
callback: () => {} callback: () => {
Modals.spawnBanClient(this.properties.client_nickname, (duration, reason) => {
this.channelTree.client.serverConnection.sendCommand("banclient", {
uid: this.properties.client_unique_identifier,
banreason: reason,
time: duration
});
});
}
}, },
MenuEntry.HR(), MenuEntry.HR(),
{ {
@ -219,20 +247,30 @@ class ClientEntry {
static chatTag(id: number, name: string, uid: string, braces: boolean = false) : JQuery { static chatTag(id: number, name: string, uid: string, braces: boolean = false) : JQuery {
let tag = $.spawn("div"); let tag = $.spawn("div");
tag.css("cursor", "pointer"); tag.css("cursor", "pointer")
tag.css("font-weight", "bold"); .css("font-weight", "bold")
tag.css("color", "darkblue"); .css("color", "darkblue")
tag.css("display", "table"); .css("display", "inline-block")
.css("margin", 0);
if(braces) if(braces)
tag.text("\"" + name + "\""); tag.text("\"" + name + "\"");
else else
tag.text(name); tag.text(name);
tag.attr("oncontextmenu", "chat_client_contextmenu(this, ...arguments);");
tag.contextmenu(event => {
if(event.isDefaultPrevented()) return;
event.preventDefault();
let client = globalClient.channelTree.findClient(id);
if(!client) return;
if(client.properties.client_unique_identifier != uid) return;
client.showContextMenu(event.pageX, event.pageY);
});
tag.attr("clientId", id); tag.attr("clientId", id);
tag.attr("clientUid", uid); tag.attr("clientUid", uid);
tag.attr("clientName", name); tag.attr("clientName", name);
return tag.wrap("<p/>").parent(); return tag;
} }
createChatTag(braces: boolean = false) : JQuery { createChatTag(braces: boolean = false) : JQuery {
@ -384,7 +422,7 @@ class ClientEntry {
} }
calculateOnlineTime() : number { calculateOnlineTime() : number {
return new Date().getTime() / 1000 - this.properties.client_lastconnected; return Date.now() / 1000 - this.properties.client_lastconnected;
} }
avatarId?() : string { avatarId?() : string {
@ -507,22 +545,3 @@ class LocalClientEntry extends ClientEntry {
}); });
} }
} }
//Global functions
function chat_client_contextmenu(_element: any, event: any) {
event.preventDefault();
let element = $(_element);
console.log("Context menue for " + element.attr("clientName"));
let clid : number = Number.parseInt(element.attr("clientId"));
let client = globalClient.channelTree.findClient(clid);
if(!client) {
//TODO
return;
}
if(client.clientUid() != element.attr("clientUid")) {
//TODO
return;
}
client.showContextMenu(event.pageX, event.pageY);
}

View file

@ -0,0 +1,91 @@
/// <reference path="../../utils/modal.ts" />
/// <reference path="../../proto.ts" />
/// <reference path="../../client.ts" />
namespace Modals {
export function spawnBanClient(name: string, callback: (length: number, reason: string) => void) {
const connectModal = createModal({
header: function() {
return "Ban client";
},
body: function () {
let tag = $("#tmpl_client_ban").tmpl({
client_name: name
});
let maxTime = 0; //globalClient.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).value;
let unlimited = maxTime == 0 || maxTime == -1;
if(unlimited) maxTime = 0;
let banTag = tag.find(".ban_duration_type");
let durationTag = tag.find(".ban_duration");
banTag.find("option[value=\"sec\"]").prop("disabled", !unlimited && 1 > maxTime)
.attr("duration-scale", 1)
.attr("duration-max", maxTime);
banTag.find("option[value=\"min\"]").prop("disabled", !unlimited && 60 > maxTime)
.attr("duration-scale", 60)
.attr("duration-max", maxTime / 60);
banTag.find("option[value=\"hours\"]").prop("disabled", !unlimited && 60 * 60 > maxTime)
.attr("duration-scale", 60 * 60)
.attr("duration-max", maxTime / (60 * 60));
banTag.find("option[value=\"days\"]").prop("disabled", !unlimited && 60 * 60 * 24 > maxTime)
.attr("duration-scale", 60 * 60 * 24)
.attr("duration-max", maxTime / (60 * 60 * 24));
banTag.find("option[value=\"perm\"]").prop("disabled", !unlimited)
.attr("duration-scale", 0);
durationTag.change(() => banTag.trigger('change'));
banTag.change(event => {
let element = $((event.target as HTMLSelectElement).selectedOptions.item(0));
if(element.val() !== "perm") {
durationTag.prop("disabled", false);
let current = durationTag.val() as number;
let max = parseInt(element.attr("duration-max"));
if (max > 0 && current > max)
durationTag.val(max);
else if(current <= 0)
durationTag.val(1);
durationTag.attr("max", max);
} else {
durationTag.prop("disabled", true);
}
});
return tag;
},
footer: function () {
let tag = $.spawn("div");
tag.css("text-align", "right");
tag.css("margin-top", "3px");
tag.css("margin-bottom", "6px");
tag.addClass("modal-button-group");
let buttonCancel = $.spawn("button");
buttonCancel.text("Cancel");
buttonCancel.on("click", () => connectModal.close());
tag.append(buttonCancel);
let buttonOk = $.spawn("button");
buttonOk.text("OK").addClass("btn_success");
tag.append(buttonOk);
return tag;
},
width: 450
});
connectModal.open();
connectModal.htmlTag.find(".btn_success").on('click', () => {
connectModal.close();
let length = connectModal.htmlTag.find(".ban_duration").val() as number;
let duration = connectModal.htmlTag.find(".ban_duration_type option:selected");
console.log(duration);
console.log(length + "*" + duration.attr("duration-scale"));
callback(length * parseInt(duration.attr("duration-scale")), connectModal.htmlTag.find(".ban_reason").val() as string);
})
}
}

View file

@ -33,12 +33,11 @@ class ServerEntry {
tag.attr("id", "server"); tag.attr("id", "server");
tag.addClass("server"); tag.addClass("server");
tag.append($.spawn("div").addClass("server_type icon client-server_green")); tag.append($.spawn("div").addClass("server_type icon client-server_green"));
tag.append($.spawn("a").addClass("name").text(this.properties.virtualserver_name));
tag.append("<a class='name'>" + this.properties.virtualserver_name + "</a>");
const serverIcon = $("<span/>"); const serverIcon = $("<span/>");
//we cant spawn an icon on creation :) //we cant spawn an icon on creation :)
serverIcon.append("<div class='icon_property icon_empty'></div>"); serverIcon.append($.spawn("div").addClass("icon_property icon_empty"));
tag.append(serverIcon); tag.append(serverIcon);
return this._htmlTag = tag; return this._htmlTag = tag;
@ -89,7 +88,7 @@ class ServerEntry {
} }
shouldUpdateProperties() : boolean { shouldUpdateProperties() : boolean {
return this.nextInfoRequest < new Date().getTime(); return this.nextInfoRequest < Date.now();
} }
calculateUptime() : number { calculateUptime() : number {

View file

@ -166,9 +166,9 @@
<x-entry> <x-entry>
<x-tag>Voice</x-tag> <x-tag>Voice</x-tag>
<x-content> <x-content>
<div style="display: flex; flex-direction: column;" class="settings_voice"> <div class="settings_voice align_column">
<div> <div style="justify-content: right">
<a>Microphone:</a> <a style="margin-left: 20px">Microphone:</a>
<select class="voice_microphone_select"></select> <select class="voice_microphone_select"></select>
<hr> <hr>
</div> </div>
@ -217,5 +217,90 @@
<div class="display_volume" style="width: 60px; align-self: center; text-align: center">&plusmn;0 %</div> <div class="display_volume" style="width: 60px; align-self: center; text-align: center">&plusmn;0 %</div>
</div> </div>
</template> </template>
<template id="tmpl_client_ban">
<div class="align_column">
<div class="align_column" style="margin: 5px">
<a>Name:</a>
<input value="${client_name}" readonly>
</div>
<div class="align_column" style="margin: 5px">
<a>Reason:</a>
<textarea style="height: 32px; resize: vertical; max-height: 150px; min-height: 32px" maxlength="512" class="ban_reason"></textarea>
</div>
<div class="align_row" style="margin: 5px; justify-content: space-between">
<a>Duration:</a>
<div class="align_row">
<input type="number" value="1" class="ban_duration" style="margin-right: 7px" min="1">
<select class="ban_duration_type">
<option value="sec">seconds</option>
<option value="min">minutes</option>
<option value="hours">hours</option>
<option value="days">days</option>
<option value="perm">permanent</option>
</select>
</div>
</div>
</div>
</template>
<template id="tmpl_music_frame">
<div class="music-wrapper">
<div class="container">
<div class="left">
<div class="static-card">
<img src="${thumbnail}" alt="Thumbnail" class="thumbnail">
</div>
</div>
<div class="right">
<div class="controls hover">
<label class="btn-forward"><span></span></label>
<label class="btn-rewind"><span></span></label>
<label class="btn-settings"><span></span></label>
</div>
<div class="flip-card">
<img src="${thumbnail}" alt="Thumbnail" class="thumbnail">
</div>
</div>
</div>
<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;">
<defs>
<filter id="shadow">
<feDropShadow dx="4" dy="8" stdDeviation="4"/>
</filter>
</defs>
<polyline style="filter:url(#shadow);" class="button" points="0.6,0.3 3.9,3.4 0.6,6.6 "></polyline>
</svg>
<svg class="button" x="0px" y="0px" viewBox="0 0 4.5 6.9" style="enable-background:new 0 0 4.5 6.9;">
<defs>
<filter id="shadow">
<feDropShadow dx="4" dy="8" stdDeviation="4"/>
</filter>
</defs>
<g style="filter:url(#shadow);">
<line x1="0.4" y1="0.1" x2="0.4" y2="6.8"></line>
<line x1="4.1" y1="0.1" x2="4.1" y2="6.8"></line>
</g>
</svg>
</div>
<div class="timeline">
<div class="buffered"></div>
<div class="played"></div>
<div class="slider"></div>
</div>
</div>
</div>
</div>
</template>
<template id="tmpl_music_frame_empty">
<div class="music-wrapper empty">
<img src="img/music/empty_disk.svg">
<a>Not playing any music</a>
</div>
</template>
</body> </body>
</html> </html>