A lots of updates :)

canary
WolverinDEV 2018-11-04 00:39:29 +01:00
parent bbbd8f6365
commit 89a29a8717
65 changed files with 920 additions and 42 deletions

View File

@ -2,6 +2,15 @@
* **03.11.18** * **03.11.18**
- Reworked on the basic overlay sizes - Reworked on the basic overlay sizes
- Added hostbanner to the UI - Added hostbanner to the UI
- Implemented sounds
- Implemented poke notification
- Added local volume change to music bots
- Added move and channel kick to the music bot context menu options
- Reworked private chat format
- Added several server chat messages
- Fixed client double click chat opening
- Implemented client drag and drop
- Fixed channel select within sub channels
* **28.10.18** * **28.10.18**
- Restructured the project - Restructured the project

View File

@ -35,6 +35,22 @@
"path" => "css/", "path" => "css/",
"local-path" => "./shared/css/" "local-path" => "./shared/css/"
], ],
[ /* shared sound files */
"type" => "wav",
"search-pattern" => "/.*\.wav$/",
"build-target" => "dev|rel",
"path" => "audio/",
"local-path" => "./shared/audio/"
],
[ /* shared data sound files */
"type" => "json",
"search-pattern" => "/.*\.json/",
"build-target" => "dev|rel",
"path" => "audio/",
"local-path" => "./shared/audio/"
],
[ /* shared image files */ [ /* shared image files */
"type" => "img", "type" => "img",
"search-pattern" => "/.*\.(svg|png)/", "search-pattern" => "/.*\.(svg|png)/",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
[{"file": "sound.test.wav", "key": "sound.test"}, {"file": "sound.egg.wav", "key": "sound.egg"}, {"file": "away_activated.wav", "key": "away_activated"}, {"file": "away_deactivated.wav", "key": "away_deactivated"}, {"file": "connection.connected.wav", "key": "connection.connected"}, {"file": "connection.disconnected.wav", "key": "connection.disconnected"}, {"file": "connection.disconnected.timeout.wav", "key": "connection.disconnected.timeout"}, {"file": "connection.refused.wav", "key": "connection.refused"}, {"file": "connection.banned.wav", "key": "connection.banned"}, {"file": "server.edited.wav", "key": "server.edited"}, {"file": "server.edited.self.wav", "key": "server.edited.self"}, {"file": "server.kicked.wav", "key": "server.kicked"}, {"file": "channel.kicked.wav", "key": "channel.kicked"}, {"file": "channel.moved.wav", "key": "channel.moved"}, {"file": "channel.joined.wav", "key": "channel.joined"}, {"file": "channel.created.wav", "key": "channel.created"}, {"file": "channel.edited.wav", "key": "channel.edited"}, {"file": "channel.edited.self.wav", "key": "channel.edited.self"}, {"file": "channel.deleted.wav", "key": "channel.deleted"}, {"file": "user.moved.wav", "key": "user.moved"}, {"file": "user.moved.self.wav", "key": "user.moved.self"}, {"file": "user.poked.self.wav", "key": "user.poked.self"}, {"file": "user.banned.wav", "key": "user.banned"}, {"file": "user.joined.wav", "key": "user.joined"}, {"file": "user.joined.moved.wav", "key": "user.joined.moved"}, {"file": "user.joined.kicked.wav", "key": "user.joined.kicked"}, {"file": "user.joined.connect.wav", "key": "user.joined.connect"}, {"file": "user.left.wav", "key": "user.left"}, {"file": "user.left.kicked.channel.wav", "key": "user.left.kicked.channel"}, {"file": "user.left.kicked.server.wav", "key": "user.left.kicked.server"}, {"file": "user.left.moved.wav", "key": "user.left.moved"}, {"file": "user.left.disconnect.wav", "key": "user.left.disconnect"}, {"file": "user.left.banned.wav", "key": "user.left.banned"}, {"file": "error.insufficient_permissions.wav", "key": "error.insufficient_permissions"}]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,49 @@
#Sound test
sound.test;This is a test sound
sound.egg;WolverinDEV is the best and I love TeaSpeak!
#Away
away_activated;See you soon
away_deactivated;Welcome back
#Connection
connection.connected;Connected
connection.disconnected;Disconnected
connection.disconnected.timeout;Connection to server lost
connection.refused;Connect failed
connection.banned;You got banned from this server
#Server
server.edited;Server edited
server.edited.self;You edited the server
server.kicked;You got kicked from the server
#Channel activity
channel.kicked;You got kicked from the channel
channel.moved;Channel moved
channel.joined;Channel joined
channel.created;Channel created
channel.edited;Channel edited
channel.edited.self;You edited the channel
channel.deleted;Channel deleted
#User action
user.moved;User moved
user.moved.self;You have been moved
user.poked.self;Hey wakeup!
user.banned;User banned
user.joined;User joined your channel
user.joined.moved;User got moved to your channel
user.joined.kicked;User got kicked to your channel
user.joined.connect;User connected to your channel
user.left;User left your channel
user.left.kicked.channel;User in your channel got kicked from the channel
user.left.kicked.server;User in your channel got kicked from the server
user.left.moved;User was moved out of your channel
user.left.disconnect;User disconnected from your channel
user.left.banned;User in your channel was banned from the server
#Error
error.insufficient_permissions;insufficient permissions
1 #Sound test
2 sound.test;This is a test sound
3 sound.egg;WolverinDEV is the best and I love TeaSpeak!
4 #Away
5 away_activated;See you soon
6 away_deactivated;Welcome back
7 #Connection
8 connection.connected;Connected
9 connection.disconnected;Disconnected
10 connection.disconnected.timeout;Connection to server lost
11 connection.refused;Connect failed
12 connection.banned;You got banned from this server
13 #Server
14 server.edited;Server edited
15 server.edited.self;You edited the server
16 server.kicked;You got kicked from the server
17 #Channel activity
18 channel.kicked;You got kicked from the channel
19 channel.moved;Channel moved
20 channel.joined;Channel joined
21 channel.created;Channel created
22 channel.edited;Channel edited
23 channel.edited.self;You edited the channel
24 channel.deleted;Channel deleted
25 #User action
26 user.moved;User moved
27 user.moved.self;You have been moved
28 user.poked.self;Hey wakeup!
29 user.banned;User banned
30 user.joined;User joined your channel
31 user.joined.moved;User got moved to your channel
32 user.joined.kicked;User got kicked to your channel
33 user.joined.connect;User connected to your channel
34 user.left;User left your channel
35 user.left.kicked.channel;User in your channel got kicked from the channel
36 user.left.kicked.server;User in your channel got kicked from the server
37 user.left.moved;User was moved out of your channel
38 user.left.disconnect;User disconnected from your channel
39 user.left.banned;User in your channel was banned from the server
40 #Error
41 error.insufficient_permissions;insufficient permissions

View File

@ -13,6 +13,7 @@
display: inline-flex; display: inline-flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
flex-grow: 1;
.button-update { .button-update {
width: 100%; width: 100%;
@ -80,13 +81,14 @@
.container-select-info { .container-select-info {
flex-grow: 1; flex-grow: 1;
flex-shrink: 1; flex-shrink: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: stretch; justify-content: stretch;
> div { .select_server {
flex-grow: 1; > div {
flex-grow: 1;
}
} }
} }
} }

View File

@ -277,6 +277,7 @@ footer .container {
} }
min-width: 100px; min-width: 100px;
max-width: 60%; /* tmp chat fix */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex-grow: 1; flex-grow: 1;
@ -314,4 +315,25 @@ footer .container {
width: 40%; width: 40%;
border-radius: 0 0 2px 0; border-radius: 0 0 2px 0;
} }
}
#mouse-move {
display: none;
position: absolute;
.container {
position: relative;
display: block;
border: 2px solid gray;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
}
}
html, body {
min-height: 500px;
min-width: 500px;
overflow: hidden;
} }

View File

@ -0,0 +1,35 @@
.container-poke {
display: flex;
flex-direction: column;
.container-information {
text-align: center;
}
.container-message {
text-align: center;
.message {
color: green;
}
}
.button-close {
margin-top: 5px;
width: 150px;
float: right;
}
}
/*
<script class="jsrender-template" id="tmpl_poke_popup" type="text/html">
<div class="container-poke">
<div class="container-information">
<a>You have been poked by </a><node key="invoker"></node><a>:</a>
</div>
<div class="container-message">
<a class="message">{{>message}}</a>
</div>
</div>
</script>
*/

View File

@ -122,6 +122,10 @@
flex-direction: row; flex-direction: row;
top: 0px; top: 0px;
left: 16px; left: 16px;
&.move-selected {
border: 1px black solid;
}
} }

79
shared/generate_voice.py Normal file
View File

@ -0,0 +1,79 @@
"""
This should be executed as python 2.7 (because of pydub)
"""
import os
import requests
import json
import csv
import shutil
from pydub import AudioSegment
TARGET_DIRECTORY = "audio/speech"
SOURCE_FILE = "audio/speech_sentences.csv"
def tts(text, file):
voice_id = 4
language_id = 1
req = requests.post(
'https://kfiuqykx63.execute-api.us-east-1.amazonaws.com/Dev/tts?r={}&s={}&l=0&v=aca'.format(voice_id,
language_id),
stream=True,
headers={
'origin': 'https://www.naturalreaders.com',
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/69.0.3497.100 Safari/537.36 OPR/56.0.3051.52',
'content-type': 'application/x-www-form-urlencoded',
'referer': 'https://www.naturalreaders.com/online/',
'authority': 'kfiuqykx63.execute-api.us-east-1.amazonaws.com'
},
data=json.dumps({"t": text})
)
if req.status_code != 200:
raise ValueError("Invalid response code {}".format(req.status_code))
with open(file + ".mp3", "wb") as fstream:
for chunk in req.iter_content(chunk_size=128):
fstream.write(chunk)
fstream.close()
sound = AudioSegment.from_mp3(file + ".mp3")
sound.export(file, format="wav")
os.remove(file + ".mp3")
def main():
if os.path.exists(TARGET_DIRECTORY):
print("Deleting old speach directory (%s)!" % TARGET_DIRECTORY)
try:
shutil.rmtree(TARGET_DIRECTORY)
except e:
print("Cant delete old dir!")
os.makedirs(TARGET_DIRECTORY)
mapping = []
with open(SOURCE_FILE, 'r') as input:
reader = csv.reader(filter(lambda row: len(row) != 0 and row[0] != '#', input), delimiter=';', quotechar='#')
for row in reader:
if len(row) != 2:
continue
print("Generating speech for {}: {}".format(row[0], row[1]))
try:
file = "{}.wav".format(row[0])
tts(row[1], TARGET_DIRECTORY + "/" + file)
mapping.append({'key': row[0], 'file': file})
except e:
print(e)
print("Failed to generate {}", row[0])
with open("audio/speech/mapping.json", "w") as fstream:
fstream.write(json.dumps(mapping))
fstream.close()
pass
if __name__ == "__main__":
main()

View File

@ -53,6 +53,7 @@
<link rel="stylesheet" href="css/modal-banlist.css" type="text/css"> <link rel="stylesheet" href="css/modal-banlist.css" type="text/css">
<link rel="stylesheet" href="css/modal-bancreate.css" type="text/css"> <link rel="stylesheet" href="css/modal-bancreate.css" type="text/css">
<link rel="stylesheet" href="css/modal-settings.css" type="text/css"> <link rel="stylesheet" href="css/modal-settings.css" type="text/css">
<link rel="stylesheet" href="css/modal-poke.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"> <link rel="stylesheet" href="css/music/info_plate.css" type="text/css">
<link rel="stylesheet" href="css/frame/SelectInfo.css" type="text/css"> <link rel="stylesheet" href="css/frame/SelectInfo.css" type="text/css">
@ -145,6 +146,11 @@
<div id="music-test"></div> <div id="music-test"></div>
<div id="templates"></div> <div id="templates"></div>
<div id="sounds"></div>
<div id="mouse-move">
<div class="container">
</div>
</div>
</body> </body>
<?php <?php

View File

@ -431,7 +431,7 @@
</x-entry> </x-entry>
<x-entry> <x-entry>
<x-tag>Transfers</x-tag> <x-tag>Transfers</x-tag>
<x-content>TeaSpeak does not yet support these variables</x-content> <x-content>This needs to be implemented :)</x-content>
</x-entry> </x-entry>
<x-entry> <x-entry>
<x-tag>Anti-Flood</x-tag> <x-tag>Anti-Flood</x-tag>
@ -1322,9 +1322,7 @@
</script> </script>
<script class="jsrender-template" id="tmpl_selected_hostbanner" type="text/html"> <script class="jsrender-template" id="tmpl_selected_hostbanner" type="text/html">
<div class="hostbanner"> <div class="hostbanner">
{{if property_virtualserver_hostbanner_url}} <a href="{{:property_virtualserver_hostbanner_url}}" style="display: flex; flex-direction: row; justify-content: center;">
<a href="{{:property_virtualserver_hostbanner_url}}">
{{/if}}
<img src=" <img src="
{{:property_virtualserver_hostbanner_gfx_url}} {{:property_virtualserver_hostbanner_gfx_url}}
@ -1348,9 +1346,7 @@
alt="Host banner" alt="Host banner"
> >
{{if property_virtualserver_hostbanner_url}}
</a> </a>
{{/if}}
</div> </div>
</script> </script>
<script class="jsrender-template" id="tmpl_selected_server" type="text/html"> <script class="jsrender-template" id="tmpl_selected_server" type="text/html">
@ -1463,5 +1459,16 @@
<node key="bbcode_channel_description"></node> <node key="bbcode_channel_description"></node>
</div> </div>
</script> </script>
<script class="jsrender-template" id="tmpl_poke_popup" type="text/html">
<div class="container-poke">
<div class="container-information">
<a>You have been poked by </a><node key="invoker"></node><a>:</a>
</div>
<div class="container-message">
<a class="message">{{>message}}</a>
</div>
<button class="button-close">close</button>
</div>
</script>
</body> </body>
</html> </html>

View File

@ -65,7 +65,8 @@ namespace MessageHelper {
console.warn("Message to format contains invalid index (" + number + ")"); console.warn("Message to format contains invalid index (" + number + ")");
result.push(...this.formatElement(objects[number])); result.push(...this.formatElement(objects[number]));
begin = found = found + 2 + offset; found = found + 1 + offset;
begin = found + 1;
console.log("Offset: " + offset + " Number: " + number); console.log("Offset: " + offset + " Number: " + number);
} while(found++); } while(found++);
@ -237,6 +238,11 @@ class ChatEntry {
return tag; return tag;
} }
focus() {
this.handle.activeChat = this;
this.handle.htmlTag.find(".input_box").focus();
}
set name(newName : string) { set name(newName : string) {
console.log("Change name!"); console.log("Change name!");
this._name = newName; this._name = newName;

View File

@ -98,7 +98,7 @@ class TSClient {
helpers.hashPassword(password.password).then(password => { helpers.hashPassword(password.password).then(password => {
this.serverConnection.startConnection({host, port}, new HandshakeHandler(identity, name, password)); this.serverConnection.startConnection({host, port}, new HandshakeHandler(identity, name, password));
}).catch(error => { }).catch(error => {
createErrorModal("Error while hasing password", "Faield to hash server password!").open(); createErrorModal("Error while hashing password", "Failed to hash server password!<br>" + error).open();
}) })
} else } else
this.serverConnection.startConnection({host, port}, new HandshakeHandler(identity, name, password ? password.password : undefined)); this.serverConnection.startConnection({host, port}, new HandshakeHandler(identity, name, password ? password.password : undefined));
@ -182,6 +182,7 @@ class TSClient {
"Click <a href='" + this.certAcceptUrl() + "'>here</a> to accept the remote certificate" "Click <a href='" + this.certAcceptUrl() + "'>here</a> to accept the remote certificate"
).open(); ).open();
} }
sound.play(Sound.CONNECTION_REFUSED);
break; break;
case DisconnectReason.CONNECTION_CLOSED: case DisconnectReason.CONNECTION_CLOSED:
console.error("Lost connection to remote server!"); console.error("Lost connection to remote server!");
@ -189,9 +190,11 @@ class TSClient {
"Connection closed", "Connection closed",
"The connection was closed by remote host" "The connection was closed by remote host"
).open(); ).open();
sound.play(Sound.CONNECTION_DISCONNECTED);
break; break;
case DisconnectReason.CONNECTION_PING_TIMEOUT: case DisconnectReason.CONNECTION_PING_TIMEOUT:
console.error("Connection ping timeout"); console.error("Connection ping timeout");
sound.play(Sound.CONNECTION_DISCONNECTED_TIMEOUT);
createErrorModal( createErrorModal(
"Connection lost", "Connection lost",
"Lost connection to remote host (Ping timeout)<br>Even possible?" "Lost connection to remote host (Ping timeout)<br>Even possible?"
@ -204,6 +207,7 @@ class TSClient {
"The server is closed.<br>" + "The server is closed.<br>" +
"Reason: " + data.reasonmsg "Reason: " + data.reasonmsg
).open(); ).open();
sound.play(Sound.CONNECTION_DISCONNECTED);
break; break;
case DisconnectReason.SERVER_REQUIRES_PASSWORD: case DisconnectReason.SERVER_REQUIRES_PASSWORD:
chat.serverChat().appendError("Server requires password"); chat.serverChat().appendError("Server requires password");
@ -214,6 +218,19 @@ class TSClient {
this.serverConnection._handshakeHandler.name, this.serverConnection._handshakeHandler.name,
{password: password as string, hashed: false}); {password: password as string, hashed: false});
}).open(); }).open();
break;
case DisconnectReason.CLIENT_KICKED:
chat.serverChat().appendError("You got kicked from the server by {0}{1}",
ClientEntry.chatTag(data["invokerid"], data["invokername"], data["invokeruid"]),
data["reasonmsg"] ? " (" + data["reasonmsg"] + ")" : "");
sound.play(Sound.SERVER_KICKED);
break;
case DisconnectReason.CLIENT_BANNED:
chat.serverChat().appendError("You got banned from the server by {0}{1}",
ClientEntry.chatTag(data["invokerid"], data["invokername"], data["invokeruid"]),
data["reasonmsg"] ? " (" + data["reasonmsg"] + ")" : "");
sound.play(Sound.CONNECTION_BANNED); //TODO findout if it was a disconnect or a connect refuse
break;
default: default:
console.error("Got uncaught disconnect!"); console.error("Got uncaught disconnect!");
console.error("Type: " + type + " Data:"); console.error("Type: " + type + " Data:");

View File

@ -1,5 +1,7 @@
/// <reference path="ui/channel.ts" /> /// <reference path="ui/channel.ts" />
/// <reference path="client.ts" /> /// <reference path="client.ts" />
/// <reference path="sound/Sounds.ts" />
/// <reference path="ui/modal/ModalPoke.ts" />
class CommandResult { class CommandResult {
success: boolean; success: boolean;
@ -227,6 +229,7 @@ class ServerConnection {
if(res.id == 2568) { //Permission error if(res.id == 2568) { //Permission error
res.message = "Insufficient client permissions. Failed on permission " + this._client.permissions.resolveInfo(res.json["failed_permid"] as number).name; res.message = "Insufficient client permissions. Failed on permission " + this._client.permissions.resolveInfo(res.json["failed_permid"] as number).name;
chat.serverChat().appendError("Insufficient client permissions. Failed on permission {}", this._client.permissions.resolveInfo(res.json["failed_permid"] as number).name); chat.serverChat().appendError("Insufficient client permissions. Failed on permission {}", this._client.permissions.resolveInfo(res.json["failed_permid"] as number).name);
sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
} else { } else {
chat.serverChat().appendError(res.extra_message.length == 0 ? res.message : res.extra_message); chat.serverChat().appendError(res.extra_message.length == 0 ? res.message : res.extra_message);
} }
@ -473,6 +476,8 @@ class ConnectionCommandHandler {
this["notifyserveredited"] = this.handleNotifyServerEdited; this["notifyserveredited"] = this.handleNotifyServerEdited;
this["notifyserverupdated"] = this.handleNotifyServerUpdated; this["notifyserverupdated"] = this.handleNotifyServerUpdated;
this["notifyclientpoke"] = this.handleNotifyClientPoke;
this["notifymusicplayerinfo"] = this.handleNotifyMusicPlayerInfo; this["notifymusicplayerinfo"] = this.handleNotifyMusicPlayerInfo;
} }
@ -524,6 +529,7 @@ class ConnectionCommandHandler {
chat.serverChat().name = this.connection._client.channelTree.server.properties["virtualserver_name"]; chat.serverChat().name = this.connection._client.channelTree.server.properties["virtualserver_name"];
chat.serverChat().appendMessage("Connected as {0}", true, this.connection._client.getClient().createChatTag(true)); chat.serverChat().appendMessage("Connected as {0}", true, this.connection._client.getClient().createChatTag(true));
sound.play(Sound.CONNECTION_CONNECTED);
globalClient.onConnected(); globalClient.onConnected();
} }
@ -637,14 +643,42 @@ class ConnectionCommandHandler {
chat.channelChat().name = channel.channelName(); chat.channelChat().name = channel.channelName();
tree.moveClient(client, channel); tree.moveClient(client, channel);
} }
const own_channel = this.connection._client.getClient().currentChannel();
if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) { if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
if(own_channel == channel)
if(old_channel)
sound.play(Sound.USER_ENTERED);
else
sound.play(Sound.USER_ENTERED_CONNECT);
if(old_channel) { if(old_channel) {
chat.serverChat().appendMessage("{0} appeared from {1} to {2}", true, client.createChatTag(true), old_channel.createChatTag(true), channel.createChatTag(true)); chat.serverChat().appendMessage("{0} appeared from {1} to {2}", true, client.createChatTag(true), old_channel.createChatTag(true), channel.createChatTag(true));
} else { } else {
chat.serverChat().appendMessage("{0} connected to channel {1}", true, client.createChatTag(true), channel.createChatTag(true)); chat.serverChat().appendMessage("{0} connected to channel {1}", true, client.createChatTag(true), channel.createChatTag(true));
} }
} else if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
if(own_channel == channel)
sound.play(Sound.USER_ENTERED_MOVED);
chat.serverChat().appendMessage("{0} appeared from {1} to {2}, moved by {3}", true,
client.createChatTag(true),
old_channel ? old_channel.createChatTag(true) : undefined,
channel.createChatTag(true),
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
);
} else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
if(own_channel == channel)
sound.play(Sound.USER_ENTERED_KICKED);
chat.serverChat().appendMessage("{0} appeared from {1} to {2}, kicked by {3}{4}", true,
client.createChatTag(true),
old_channel ? old_channel.createChatTag(true) : undefined,
channel.createChatTag(true),
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
json["reasonmsg"] > 0 ? " (" + json["msg"] + ")" : ""
);
} else {
console.warn("Unknown reasonid for " + json["reasonid"]);
} }
let updates: { let updates: {
@ -678,44 +712,69 @@ class ConnectionCommandHandler {
return 0; return 0;
} }
if(client == this.connection._client.getClient()) { if(client == this.connection._client.getClient()) {
if(json["reasonid"] == ViewReasonId.VREASON_BAN) if(json["reasonid"] == ViewReasonId.VREASON_BAN) {
this.connection._client.handleDisconnect(DisconnectReason.CLIENT_BANNED, json); this.connection._client.handleDisconnect(DisconnectReason.CLIENT_BANNED, json);
else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_KICK) } else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_KICK) {
this.connection._client.handleDisconnect(DisconnectReason.CLIENT_KICKED, json); this.connection._client.handleDisconnect(DisconnectReason.CLIENT_KICKED, json);
else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_SHUTDOWN) } else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_SHUTDOWN) {
this.connection._client.handleDisconnect(DisconnectReason.SERVER_CLOSED, json); this.connection._client.handleDisconnect(DisconnectReason.SERVER_CLOSED, json);
else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_STOPPED) } else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_STOPPED) {
this.connection._client.handleDisconnect(DisconnectReason.SERVER_CLOSED, json); this.connection._client.handleDisconnect(DisconnectReason.SERVER_CLOSED, json);
else } else
this.connection._client.handleDisconnect(DisconnectReason.UNKNOWN, json); this.connection._client.handleDisconnect(DisconnectReason.UNKNOWN, json);
return; return;
} }
const own_channel = this.connection._client.getClient().currentChannel();
let channel_from = tree.findChannel(json["cfid"]); let channel_from = tree.findChannel(json["cfid"]);
let channel_to = tree.findChannel(json["ctid"]); let channel_to = tree.findChannel(json["ctid"]);
if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) { if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
chat.serverChat().appendMessage("{0} disappeared from {1} to {2}", true, client.createChatTag(true), channel_from.createChatTag(true), channel_to.createChatTag(true)); chat.serverChat().appendMessage("{0} disappeared from {1} to {2}", true, client.createChatTag(true), channel_from.createChatTag(true), channel_to.createChatTag(true));
if(channel_from == own_channel)
sound.play(Sound.USER_LEFT);
} else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_LEFT) { } else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_LEFT) {
chat.serverChat().appendMessage("{0} left the server ({1})", true, client.createChatTag(true), json["reasonmsg"]); chat.serverChat().appendMessage("{0} left the server{1}", true,
client.createChatTag(true),
json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
);
if(channel_from == own_channel)
sound.play(Sound.USER_LEFT_DISCONNECT);
} else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_KICK) { } else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_KICK) {
chat.serverChat().appendError("{0} was kicked from the server by {1}. ({2})", chat.serverChat().appendError("{0} was kicked from the server by {1}.{2}",
client.createChatTag(true), client.createChatTag(true),
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]), ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
json["reasonmsg"] json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
); );
if(channel_from == own_channel)
sound.play(Sound.USER_LEFT_KICKED_SERVER);
} else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
chat.serverChat().appendError("{0} was kicked from your channel by {1}.{2}",
client.createChatTag(true),
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
);
if(channel_from == own_channel)
sound.play(Sound.USER_LEFT_KICKED_CHANNEL);
} else if(json["reasonid"] == ViewReasonId.VREASON_BAN) { } else if(json["reasonid"] == ViewReasonId.VREASON_BAN) {
//"Mulus" was banned for 1 second from the server by "WolverinDEV" (Sry brauchte kurz ein opfer :P <3 (Nohomo)) //"Mulus" was banned for 1 second from the server by "WolverinDEV" (Sry brauchte kurz ein opfer :P <3 (Nohomo))
let duration = "permanently"; let duration = "permanently";
if(json["bantime"]) if(json["bantime"])
duration = "for " + formatDate(Number.parseInt(json["bantime"])); duration = "for " + formatDate(Number.parseInt(json["bantime"]));
chat.serverChat().appendError("{0} was banned {1} by {2}. ({3})",
chat.serverChat().appendError("{0} was banned {1} by {2}.{3}",
client.createChatTag(true), client.createChatTag(true),
duration, duration,
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]), ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
json["reasonmsg"] json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
); );
if(channel_from == own_channel)
sound.play(Sound.USER_LEFT_BANNED);
} else { } else {
console.error("Unknown client left reason!"); console.error("Unknown client left reason!");
} }
@ -749,8 +808,10 @@ class ConnectionCommandHandler {
if(entry !== client) entry.getAudioController().stopAudio(true); if(entry !== client) entry.getAudioController().stopAudio(true);
this.connection._client.controlBar.updateVoice(channel_to); this.connection._client.controlBar.updateVoice(channel_to);
} }
tree.moveClient(client, channel_to); tree.moveClient(client, channel_to);
const own_channel = this.connection._client.getClient().currentChannel();
if(json["reasonid"] == ViewReasonId.VREASON_MOVED) { if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
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, 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),
@ -758,12 +819,39 @@ class ConnectionCommandHandler {
channel_to.createChatTag(true), channel_to.createChatTag(true),
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]) ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"])
); );
if(self)
sound.play(Sound.USER_MOVED_SELF);
else if(own_channel == channel_to)
sound.play(Sound.USER_ENTERED_MOVED);
else if(own_channel == channel_from)
sound.play(Sound.USER_LEFT_MOVED);
} else if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) { } else if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
chat.serverChat().appendMessage(self ? "You switched from channel {1} to {2}" : "{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)
); );
if(self) {} //If we do an action we wait for the error response
else if(own_channel == channel_to)
sound.play(Sound.USER_ENTERED);
else if(own_channel == channel_from)
sound.play(Sound.USER_LEFT);
} else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
chat.serverChat().appendMessage(self ? "You got kicked out of the channel {1} to channel {2} by {3}{4}" : "{0} got kicked from channel {1} to {2} by {3}{4}", true,
client.createChatTag(true),
channel_from ? channel_from.createChatTag(true) : undefined,
channel_to.createChatTag(true),
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
(json["reasonmsg"] || "").length > 0 ? " (" + json["msg"] + ")" : ""
);
if(self) {
sound.play(Sound.CHANNEL_KICKED);
} else if(own_channel == channel_to)
sound.play(Sound.USER_ENTERED_KICKED);
else if(own_channel == channel_from)
sound.play(Sound.USER_LEFT_KICKED_CHANNEL);
} else {
console.warn("Unknown reason id " + json["reasonid"]);
} }
} }
@ -836,14 +924,20 @@ class ConnectionCommandHandler {
return; return;
} }
if(invoker == this.connection._client.getClient()) { if(invoker == this.connection._client.getClient()) {
target.chat(true).appendMessage("<< " + json["msg"]); sound.play(Sound.MESSAGE_SEND, { background_notification: true });
target.chat(true).appendMessage("{0}: {1}", true, this.connection._client.getClient().createChatTag(true), json["msg"]);
} else { } else {
invoker.chat(true).appendMessage(">> " + json["msg"]); sound.play(Sound.MESSAGE_RECEIVED, { background_notification: true });
invoker.chat(true).appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), json["msg"]);
} }
} else if(mode == 2) { } else if(mode == 2) {
chat.channelChat().appendMessage("{0} >> {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), json["msg"]) if(json["invokerid"] == this.connection._client.clientId)
sound.play(Sound.MESSAGE_SEND, { background_notification: true });
else
sound.play(Sound.MESSAGE_RECEIVED, { background_notification: true });
chat.channelChat().appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), json["msg"])
} else if(mode == 3) { } else if(mode == 3) {
chat.serverChat().appendMessage("{0} >> {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), json["msg"]); chat.serverChat().appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), json["msg"]);
} }
} }
@ -921,4 +1015,15 @@ class ConnectionCommandHandler {
bot.handlePlayerInfo(json); bot.handlePlayerInfo(json);
} }
handleNotifyClientPoke(json) {
json = json[0];
Modals.spawnPoke({
id: parseInt(json["invokerid"]),
name: json["invokername"],
unique_id: json["invokeruid"]
}, json["msg"]);
sound.play(Sound.USER_POKED_SELF);
}
} }

View File

@ -394,7 +394,7 @@ namespace sha {
export function sha1(message: string | ArrayBuffer) : PromiseLike<ArrayBuffer> { export function sha1(message: string | ArrayBuffer) : PromiseLike<ArrayBuffer> {
let buffer = message instanceof ArrayBuffer ? message : encode_text(message as string); let buffer = message instanceof ArrayBuffer ? message : encode_text(message as string);
if(/Edge/.test(navigator.userAgent)) if(!crypto || !crypto.subtle || !crypto.subtle.digest || /Edge/.test(navigator.userAgent))
return new Promise<ArrayBuffer>(resolve => { return new Promise<ArrayBuffer>(resolve => {
resolve(_sha1.arrayBuffer(buffer as ArrayBuffer)); resolve(_sha1.arrayBuffer(buffer as ArrayBuffer));
}); });

View File

@ -129,6 +129,8 @@ function loadDebug() {
//Load general API's //Load general API's
"js/log.js", "js/log.js",
"js/sound/Sounds.js",
"js/utils/modal.js", "js/utils/modal.js",
"js/utils/tab.js", "js/utils/tab.js",
"js/utils/helpers.js", "js/utils/helpers.js",
@ -147,6 +149,7 @@ function loadDebug() {
"js/ui/modal/ModalBanCreate.js", "js/ui/modal/ModalBanCreate.js",
"js/ui/modal/ModalBanList.js", "js/ui/modal/ModalBanList.js",
"js/ui/modal/ModalYesNo.js", "js/ui/modal/ModalYesNo.js",
"js/ui/modal/ModalPoke.js",
"js/ui/modal/ModalPermissionEdit.js", "js/ui/modal/ModalPermissionEdit.js",
"js/ui/modal/ModalServerGroupDialog.js", "js/ui/modal/ModalServerGroupDialog.js",
@ -154,6 +157,7 @@ function loadDebug() {
"js/ui/client.js", "js/ui/client.js",
"js/ui/server.js", "js/ui/server.js",
"js/ui/view.js", "js/ui/view.js",
"js/ui/client_move.js",
"js/ui/frames/SelectedItemInfo.js", "js/ui/frames/SelectedItemInfo.js",
"js/ui/frames/ControlBar.js", "js/ui/frames/ControlBar.js",

View File

@ -152,8 +152,14 @@ function main() {
*/ */
setup_close(); setup_close();
let _resize_timeout: NodeJS.Timer;
$(window).on('resize', () => { $(window).on('resize', () => {
globalClient.channelTree.handle_resized(); if(_resize_timeout)
clearTimeout(_resize_timeout);
_resize_timeout = setTimeout(() => {
globalClient.channelTree.handle_resized();
}, 1000);
}); });
} }

View File

@ -121,7 +121,7 @@ if(typeof ($) !== "undefined") {
result = $(result); result = $(result);
} }
result.find("node").each((index, element) => { result.find("node").each((index, element) => {
$(element).replaceWith(values[$(element).attr("key")] || values[0][$(element).attr("key")]); $(element).replaceWith(values[$(element).attr("key")] || (values[0] || [])[$(element).attr("key")]);
}); });
return result; return result;
} }

212
shared/js/sound/Sounds.ts Normal file
View File

@ -0,0 +1,212 @@
enum Sound {
SOUND_TEST = "sound.test",
SOUND_EGG = "sound.egg",
AWAY_ACTIVATED = "away_activated",
AWAY_DEACTIVATED = "away_deactivated",
CONNECTION_CONNECTED = "connection.connected",
CONNECTION_DISCONNECTED = "connection.disconnected",
CONNECTION_BANNED = "connection.banned",
CONNECTION_DISCONNECTED_TIMEOUT = "connection.disconnected.timeout",
CONNECTION_REFUSED = "connection.refused",
SERVER_EDITED = "server.edited",
SERVER_EDITED_SELF = "server.edited.self",
SERVER_KICKED = "server.kicked",
CHANNEL_CREATED = "channel.created",
CHANNEL_MOVED = "channel.moved",
CHANNEL_EDITED = "channel.edited",
CHANNEL_EDITED_SELF = "channel.edited.self",
CHANNEL_DELETED = "channel.deleted",
CHANNEL_JOINED = "channel.joined",
CHANNEL_KICKED = "channel.kicked", //You got kicked from the channel
USER_MOVED = "user.moved", //User moved
USER_MOVED_SELF = "user.moved.self", //You were moved
USER_POKED_SELF = "user.poked.self", //Hey wakeup
USER_BANNED = "user.banned",
USER_ENTERED = "user.joined", //User joined your channel
USER_ENTERED_MOVED = "user.joined.moved", //User was moved to your channel
USER_ENTERED_KICKED = "user.joined.kicked", //User was kicked to your channel
USER_ENTERED_CONNECT = "user.joined.connect",
USER_LEFT = "user.left", //User left your channel
USER_LEFT_MOVED = "user.left.moved", //User was move out of your channel
USER_LEFT_KICKED_CHANNEL = "user.left.kicked.server", //User was kicked out of your channel
USER_LEFT_KICKED_SERVER = "user.left.kicked.channel", //User is your channel was kicked from the server
USER_LEFT_DISCONNECT = "user.left.disconnect",
USER_LEFT_BANNED = "user.left.banned",
ERROR_INSUFFICIENT_PERMISSIONS = "error.insufficient_permissions",
MESSAGE_SEND = "message.send",
MESSAGE_RECEIVED = "message.received"
}
namespace sound {
interface SpeechFile {
key: string;
filename: string;
not_supported?: boolean;
not_supported_timeout?: number;
cached?: AudioBuffer;
node?: HTMLAudioElement;
}
let warned = false
let speech_mapping: {[key: string]:SpeechFile} = {};
function register_sound(key: string, file: string) {
speech_mapping[key] = {key: key, filename: file} as SpeechFile;
}
export function initialize() : Promise<void> {
$.ajaxSetup({
beforeSend: function(jqXHR,settings){
if (settings.dataType === 'binary'){
console.log("Settins binary");
settings.xhr().responseType = 'arraybuffer';
settings.processData = false;
}
}
});
register_sound("message.received", "effects/message_received.wav");
register_sound("message.send", "effects/message_send.wav");
return new Promise<void>(resolve => {
$.ajax({
url: "audio/speech/mapping.json",
success: response => {
for(const entry of response)
register_sound(entry.key, "speech/" + entry.file);
resolve();
},
error: () => {
console.log("error!");
console.dir(...arguments);
},
timeout: 5000,
async: true,
type: 'GET'
});
})
}
function str2ab(str) {
var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
var bufView = new Uint16Array(buf);
for (var i = 0, strLen=str.length; i<strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
export function play(sound: Sound, options?: {
background_notification?: boolean
}) {
console.log("playback sound " + sound);
const file: SpeechFile = speech_mapping[sound];
if(!file) {
console.warn("Missing sound " + sound);
return;
}
if(file.not_supported) {
if(!file.not_supported_timeout || Date.now() < file.not_supported_timeout) //Test if the not supported isnt may timeouted
return;
file.not_supported = false;
file.not_supported_timeout = undefined;
}
const path = "audio/" + file.filename;
const context = audio.player.context();
const volume = options && options.background_notification ? .5 : 1;
if(context.decodeAudioData) {
if(file.cached) {
console.log("Using cached buffer: %o", file.cached);
const player = context.createBufferSource();
player.buffer = file.cached;
player.start(0);
if(volume != 1 && context.createGain) {
const gain = context.createGain();
if(gain.gain.setValueAtTime)
gain.gain.setValueAtTime(volume, 0);
else
gain.gain.value = volume;
player.connect(gain);
gain.connect(audio.player.destination());
} else
player.connect(audio.player.destination());
} else {
const decode_data = buffer => {
console.log(buffer);
try {
console.log("Decoding data");
context.decodeAudioData(buffer, result => {
console.log("Got decoded data");
file.cached = result;
play(sound, options);
}, error => {
console.error("Failed to decode audio data for " + sound);
console.error(error);
file.not_supported = true;
file.not_supported_timeout = Date.now() + 1000 * 60 * 60; //Try in 2min again!
})
} catch (error) {
console.error(error);
file.not_supported = true;
file.not_supported_timeout = Date.now() + 1000 * 60 * 60; //Try in 2min again!
}
};
const xhr = new XMLHttpRequest();
xhr.open('GET', path, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
if (this.status == 200) {
decode_data(this.response);
} else {
console.error("Failed to load audio file. (Response code " + this.status + ")");
file.not_supported = true;
file.not_supported_timeout = Date.now() + 1000 * 60 * 60; //Try in 2min again!
}
};
xhr.onerror = error => {
console.error("Failed to load audio file " + sound);
console.error(error);
file.not_supported = true;
file.not_supported_timeout = Date.now() + 1000 * 60 * 60; //Try in 2min again!
};
xhr.send();
}
} else {
console.log("Replaying " + path);
if(file.node) {
file.node.currentTime = 0;
file.node.play();
} else {
if(!warned) {
warned = true;
console.warn("Your browser does not support decodeAudioData! Using a node to playback! This bypasses the audio output and volume regulation!");
}
const container = $("#sounds");
const node = $.spawn("audio").attr("src", path);
node.appendTo(container);
file.node = node[0];
file.node.play();
}
}
}
}

View File

@ -56,6 +56,7 @@ class ChannelEntry {
private _channelAlign: string; private _channelAlign: string;
private _formatedChannelName: string; private _formatedChannelName: string;
private _family_index: number = 0;
//HTML DOM elements //HTML DOM elements
private _tag_root: JQuery<HTMLElement>; private _tag_root: JQuery<HTMLElement>;
@ -161,8 +162,10 @@ class ChannelEntry {
//Tag channel //Tag channel
this._tag_channel = $.spawn("div"); this._tag_channel = $.spawn("div");
this._tag_channel.attr('channel-id', this.channelId);
this._tag_channel.addClass("channelLine"); this._tag_channel.addClass("channelLine");
this._tag_channel.addClass(this._channelAlign); //For left this._tag_channel.addClass(this._channelAlign); //For left
this._tag_channel.css('z-index', this._family_index);
let channelType = $.spawn("div"); let channelType = $.spawn("div");
channelType.addClass("channel_only_normal channel_type icon client-channel_green_subscribed"); channelType.addClass("channel_only_normal channel_type icon client-channel_green_subscribed");
@ -373,7 +376,9 @@ class ChannelEntry {
} }
perms[0]["cid"] = this.channelId; perms[0]["cid"] = this.channelId;
this.channelTree.client.serverConnection.sendCommand("channeladdperm", perms, ["continueonerror"]); this.channelTree.client.serverConnection.sendCommand("channeladdperm", perms, ["continueonerror"]).then(() => {
sound.play(Sound.CHANNEL_EDITED_SELF);
});
} }
}); });
} }
@ -383,7 +388,11 @@ class ChannelEntry {
icon: "client-channel_delete", icon: "client-channel_delete",
name: "Delete channel", name: "Delete channel",
invalidPermission: !flagDelete, invalidPermission: !flagDelete,
callback: () => this.channelTree.client.serverConnection.sendCommand("channeldelete", {cid: this.channelId}) callback: () => {
this.channelTree.client.serverConnection.sendCommand("channeldelete", {cid: this.channelId}).then(() => {
sound.play(Sound.CHANNEL_DELETED);
})
}
}, },
MenuEntry.HR(), MenuEntry.HR(),
{ {
@ -564,8 +573,10 @@ class ChannelEntry {
this.updateChannelTypeIcon(); this.updateChannelTypeIcon();
}); });
}).open(); }).open();
} else } else if(this.channelTree.client.getClient().currentChannel() != this)
this.channelTree.client.getServerConnection().joinChannel(this, this._cachedPassword).catch(error => { this.channelTree.client.getServerConnection().joinChannel(this, this._cachedPassword).then(() => {
sound.play(Sound.CHANNEL_JOINED);
}).catch(error => {
if(error instanceof CommandResult) { if(error instanceof CommandResult) {
if(error.id == 781) { //Invalid password if(error.id == 781) { //Invalid password
this._cachedPassword = undefined; this._cachedPassword = undefined;

View File

@ -1,6 +1,7 @@
/// <reference path="channel.ts" /> /// <reference path="channel.ts" />
/// <reference path="modal/ModalChangeVolume.ts" /> /// <reference path="modal/ModalChangeVolume.ts" />
/// <reference path="modal/ModalServerGroupDialog.ts" /> /// <reference path="modal/ModalServerGroupDialog.ts" />
/// <reference path="client_move.ts" />
enum ClientType { enum ClientType {
CLIENT_VOICE, CLIENT_VOICE,
@ -80,7 +81,7 @@ class ClientEntry {
return this._properties; return this._properties;
} }
currentChannel() { return this._channel; } currentChannel() : ChannelEntry { 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; }
clientId(){ return this._clientId; } clientId(){ return this._clientId; }
@ -95,6 +96,11 @@ class ClientEntry {
_this.channelTree.onSelect(_this); _this.channelTree.onSelect(_this);
}); });
if(this.clientId() != this.channelTree.client.clientId && !(this instanceof MusicClientEntry))
this.tag.dblclick(event => {
this.chat(true).focus();
});
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) { if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
this.tag.on("contextmenu", function (event) { this.tag.on("contextmenu", function (event) {
event.preventDefault(); event.preventDefault();
@ -105,6 +111,25 @@ class ClientEntry {
return false; return false;
}); });
} }
this.tag.mousedown(event => {
this.channelTree.client_mover.activate(this, target => {
if(!target) return;
if(target == this._channel) return;
const source = this._channel;
const self = this.channelTree.client.getClient();
this.channelTree.client.serverConnection.sendCommand("clientmove", {
clid: this.clientId(),
cid: target.getChannelId()
}).then(event => {
if(this.clientId() == this.channelTree.client.clientId)
sound.play(Sound.CHANNEL_JOINED);
else if(target !== source && target != self.currentChannel())
sound.play(Sound.USER_MOVED);
});
}, event);
});
} }
protected assignment_context() : ContextMenuEntry[] { protected assignment_context() : ContextMenuEntry[] {
@ -312,7 +337,9 @@ class ClientEntry {
uid: this.properties.client_unique_identifier, uid: this.properties.client_unique_identifier,
banreason: data.reason, banreason: data.reason,
time: data.length time: data.length
}, [data.no_ip ? "no-ip" : "", data.no_hwid ? "no-hardware-id" : "", data.no_name ? "no-nickname" : ""]); }, [data.no_ip ? "no-ip" : "", data.no_hwid ? "no-hardware-id" : "", data.no_name ? "no-nickname" : ""]).then(() => {
sound.play(Sound.USER_BANNED);
});
}); });
} }
}, },
@ -780,6 +807,48 @@ class MusicClientEntry extends ClientEntry {
}, },
MenuEntry.HR(), MenuEntry.HR(),
...super.assignment_context(), ...super.assignment_context(),
MenuEntry.HR(),{
type: MenuEntryType.ENTRY,
icon: "client-move_client_to_own_channel",
name: "Move client to your channel",
callback: () => {
this.channelTree.client.serverConnection.sendCommand("clientmove", {
clid: this.clientId(),
cid: this.channelTree.client.getClient().currentChannel().getChannelId()
});
}
}, {
type: MenuEntryType.ENTRY,
icon: "client-kick_channel",
name: "Kick client from channel",
callback: () => {
createInputModal("Kick client from channel", "Kick reason:<br>", text => true, result => {
if(result) {
console.log("Kicking client " + this.clientNickName() + " from channel with reason " + result);
this.channelTree.client.serverConnection.sendCommand("clientkick", {
clid: this.clientId(),
reasonid: ViewReasonId.VREASON_CHANNEL_KICK,
reasonmsg: result
});
}
}, { width: 400, maxLength: 255 }).open();
}
},
MenuEntry.HR(),
{
type: MenuEntryType.ENTRY,
icon: "client-volume",
name: "Change Volume",
callback: () => {
Modals.spawnChangeVolume(this.audioController.volume, volume => {
settings.changeServer("volume_client_" + this.clientUid(), volume);
this.audioController.volume = volume;
if(globalClient.selectInfo.currentSelected == this)
globalClient.selectInfo.update();
});
}
},
MenuEntry.HR(), MenuEntry.HR(),
{ {
name: "Delete bot", name: "Delete bot",

110
shared/js/ui/client_move.ts Normal file
View File

@ -0,0 +1,110 @@
/// <reference path="client.ts" />
class ClientMover {
static readonly listener_root = $(document);
static readonly move_element = $("#mouse-move");
readonly channel_tree: ChannelTree;
selected_client: ClientEntry;
hovered_channel: HTMLDivElement;
callback: (channel?: ChannelEntry) => any;
private _bound_finish;
private _bound_move;
private _active: boolean = false;
private origin_point: {x: number, y: number} = undefined;
constructor(tree: ChannelTree) {
this.channel_tree = tree;
}
activate(client: ClientEntry, callback: (channel?: ChannelEntry) => any, event: any) {
this.finish_listener(undefined);
this.selected_client = client;
this.callback = callback;
console.log("Starting mouse move");
ClientMover.listener_root.on('mouseup', this._bound_finish = this.finish_listener.bind(this)).on('mousemove', this._bound_move = this.move_listener.bind(this));
{
const content = ClientMover.move_element.find(".container");
content.empty();
content.append($.spawn("a").text(client.clientNickName()));
}
this.move_listener(event);
}
private move_listener(event) {
//console.log("Mouse move: " + event.pageX + " - " + event.pageY);
if(!event.pageX || !event.pageY) return;
if(!this.origin_point)
this.origin_point = {x: event.pageX, y: event.pageY};
ClientMover.move_element.css({
"top": (event.pageY - 1) + "px",
"left": (event.pageX + 10) + "px"
});
if(!this._active) {
const d_x = this.origin_point.x - event.pageX;
const d_y = this.origin_point.y - event.pageY;
this._active = Math.sqrt(d_x * d_x + d_y * d_y) > 5 * 5;
if(this._active)
ClientMover.move_element.show();
}
const elements = document.elementsFromPoint(event.pageX, event.pageY);
while(elements.length > 0) {
if(elements[0].classList.contains("channelLine")) break;
elements.pop_front();
}
if(this.hovered_channel) {
this.hovered_channel.classList.remove("move-selected");
this.hovered_channel = undefined;
}
if(elements.length > 0) {
elements[0].classList.add("move-selected");
this.hovered_channel = elements[0] as HTMLDivElement;
}
}
private finish_listener(event) {
ClientMover.move_element.hide();
const channel_id = this.hovered_channel ? parseInt(this.hovered_channel.getAttribute("channel-id")) : 0;
ClientMover.listener_root.unbind('mouseleave', this._bound_finish);
ClientMover.listener_root.unbind('mouseup', this._bound_finish);
ClientMover.listener_root.unbind('mousemove', this._bound_move);
if(this.hovered_channel) {
this.hovered_channel.classList.remove("move-selected");
this.hovered_channel = undefined;
}
this.origin_point = undefined;
if(!this._active) {
this.selected_client = undefined;
this.callback = undefined;
return;
}
this._active = false;
if(this.callback) {
if(!channel_id)
this.callback(undefined);
else {
this.callback(this.channel_tree.findChannel(channel_id));
}
this.callback = undefined;
}
}
deactivate() {
this.callback = undefined;
this.finish_listener(undefined);
}
}

View File

@ -232,6 +232,7 @@ class ControlBar {
private onDisconnect() { private onDisconnect() {
this.handle.handleDisconnect(DisconnectReason.REQUESTED); //TODO message? this.handle.handleDisconnect(DisconnectReason.REQUESTED); //TODO message?
this.update_connection_state(); this.update_connection_state();
sound.play(Sound.CONNECTION_DISCONNECTED);
} }
private on_token_use() { private on_token_use() {

View File

@ -530,10 +530,64 @@ class MusicInfoManager extends ClientInfoManager {
} }
}); });
} }
const player = properties["music_player"];
const player_transformer = $.spawn("div").append(player);
player_transformer.css({
'display': 'block',
//'width': "100%",
'height': '100%'
});
properties["music_player"] = player_transformer;
} }
let rendered = $("#tmpl_selected_music").renderTag([properties]); let rendered = $("#tmpl_selected_music").renderTag([properties]);
html_tag.append(rendered); html_tag.append(rendered);
{
const player = properties["music_player"] as JQuery;
const player_width = 400; //player.width();
const player_height = 400; //player.height();
const parent = player.parent();
parent.css({
'flex-grow': 1,
'display': 'flex',
'flex-direction': 'row',
'justify-content': 'space-around',
});
const padding = 14;
const scale_x = Math.min((parent.width() - padding) / player_width, 1.5);
const scale_y = Math.min((parent.height() - padding) / player_height, 1.5);
let scale = Math.min(scale_x, scale_y);
let translate_x = 50, translate_y = 50;
if(scale_x == scale_y && scale_x == scale) {
//Equal scale
} else if(scale_x == scale) {
//We scale on the x-Axis
//We have to adjust the y-Axis
} else {
//We scale on the y-Axis
//We have to adjust the x-Axis
}
//1 => 0 | 0
//1.5 => 0 | 25
//0.5 => 0 | -25
//const translate_x = scale_x != scale ? 0 : undefined || 50 - (50 * ((parent.width() - padding) / player_width));
//const translate_y = scale_y != scale || scale_y > 1 ? 0 : undefined || 50 - (50 * ((parent.height() - padding) / player_height));
const transform = ("translate(0%, " + (scale * 50 - 50) + "%) scale(" + scale.toPrecision(2) + ")");
console.log("Parents: %o | %o", parent.width(), parent.height());
console.log("Player: %o | %o", player_width, player_height);
console.log("Scale: %f => translate: %o | %o", scale, translate_x, translate_y);
player.css({
transform: transform
});
console.log("Transform: " + transform);
}
} }
available<V>(object: V): boolean { available<V>(object: V): boolean {

View File

@ -6,7 +6,10 @@ namespace Modals {
const modal = createModal({ const modal = createModal({
header: channel ? "Edit channel" : "Create channel", header: channel ? "Edit channel" : "Create channel",
body: () => { body: () => {
let template = $("#tmpl_channel_edit").renderTag(channel ? channel.properties : new ChannelProperties()); let template = $("#tmpl_channel_edit").renderTag(channel ? channel.properties : {
channel_flag_maxfamilyclients_unlimited: true,
channel_flag_maxclients_unlimited: true
} as ChannelProperties);
template = $.spawn("div").append(template); template = $.spawn("div").append(template);
return template.tabify(); return template.tabify();
}, },
@ -62,6 +65,8 @@ namespace Modals {
}); });
modal.open(); modal.open();
if(!channel)
modal.htmlTag.find(".channel_name").focus();
} }
function applyGeneralListener(properties: ChannelProperties, tag: JQuery, button: JQuery, create: boolean) { function applyGeneralListener(properties: ChannelProperties, tag: JQuery, button: JQuery, create: boolean) {
@ -71,7 +76,7 @@ namespace Modals {
else button.attr("disabled", "true"); else button.attr("disabled", "true");
}; };
tag.find(".channel_name").change(function (this: HTMLInputElement) { tag.find(".channel_name").on('change keyup', function (this: HTMLInputElement) {
properties.channel_name = this.value; properties.channel_name = this.value;
$(this).removeClass("input_error"); $(this).removeClass("input_error");
@ -101,8 +106,10 @@ namespace Modals {
}).prop("disabled", !globalClient.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_WITH_DESCRIPTION : PermissionType.B_CHANNEL_MODIFY_DESCRIPTION).granted(1)); }).prop("disabled", !globalClient.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_WITH_DESCRIPTION : PermissionType.B_CHANNEL_MODIFY_DESCRIPTION).granted(1));
if(create) { if(create) {
tag.find(".channel_name").trigger("change"); setTimeout(() => {
tag.find(".channel_password").trigger('change'); tag.find(".channel_name").trigger("change");
tag.find(".channel_password").trigger('change');
}, 0);
} }
} }

View File

@ -0,0 +1,30 @@
/// <reference path="../../utils/modal.ts" />
/// <reference path="../../proto.ts" />
/// <reference path="../../client.ts" />
namespace Modals {
export function spawnPoke(invoker: {
name: string,
id: number,
unique_id: string
}, message) {
let modal;
modal = createModal({
header: "You have been poked!",
body: () => {
let template = $("#tmpl_poke_popup").renderTag({
"invoker": ClientEntry.chatTag(invoker.id, invoker.name, invoker.unique_id, true),
"message": message
});
template = $.spawn("div").append(template);
template.find(".button-close").on('click', event => modal.close());
return template;
},
footer: undefined,
width: 750
});
modal.open();
}
}

View File

@ -130,7 +130,9 @@ class ServerEntry {
log.info(LogCategory.SERVER, "Changing server properties %o", properties); log.info(LogCategory.SERVER, "Changing server properties %o", properties);
console.log("Changed properties: %o", properties); console.log("Changed properties: %o", properties);
if (properties) if (properties)
this.channelTree.client.serverConnection.sendCommand("serveredit", properties); this.channelTree.client.serverConnection.sendCommand("serveredit", properties).then(() => {
sound.play(Sound.SERVER_EDITED_SELF);
});
}); });
} }
}, },

View File

@ -13,9 +13,14 @@ class ChannelTree {
channels: ChannelEntry[]; channels: ChannelEntry[];
clients: ClientEntry[]; clients: ClientEntry[];
readonly client_mover: ClientMover;
constructor(client, htmlTree) { constructor(client, htmlTree) {
document.addEventListener("touchstart", function(){}, true);
this.client = client; this.client = client;
this.htmlTree = htmlTree; this.htmlTree = htmlTree;
this.client_mover = new ClientMover(this);
this.reset(); this.reset();
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) { if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
@ -122,6 +127,7 @@ class ChannelTree {
elm.after(entry); elm.after(entry);
channel.adjustSize(true); channel.adjustSize(true);
channel.initializeListener(); channel.initializeListener();
} }
@ -164,8 +170,12 @@ class ChannelTree {
} }
if(oldParent) oldParent.adjustSize(); if(oldParent) {
if(channel) channel.adjustSize(); oldParent.adjustSize();
}
if(channel) {
channel.adjustSize();
}
} }
deleteClient(client: ClientEntry) { deleteClient(client: ClientEntry) {
@ -306,6 +316,7 @@ class ChannelTree {
return new Promise<ChannelEntry>(resolve => { resolve(channel); }) return new Promise<ChannelEntry>(resolve => { resolve(channel); })
}).then(channel => { }).then(channel => {
chat.serverChat().appendMessage("Channel {} successfully created!", true, channel.createChatTag()); chat.serverChat().appendMessage("Channel {} successfully created!", true, channel.createChatTag());
sound.play(Sound.CHANNEL_CREATED);
}); });
}); });
} }

View File

@ -15,6 +15,9 @@ class AudioController {
static initializeAudioController() { static initializeAudioController() {
if(!audio.player.initialize()) if(!audio.player.initialize())
console.warn("Failed to initialize audio controller!"); console.warn("Failed to initialize audio controller!");
sound.initialize().then(() => {
console.log("Sounds initialitzed");
});
//this._globalReplayScheduler = setInterval(() => { AudioController.invokeNextReplay(); }, 20); //Fix me //this._globalReplayScheduler = setInterval(() => { AudioController.invokeNextReplay(); }, 20); //Fix me
} }