Property and channel tree improvements
This commit is contained in:
parent
09229a8e9d
commit
4689cdd4e0
21 changed files with 1056 additions and 235 deletions
|
@ -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;
|
||||
}
|
|
@ -30,11 +30,12 @@
|
|||
|
||||
#chat .message {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#chat .message *{
|
||||
display: inline-flex;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#chat .chats {
|
||||
|
|
|
@ -14,6 +14,7 @@ class AudioController {
|
|||
this._volume = 1;
|
||||
this._codecCache = [];
|
||||
this._timeIndex = 0;
|
||||
this._latencyBufferLength = 3;
|
||||
this.allowBuffering = true;
|
||||
this.speakerContext = AudioController.globalContext;
|
||||
this.onSpeaking = function () { };
|
||||
|
@ -37,7 +38,7 @@ class AudioController {
|
|||
playBuffer(buffer) {
|
||||
if (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);
|
||||
if (this.playerState == PlayerState.STOPPED || this.playerState == PlayerState.STOPPING) {
|
||||
console.log("[Audio] Starting new playback");
|
||||
|
@ -47,7 +48,7 @@ class AudioController {
|
|||
switch (this.playerState) {
|
||||
case PlayerState.PREBUFFERING:
|
||||
case PlayerState.BUFFERING:
|
||||
if (this.audioCache.length < 3) {
|
||||
if (this.audioCache.length <= this._latencyBufferLength) {
|
||||
if (this.playerState == PlayerState.BUFFERING) {
|
||||
if (this.allowBuffering)
|
||||
break;
|
||||
|
@ -119,9 +120,9 @@ class AudioController {
|
|||
return;
|
||||
this._volume = val;
|
||||
for (let buffer of this.audioCache)
|
||||
this.applayVolume(buffer);
|
||||
this.applyVolume(buffer);
|
||||
}
|
||||
applayVolume(buffer) {
|
||||
applyVolume(buffer) {
|
||||
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
|
||||
let data = buffer.getChannelData(channel);
|
||||
for (let sample = 0; sample < data.length; sample++) {
|
||||
|
@ -721,7 +722,6 @@ class CodecPool {
|
|||
else {
|
||||
this.entries[index].instance.initialise().then((flag) => {
|
||||
//TODO test success flag
|
||||
console.error(flag);
|
||||
this.ownCodec(clientId, false).then(resolve).catch(reject);
|
||||
}).catch(error => {
|
||||
console.error("Could not initialize codec!\nError: %o", error);
|
||||
|
@ -1409,7 +1409,7 @@ class ChannelEntry {
|
|||
}
|
||||
createChatTag(braces = false) {
|
||||
let tag = $.spawn("div");
|
||||
tag.css("display", "table");
|
||||
tag.css("display", "inline-block");
|
||||
tag.css("cursor", "pointer");
|
||||
tag.css("font-weight", "bold");
|
||||
tag.css("color", "darkblue");
|
||||
|
@ -1417,10 +1417,15 @@ class ChannelEntry {
|
|||
tag.text("\"" + this.channelName() + "\"");
|
||||
else
|
||||
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("channelName", this.channelName());
|
||||
return tag.wrap("<p/>").parent();
|
||||
return tag;
|
||||
}
|
||||
channelType() {
|
||||
if (this.properties.channel_flag_permanent == true)
|
||||
|
@ -1546,6 +1551,8 @@ class ClientProperties {
|
|||
this.client_input_hardware = false;
|
||||
this.client_input_muted = false;
|
||||
this.client_is_channel_commander = false;
|
||||
this.client_teaforum_id = 0;
|
||||
this.client_teaforum_name = "";
|
||||
}
|
||||
}
|
||||
class ClientEntry {
|
||||
|
@ -1676,8 +1683,16 @@ class ClientEntry {
|
|||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-ban_client",
|
||||
name: "Ban client",
|
||||
disabled: true,
|
||||
callback: () => { }
|
||||
invalidPermission: !this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
|
||||
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(), {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-volume",
|
||||
|
@ -1708,19 +1723,30 @@ class ClientEntry {
|
|||
}
|
||||
static chatTag(id, name, uid, braces = false) {
|
||||
let tag = $.spawn("div");
|
||||
tag.css("cursor", "pointer");
|
||||
tag.css("font-weight", "bold");
|
||||
tag.css("color", "darkblue");
|
||||
tag.css("display", "table");
|
||||
tag.css("cursor", "pointer")
|
||||
.css("font-weight", "bold")
|
||||
.css("color", "darkblue")
|
||||
.css("display", "inline-block")
|
||||
.css("margin", 0);
|
||||
if (braces)
|
||||
tag.text("\"" + name + "\"");
|
||||
else
|
||||
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("clientUid", uid);
|
||||
tag.attr("clientName", name);
|
||||
return tag.wrap("<p/>").parent();
|
||||
return tag;
|
||||
}
|
||||
createChatTag(braces = false) {
|
||||
return ClientEntry.chatTag(this.clientId(), this.clientNickName(), this.clientUid(), braces);
|
||||
|
@ -1865,7 +1891,7 @@ class ClientEntry {
|
|||
this.audioController = undefined;
|
||||
}
|
||||
calculateOnlineTime() {
|
||||
return new Date().getTime() / 1000 - this.properties.client_lastconnected;
|
||||
return Date.now() / 1000 - this.properties.client_lastconnected;
|
||||
}
|
||||
avatarId() {
|
||||
function str2ab(str) {
|
||||
|
@ -2988,30 +3014,11 @@ if (typeof (customElements) !== "undefined") {
|
|||
customElements.define('x-properties', X_Properties, { extends: 'div' });
|
||||
customElements.define('x-property', X_Property, { extends: 'div' });
|
||||
}
|
||||
class Settings {
|
||||
constructor() {
|
||||
this.cacheGlobal = {};
|
||||
this.cacheServer = {};
|
||||
this.updated = false;
|
||||
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);
|
||||
});
|
||||
class StaticSettings {
|
||||
static get instance() {
|
||||
if (!this._instance)
|
||||
this._instance = new StaticSettings(true);
|
||||
return this._instance;
|
||||
}
|
||||
static transformStO(input, _default) {
|
||||
if (typeof input === "undefined")
|
||||
|
@ -3037,24 +3044,68 @@ class Settings {
|
|||
return undefined;
|
||||
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) {
|
||||
let result = this.cacheGlobal[key];
|
||||
return Settings.transformStO(result, _default);
|
||||
return StaticSettings.transformStO(result, _default);
|
||||
}
|
||||
server(key, _default) {
|
||||
let result = this.cacheServer[key];
|
||||
return Settings.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);
|
||||
return StaticSettings.transformStO(result, _default);
|
||||
}
|
||||
changeGlobal(key, value) {
|
||||
if (this.cacheGlobal[key] == value)
|
||||
return;
|
||||
this.updated = true;
|
||||
this.cacheGlobal[key] = Settings.transformOtS(value);
|
||||
this.cacheGlobal[key] = StaticSettings.transformOtS(value);
|
||||
if (Settings.UPDATE_DIRECT)
|
||||
this.save();
|
||||
}
|
||||
|
@ -3062,7 +3113,7 @@ class Settings {
|
|||
if (this.cacheServer[key] == value)
|
||||
return;
|
||||
this.updated = true;
|
||||
this.cacheServer[key] = Settings.transformOtS(value);
|
||||
this.cacheServer[key] = StaticSettings.transformOtS(value);
|
||||
if (Settings.UPDATE_DIRECT)
|
||||
this.save();
|
||||
}
|
||||
|
@ -3090,11 +3141,6 @@ class Settings {
|
|||
let global = JSON.stringify(this.cacheGlobal);
|
||||
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_UNLOAD_DIALOG = "disableUnloadDialog";
|
||||
|
@ -3108,12 +3154,16 @@ class InfoBar {
|
|||
this._htmlTag = htmlTag;
|
||||
}
|
||||
createInfoTable(infos) {
|
||||
let table = $("<table/>");
|
||||
for (let e in infos) {
|
||||
console.log("Display info " + e);
|
||||
let entry = $("<tr/>");
|
||||
entry.append("<td class='info_key'>" + e + ":</td>");
|
||||
entry.append("<td>" + infos[e] + "</td>");
|
||||
let table = $.spawn("table");
|
||||
for (let key in infos) {
|
||||
console.log("Display info " + key);
|
||||
let entry = $.spawn("tr");
|
||||
entry.append($.spawn("td").addClass("info_key").html(key + ":"));
|
||||
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);
|
||||
}
|
||||
return table;
|
||||
|
@ -3187,7 +3237,7 @@ class InfoBar {
|
|||
else if (this._currentSelected instanceof ChannelEntry) {
|
||||
let props = this._currentSelected.properties;
|
||||
this._htmlTag.append(this.createInfoTable({
|
||||
"Name": this._currentSelected.createChatTag().html(),
|
||||
"Name": this._currentSelected.createChatTag(),
|
||||
"Topic": this._currentSelected.properties.channel_topic,
|
||||
"Codec": this._currentSelected.properties.channel_codec,
|
||||
"Codec Quality": this._currentSelected.properties.channel_codec_quality,
|
||||
|
@ -3198,19 +3248,22 @@ class InfoBar {
|
|||
}));
|
||||
}
|
||||
else if (this._currentSelected instanceof ClientEntry) {
|
||||
this._currentSelected.updateVariables();
|
||||
this._currentSelected.updateClientVariables();
|
||||
let version = this._currentSelected.properties.client_version;
|
||||
if (!version)
|
||||
version = "";
|
||||
let infos = {
|
||||
"Name": this._currentSelected.createChatTag().html(),
|
||||
"Name": this._currentSelected.createChatTag(),
|
||||
"Description": this._currentSelected.properties.client_description,
|
||||
"Version": "<a title='" + ChatMessage.formatMessage(version) + "'>" + version.split(" ")[0] + "</a>" + " on " + this._currentSelected.properties.client_platform,
|
||||
"Online since": "<a class='online'>" + formatDate(this._currentSelected.calculateOnlineTime()) + "</a>",
|
||||
"Version": MessageHelper.formatMessage("{0} on {1}", $.spawn("a").attr("title", version).text(version.split(" ")[0]), this._currentSelected.properties.client_platform),
|
||||
"Online since": $.spawn("a").addClass("online").text(formatDate(this._currentSelected.calculateOnlineTime())),
|
||||
"Volume": this._currentSelected.audioController.volume * 100 + " %"
|
||||
};
|
||||
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"]);
|
||||
if (this._currentSelected.properties.client_teaforum_id > 0) {
|
||||
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));
|
||||
{
|
||||
|
@ -3264,13 +3317,17 @@ class InfoBar {
|
|||
.css("margin-left", "10px")
|
||||
.css("align-items", "center");
|
||||
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);
|
||||
}
|
||||
this._htmlTag.append(channelGroup);
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -4628,14 +4685,14 @@ class AvatarManager {
|
|||
let img = $.spawn("img");
|
||||
img.attr("alt", "");
|
||||
let avatar = this.resolveCached(client);
|
||||
avatar = undefined;
|
||||
if (avatar) {
|
||||
img.attr("src", "data:image/png;base64," + avatar.base64);
|
||||
tag.append(img);
|
||||
}
|
||||
else {
|
||||
img.attr("src", "file://null");
|
||||
let loader = $.spawn("div");
|
||||
loader.addClass("avatar_loading");
|
||||
let loader = $.spawn("img");
|
||||
loader.attr("src", "img/loading_image.svg").css("width", "75%");
|
||||
tag.append(loader);
|
||||
this.loadAvatar(client).then(avatar => {
|
||||
img.attr("src", "data:image/png;base64," + avatar.base64);
|
||||
|
@ -4649,7 +4706,7 @@ class AvatarManager {
|
|||
}).catch(reason => {
|
||||
console.error("Could not load avatar for " + client.clientNickName() + ". Reason: " + reason);
|
||||
//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;
|
||||
|
@ -4748,6 +4805,73 @@ var ChatType;
|
|||
ChatType[ChatType["CHANNEL"] = 2] = "CHANNEL";
|
||||
ChatType[ChatType["CLIENT"] = 3] = "CLIENT";
|
||||
})(ChatType || (ChatType = {}));
|
||||
var MessageHelper;
|
||||
(function (MessageHelper) {
|
||||
function htmlEscape(message) {
|
||||
const div = document.createElement('div');
|
||||
div.innerText = message;
|
||||
message = div.innerHTML;
|
||||
return message.replace(/ /g, ' ');
|
||||
}
|
||||
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 {
|
||||
constructor(message) {
|
||||
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.css("margin-right", "4px");
|
||||
dateTag.css("color", "dodgerblue");
|
||||
let messageTag = $.spawn("div");
|
||||
messageTag.html(this.message);
|
||||
messageTag.css("color", "blue");
|
||||
this._htmlTag = tag;
|
||||
tag.append(dateTag);
|
||||
tag.append(messageTag);
|
||||
this.message.forEach(e => e.appendTo(tag));
|
||||
tag.hide();
|
||||
return tag;
|
||||
}
|
||||
static formatMessage(message) {
|
||||
/*
|
||||
message = message
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/\n/g, "<br/>");
|
||||
*/
|
||||
const div = document.createElement('div');
|
||||
div.innerText = message;
|
||||
message = div.innerHTML;
|
||||
console.log(message + "->" + div.innerHTML);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
class ChatEntry {
|
||||
constructor(handle, type, key) {
|
||||
|
@ -4800,25 +4909,15 @@ class ChatEntry {
|
|||
this.onClose = function () { return true; };
|
||||
}
|
||||
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) {
|
||||
let parms = [];
|
||||
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]);
|
||||
}
|
||||
let msg = fmt ? ChatMessage.formatMessage(message) : message;
|
||||
msg = msg.format(parms);
|
||||
let elm = new ChatMessage(msg);
|
||||
this.history.push(elm);
|
||||
this.pushChatMessage(new ChatMessage(MessageHelper.formatMessage(message, ...args)));
|
||||
}
|
||||
pushChatMessage(entry) {
|
||||
this.history.push(entry);
|
||||
while (this.history.length > 100) {
|
||||
let elm = this.history.pop_front();
|
||||
elm.htmlTag.animate({ opacity: 0 }, 200, function () {
|
||||
|
@ -4829,8 +4928,8 @@ class ChatEntry {
|
|||
let box = $(this.handle.htmlTag).find(".messages");
|
||||
let mbox = $(this.handle.htmlTag).find(".message_box");
|
||||
let bottom = box.scrollTop() + box.height() + 1 >= mbox.height();
|
||||
mbox.append(elm.htmlTag);
|
||||
elm.htmlTag.show().css("opacity", "0").animate({ opacity: 1 }, 100);
|
||||
mbox.append(entry.htmlTag);
|
||||
entry.htmlTag.show().css("opacity", "0").animate({ opacity: 1 }, 100);
|
||||
if (bottom)
|
||||
box.scrollTop(mbox.height());
|
||||
}
|
||||
|
@ -5341,6 +5440,88 @@ var Modals;
|
|||
Identity isnt valid!
|
||||
</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"/>
|
||||
class BasicCodec {
|
||||
constructor(codecSampleRate) {
|
||||
|
@ -5561,6 +5742,7 @@ class CodecWrapper extends BasicCodec {
|
|||
/// <reference path="utils/modal.ts" />
|
||||
/// <reference path="ui/modal/ModalConnect.ts" />
|
||||
/// <reference path="ui/modal/ModalCreateChannel.ts" />
|
||||
/// <reference path="ui/modal/ModalBanClient.ts" />
|
||||
/// <reference path="codec/CodecWrapper.ts" />
|
||||
/// <reference path="settings.ts" />
|
||||
/// <reference path="log.ts" />
|
||||
|
@ -5569,7 +5751,9 @@ let globalClient;
|
|||
let chat;
|
||||
let forumIdentity;
|
||||
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
|
||||
//disableUnloadDialog=1&default_connect_type=forum&default_connect_url=localhost&loader_ignore_age=1
|
||||
AudioController.initializeAudioController();
|
||||
if (!TSIdentityHelper.setup()) {
|
||||
console.error("Could not setup the TeamSpeak identity parser!");
|
||||
|
@ -5595,7 +5779,7 @@ function main() {
|
|||
//Modals.spawnSettingsModal();
|
||||
//Modals.createChannelModal(undefined);
|
||||
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);
|
||||
}
|
||||
else
|
||||
|
@ -5683,10 +5867,10 @@ class ServerEntry {
|
|||
tag.attr("id", "server");
|
||||
tag.addClass("server");
|
||||
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/>");
|
||||
//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);
|
||||
return this._htmlTag = tag;
|
||||
}
|
||||
|
@ -5728,7 +5912,7 @@ class ServerEntry {
|
|||
this.channelTree.client.serverConnection.sendCommand("servergetvariables");
|
||||
}
|
||||
shouldUpdateProperties() {
|
||||
return this.nextInfoRequest < new Date().getTime();
|
||||
return this.nextInfoRequest < Date.now();
|
||||
}
|
||||
calculateUptime() {
|
||||
if (this.properties.virtualserver_uptime == 0 || this.lastInfoRequest == 0)
|
||||
|
|
File diff suppressed because one or more lines are too long
2
generated/js/client.min.js
vendored
2
generated/js/client.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -103,4 +103,5 @@
|
|||
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"
|
||||
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 |
11
index.php
11
index.php
|
@ -30,6 +30,7 @@
|
|||
<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/loader.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/music/info_plate.css" type="text/css">
|
||||
|
||||
<!-- PHP generated properies -->
|
||||
<!-- 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 id="control_bar" class="control_bar">
|
||||
<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 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>
|
||||
|
@ -117,9 +119,7 @@
|
|||
<div style="flex-direction: row; height: 100%; width: 100%; display: flex">
|
||||
<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 class="channelTree" id="channelTree">
|
||||
<div class="server l"><div class="icon client-server_green"></div> TeaSpeak web!</div>
|
||||
</div>
|
||||
<div class="channelTree" id="channelTree"></div>
|
||||
</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 id="chat">
|
||||
|
@ -136,7 +136,7 @@
|
|||
</div> <!-- Chat window -->
|
||||
</div>
|
||||
<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> <!-- Selection info -->
|
||||
</div>
|
||||
|
@ -148,6 +148,9 @@
|
|||
<div id="scripts">
|
||||
<script src="js/load.js"></script>
|
||||
</div>
|
||||
|
||||
<div id="music-test"></div>
|
||||
<div style="height: 100px"></div>
|
||||
</body>
|
||||
<footer>
|
||||
<div class="container">
|
||||
|
|
|
@ -425,14 +425,13 @@ class AvatarManager {
|
|||
img.attr("alt", "");
|
||||
|
||||
let avatar = this.resolveCached(client);
|
||||
avatar = undefined;
|
||||
if(avatar) {
|
||||
img.attr("src", "data:image/png;base64," + avatar.base64);
|
||||
tag.append(img);
|
||||
} else {
|
||||
img.attr("src", "file://null");
|
||||
|
||||
let loader = $.spawn("div");
|
||||
loader.addClass("avatar_loading");
|
||||
let loader = $.spawn("img");
|
||||
loader.attr("src", "img/loading_image.svg").css("width", "75%");
|
||||
tag.append(loader);
|
||||
|
||||
this.loadAvatar(client).then(avatar => {
|
||||
|
@ -448,7 +447,7 @@ class AvatarManager {
|
|||
}).catch(reason => {
|
||||
console.error("Could not load avatar for " + client.clientNickName() + ". Reason: " + reason);
|
||||
//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());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -16,13 +16,17 @@ class InfoBar {
|
|||
|
||||
|
||||
private createInfoTable(infos: any) : JQuery<HTMLElement> {
|
||||
let table = $("<table/>");
|
||||
let table = $.spawn("table");
|
||||
|
||||
for(let e in infos) {
|
||||
console.log("Display info " + e);
|
||||
let entry = $("<tr/>");
|
||||
entry.append("<td class='info_key'>" + e + ":</td>");
|
||||
entry.append("<td>" + infos[e] + "</td>");
|
||||
for(let key in infos) {
|
||||
console.log("Display info " + key);
|
||||
let entry = $.spawn("tr");
|
||||
entry.append($.spawn("td").addClass("info_key").html(key + ":"));
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -106,7 +110,7 @@ class InfoBar {
|
|||
} else if(this._currentSelected instanceof ChannelEntry) {
|
||||
let props = this._currentSelected.properties;
|
||||
this._htmlTag.append(this.createInfoTable({
|
||||
"Name": this._currentSelected.createChatTag().html(),
|
||||
"Name": this._currentSelected.createChatTag(),
|
||||
"Topic": this._currentSelected.properties.channel_topic,
|
||||
"Codec": this._currentSelected.properties.channel_codec,
|
||||
"Codec Quality": this._currentSelected.properties.channel_codec_quality,
|
||||
|
@ -115,20 +119,35 @@ class InfoBar {
|
|||
"Subscription Status": "unknown",
|
||||
"Voice Data Encryption": "unknown"
|
||||
}));
|
||||
} else if(this._currentSelected instanceof ClientEntry) {
|
||||
this._currentSelected.updateVariables();
|
||||
} else if(this._currentSelected instanceof MusicClientEntry) {
|
||||
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;
|
||||
if(!version) version = "";
|
||||
let infos = {
|
||||
"Name": this._currentSelected.createChatTag().html(),
|
||||
"Name": this._currentSelected.createChatTag(),
|
||||
"Description": this._currentSelected.properties.client_description,
|
||||
"Version": "<a title='" + ChatMessage.formatMessage(version) + "'>" + version.split(" ")[0] + "</a>" + " on " + this._currentSelected.properties.client_platform,
|
||||
"Online since": "<a class='online'>" + formatDate(this._currentSelected.calculateOnlineTime()) + "</a>",
|
||||
"Version": MessageHelper.formatMessage("{0} on {1}", $.spawn("a").attr("title", version).text(version.split(" ")[0]), this._currentSelected.properties.client_platform),
|
||||
"Online since": $.spawn("a").addClass("online").text(formatDate(this._currentSelected.calculateOnlineTime())),
|
||||
"Volume": this._currentSelected.audioController.volume * 100 + " %"
|
||||
};
|
||||
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"]);
|
||||
if(this._currentSelected.properties.client_teaforum_id > 0) {
|
||||
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));
|
||||
|
||||
|
@ -169,7 +188,8 @@ class InfoBar {
|
|||
let channelGroup = $.spawn("div");
|
||||
channelGroup
|
||||
.css("display", "flex")
|
||||
.css("flex-direction", "column");
|
||||
.css("flex-direction", "column")
|
||||
.css("margin-bottom", "20px");
|
||||
|
||||
let header = $.spawn("div");
|
||||
header
|
||||
|
@ -189,15 +209,42 @@ class InfoBar {
|
|||
.css("margin-left", "10px")
|
||||
.css("align-items", "center");
|
||||
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);
|
||||
|
||||
}
|
||||
this._htmlTag.append(channelGroup);
|
||||
}
|
||||
|
||||
if(this._currentSelected.properties.client_flag_avatar.length > 0)
|
||||
this.handle.fileManager.avatars.generateTag(this._currentSelected).appendTo(this._htmlTag);
|
||||
{
|
||||
if(this._currentSelected.properties.client_flag_avatar.length > 0)
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
117
js/chat.ts
117
js/chat.ts
|
@ -5,12 +5,79 @@ enum ChatType {
|
|||
CLIENT
|
||||
}
|
||||
|
||||
namespace MessageHelper {
|
||||
export function htmlEscape(message: string) : string {
|
||||
const div = document.createElement('div');
|
||||
div.innerText = message;
|
||||
message = div.innerHTML;
|
||||
return message.replace(/ /g, ' ');
|
||||
}
|
||||
|
||||
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 {
|
||||
date: Date;
|
||||
message: string;
|
||||
message: JQuery[];
|
||||
private _htmlTag: JQuery<HTMLElement>;
|
||||
|
||||
constructor(message) {
|
||||
constructor(message: JQuery[]) {
|
||||
this.date = new Date();
|
||||
this.message = message;
|
||||
}
|
||||
|
@ -32,29 +99,12 @@ class ChatMessage {
|
|||
dateTag.css("margin-right", "4px");
|
||||
dateTag.css("color", "dodgerblue");
|
||||
|
||||
let messageTag = $.spawn("div");
|
||||
messageTag.html(this.message);
|
||||
messageTag.css("color", "blue");
|
||||
|
||||
this._htmlTag = tag;
|
||||
tag.append(dateTag);
|
||||
tag.append(messageTag);
|
||||
this.message.forEach(e => e.appendTo(tag));
|
||||
tag.hide();
|
||||
return tag;
|
||||
}
|
||||
|
||||
static formatMessage(message: string) : string {
|
||||
/*
|
||||
message = message
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/\n/g, "<br/>");
|
||||
*/
|
||||
const div = document.createElement('div');
|
||||
div.innerText = message;
|
||||
message = div.innerHTML;
|
||||
console.log(message + "->" + div.innerHTML);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
class ChatEntry {
|
||||
|
@ -81,24 +131,17 @@ class ChatEntry {
|
|||
}
|
||||
|
||||
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) {
|
||||
let parms: any[] = [];
|
||||
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]);
|
||||
}
|
||||
let msg : string = fmt ? ChatMessage.formatMessage(message) : message;
|
||||
msg = msg.format(parms);
|
||||
let elm = new ChatMessage(msg);
|
||||
this.history.push(elm);
|
||||
this.pushChatMessage(new ChatMessage(MessageHelper.formatMessage(message, ...args)));
|
||||
}
|
||||
|
||||
private pushChatMessage(entry: ChatMessage) {
|
||||
this.history.push(entry);
|
||||
while(this.history.length > 100) {
|
||||
let elm = this.history.pop_front();
|
||||
elm.htmlTag.animate({opacity: 0}, 200, function () {
|
||||
|
@ -109,8 +152,8 @@ class ChatEntry {
|
|||
let box = $(this.handle.htmlTag).find(".messages");
|
||||
let mbox = $(this.handle.htmlTag).find(".message_box");
|
||||
let bottom : boolean = box.scrollTop() + box.height() + 1 >= mbox.height();
|
||||
mbox.append(elm.htmlTag);
|
||||
elm.htmlTag.show().css("opacity", "0").animate({opacity: 1}, 100);
|
||||
mbox.append(entry.htmlTag);
|
||||
entry.htmlTag.show().css("opacity", "0").animate({opacity: 1}, 100);
|
||||
if(bottom) box.scrollTop(mbox.height());
|
||||
} else {
|
||||
this.unread = true;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/// <reference path="ui/channel.ts" />
|
||||
/// <reference path="client.ts" />
|
||||
/// <reference path="ui/MusicClient.ts" />
|
||||
|
||||
class CommandResult {
|
||||
success: boolean;
|
||||
|
@ -466,7 +467,11 @@ class ConnectionCommandHandler {
|
|||
client = tree.findClient(json["clid"]);
|
||||
|
||||
if(!client) {
|
||||
client = new ClientEntry(parseInt(json["clid"]), json["client_nickname"]);
|
||||
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 = tree.insertClient(client, channel);
|
||||
} else {
|
||||
if(client == this.connection._client.getClient())
|
||||
|
@ -500,6 +505,9 @@ class ConnectionCommandHandler {
|
|||
}
|
||||
|
||||
client.updateVariables(...updates);
|
||||
|
||||
if(client instanceof LocalClientEntry)
|
||||
this.connection._client.controlBar.updateVoice();
|
||||
}
|
||||
|
||||
handleCommandClientLeftView(json) {
|
||||
|
@ -575,22 +583,24 @@ class ConnectionCommandHandler {
|
|||
if(!channel_from) //Not critical
|
||||
console.error("Unknown client move (Channel from)!");
|
||||
|
||||
if(client instanceof LocalClientEntry) {
|
||||
let self = client instanceof LocalClientEntry;
|
||||
if(self) {
|
||||
chat.channelChat().name = channel_to.channelName();
|
||||
for(let entry of client.channelTree.clientsByChannel(client.currentChannel()))
|
||||
if(entry !== client) entry.getAudioController().stopAudio(true);
|
||||
this.connection._client.controlBar.updateVoice(channel_to);
|
||||
}
|
||||
tree.moveClient(client, channel_to);
|
||||
|
||||
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),
|
||||
channel_from ? channel_from.createChatTag(true) : undefined,
|
||||
channel_to.createChatTag(true),
|
||||
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"])
|
||||
);
|
||||
} 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),
|
||||
channel_from ? channel_from.createChatTag(true) : undefined,
|
||||
channel_to.createChatTag(true)
|
||||
|
|
|
@ -145,8 +145,10 @@ function loadDebug() {
|
|||
"js/ui/modal/ModalCreateChannel.js",
|
||||
"js/ui/modal/ModalConnect.js",
|
||||
"js/ui/modal/ModalChangeVolume.js",
|
||||
"js/ui/modal/ModalBanClient.js",
|
||||
"js/ui/channel.js",
|
||||
"js/ui/client.js",
|
||||
"js/ui/MusicClient.js",
|
||||
"js/ui/server.js",
|
||||
"js/ui/view.js",
|
||||
"js/ui/ControlBar.js",
|
||||
|
|
12
js/main.ts
12
js/main.ts
|
@ -4,6 +4,7 @@
|
|||
/// <reference path="utils/modal.ts" />
|
||||
/// <reference path="ui/modal/ModalConnect.ts" />
|
||||
/// <reference path="ui/modal/ModalCreateChannel.ts" />
|
||||
/// <reference path="ui/modal/ModalBanClient.ts" />
|
||||
/// <reference path="codec/CodecWrapper.ts" />
|
||||
/// <reference path="settings.ts" />
|
||||
/// <reference path="log.ts" />
|
||||
|
@ -16,6 +17,7 @@ let forumIdentity: TeaForumIdentity;
|
|||
|
||||
function main() {
|
||||
//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();
|
||||
if(!TSIdentityHelper.setup()) { console.error("Could not setup the TeamSpeak identity parser!"); return; }
|
||||
|
||||
|
@ -43,11 +45,19 @@ function main() {
|
|||
//Modals.createChannelModal(undefined);
|
||||
|
||||
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);
|
||||
} else
|
||||
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());
|
|
@ -17,6 +17,8 @@ class ControlBar {
|
|||
private _away: boolean;
|
||||
private _awayMessage: string;
|
||||
|
||||
private _codecNotSupported: boolean = true;
|
||||
|
||||
readonly handle: TSClient;
|
||||
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() {
|
||||
Modals.spawnSettingsModal();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -447,7 +447,7 @@ class ChannelEntry {
|
|||
createChatTag(braces: boolean = false) : JQuery {
|
||||
let tag = $.spawn("div");
|
||||
|
||||
tag.css("display", "table");
|
||||
tag.css("display", "inline-block");
|
||||
tag.css("cursor", "pointer");
|
||||
tag.css("font-weight", "bold");
|
||||
tag.css("color", "darkblue");
|
||||
|
@ -455,10 +455,15 @@ class ChannelEntry {
|
|||
tag.text("\"" + this.channelName() + "\"");
|
||||
else
|
||||
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("channelName", this.channelName());
|
||||
return tag.wrap("<p/>").parent();
|
||||
return tag;
|
||||
}
|
||||
|
||||
channelType() : ChannelType {
|
||||
|
|
|
@ -1,7 +1,18 @@
|
|||
/// <reference path="channel.ts" />
|
||||
/// <reference path="modal/ModalChangeVolume.ts" />
|
||||
|
||||
enum ClientType {
|
||||
CLIENT_VOICE,
|
||||
CLIENT_QUERY,
|
||||
CLIENT_INTERNAL,
|
||||
CLIENT_WEB,
|
||||
CLIENT_MUSIC,
|
||||
CLIENT_UNDEFINED
|
||||
}
|
||||
|
||||
class ClientProperties {
|
||||
client_type: ClientType = ClientType.CLIENT_VOICE; //TeamSpeaks type
|
||||
client_type_exact: ClientType = ClientType.CLIENT_VOICE;
|
||||
client_version: string = "";
|
||||
client_platform: string = "";
|
||||
client_nickname: string = "unknown";
|
||||
|
@ -14,32 +25,37 @@ class ClientProperties {
|
|||
|
||||
client_flag_avatar: string = "";
|
||||
|
||||
client_output_muted: boolean = false;
|
||||
|
||||
client_away_message: string = "";
|
||||
client_away: boolean = false;
|
||||
|
||||
|
||||
client_input_hardware: boolean = false;
|
||||
client_output_hardware: boolean = false;
|
||||
client_input_muted: boolean = false;
|
||||
client_output_muted: boolean = false;
|
||||
client_is_channel_commander: boolean = false;
|
||||
|
||||
client_teaforum_id: number = 0;
|
||||
client_teaforum_name: string = "";
|
||||
}
|
||||
|
||||
class ClientEntry {
|
||||
private _clientId: number;
|
||||
private _channel: ChannelEntry;
|
||||
private _tag: JQuery<HTMLElement>;
|
||||
protected _clientId: number;
|
||||
protected _channel: ChannelEntry;
|
||||
protected _tag: JQuery<HTMLElement>;
|
||||
|
||||
properties: ClientProperties = new ClientProperties();
|
||||
private lastVariableUpdate: number = 0;
|
||||
private _speaking: boolean = false;
|
||||
protected _properties: ClientProperties;
|
||||
protected lastVariableUpdate: number = 0;
|
||||
protected _speaking: boolean = false;
|
||||
|
||||
channelTree: ChannelTree;
|
||||
audioController: AudioController;
|
||||
|
||||
constructor(clientId, clientName) {
|
||||
constructor(clientId, clientName, properties: ClientProperties = new ClientProperties()) {
|
||||
this._properties = properties;
|
||||
this._properties.client_nickname = clientName;
|
||||
this._clientId = clientId;
|
||||
this.properties.client_nickname = clientName;
|
||||
this.channelTree = null;
|
||||
this._channel = null;
|
||||
this.audioController = new AudioController();
|
||||
|
@ -55,6 +71,10 @@ class ClientEntry {
|
|||
this.audioController.initialize();
|
||||
}
|
||||
|
||||
get properties() : ClientProperties {
|
||||
return this._properties;
|
||||
}
|
||||
|
||||
currentChannel() { return this._channel; }
|
||||
clientNickName(){ return this.properties.client_nickname; }
|
||||
clientUid(){ return this.properties.client_unique_identifier; }
|
||||
|
@ -175,8 +195,16 @@ class ClientEntry {
|
|||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-ban_client",
|
||||
name: "Ban client",
|
||||
disabled: true,
|
||||
callback: () => {}
|
||||
invalidPermission: !this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
|
||||
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(),
|
||||
{
|
||||
|
@ -219,20 +247,30 @@ class ClientEntry {
|
|||
static chatTag(id: number, name: string, uid: string, braces: boolean = false) : JQuery {
|
||||
let tag = $.spawn("div");
|
||||
|
||||
tag.css("cursor", "pointer");
|
||||
tag.css("font-weight", "bold");
|
||||
tag.css("color", "darkblue");
|
||||
tag.css("display", "table");
|
||||
tag.css("cursor", "pointer")
|
||||
.css("font-weight", "bold")
|
||||
.css("color", "darkblue")
|
||||
.css("display", "inline-block")
|
||||
.css("margin", 0);
|
||||
|
||||
if(braces)
|
||||
tag.text("\"" + name + "\"");
|
||||
else
|
||||
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("clientUid", uid);
|
||||
tag.attr("clientName", name);
|
||||
return tag.wrap("<p/>").parent();
|
||||
return tag;
|
||||
}
|
||||
|
||||
createChatTag(braces: boolean = false) : JQuery {
|
||||
|
@ -384,7 +422,7 @@ class ClientEntry {
|
|||
}
|
||||
|
||||
calculateOnlineTime() : number {
|
||||
return new Date().getTime() / 1000 - this.properties.client_lastconnected;
|
||||
return Date.now() / 1000 - this.properties.client_lastconnected;
|
||||
}
|
||||
|
||||
avatarId?() : string {
|
||||
|
@ -506,23 +544,4 @@ 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);
|
||||
}
|
|
@ -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);
|
||||
})
|
||||
}
|
||||
}
|
|
@ -33,12 +33,11 @@ class ServerEntry {
|
|||
tag.attr("id", "server");
|
||||
tag.addClass("server");
|
||||
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/>");
|
||||
//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);
|
||||
|
||||
return this._htmlTag = tag;
|
||||
|
@ -89,7 +88,7 @@ class ServerEntry {
|
|||
}
|
||||
|
||||
shouldUpdateProperties() : boolean {
|
||||
return this.nextInfoRequest < new Date().getTime();
|
||||
return this.nextInfoRequest < Date.now();
|
||||
}
|
||||
|
||||
calculateUptime() : number {
|
||||
|
|
|
@ -166,9 +166,9 @@
|
|||
<x-entry>
|
||||
<x-tag>Voice</x-tag>
|
||||
<x-content>
|
||||
<div style="display: flex; flex-direction: column;" class="settings_voice">
|
||||
<div>
|
||||
<a>Microphone:</a>
|
||||
<div class="settings_voice align_column">
|
||||
<div style="justify-content: right">
|
||||
<a style="margin-left: 20px">Microphone:</a>
|
||||
<select class="voice_microphone_select"></select>
|
||||
<hr>
|
||||
</div>
|
||||
|
@ -217,5 +217,90 @@
|
|||
<div class="display_volume" style="width: 60px; align-self: center; text-align: center">±0 %</div>
|
||||
</div>
|
||||
</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>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue