Merged with develop and fixed build

canary
WolverinDEV 2018-12-26 14:01:54 +01:00
commit d75e1ca492
26 changed files with 6742 additions and 27 deletions

View File

@ -1,4 +1,21 @@
# Changelog:
* **23.12.18**
- Added query account management (since server 1.2.32b)
* **18.12.18**
- Added bookmarks and bookmarks management
- Added query user visibility button and creation (Query management will follow soon)
- Fixed overflow within the group assignment dialog
* **17.12.18**
- Implemented group prefix and suffix
* **15.12.18**
- Implemented a translation system with default translated language sets for
- German
- Turkish
- Russian
* **3.12.18**
- Fixed url connect parameters

View File

@ -103,9 +103,7 @@ $background:lightgray;
background-color: $background;
border-radius: 5px;
align-items: center;
border: 2px solid rgba(0, 0, 0, 0);
border-color: $border_color_activated;
border: 2px solid $border_color_activated;
width: 230px;
user-select: none;
@ -134,6 +132,10 @@ $background:lightgray;
& > div:last-of-type {
border-radius: 0 0 2px 2px;
}
&.display_left {
margin-left: -165px;
}
}
&:hover {
@ -142,4 +144,10 @@ $background:lightgray;
}
}
}
.bookmark-dropdown {
hr:last-child {
display: none;
}
}
}

173
shared/css/modal-query.scss Normal file
View File

@ -0,0 +1,173 @@
.query-create {
display: flex;
flex-direction: column;
.row-name {
width: 100%;
display: flex;
flex-direction: row;
justify-content: stretch;
input {
flex-grow: 1;
flex-shrink: 1;
margin-left: 5px;
}
}
.buttons {
margin-top: 5px;
text-align: right;
}
}
.query-created {
display: flex;
flex-direction: column;
.property-row {
width: 100%;
display: flex;
flex-direction: row;
justify-content: stretch;
align-items: center;
margin-top: 2px;
input {
flex-grow: 1;
flex-shrink: 1;
margin-left: 5px;
}
a:first-of-type {
width: 150px;
}
div:last-of-type {
margin-left: 5px;
cursor: pointer;
}
}
.buttons {
margin-top: 5px;
text-align: right;
}
}
.query-management {
height: 100%;
display: flex;
flex-direction: column;
.container {
display: flex;
flex-direction: column;
justify-content: stretch;
.header, .footer {
flex-grow: 0;
flex-shrink: 0;
}
.header {
display: flex;
flex-direction: row;
justify-content: stretch;
.buttons {
flex-grow: 0;
}
.search {
margin-left: 5px;
flex-grow: 1;
input {
width: 100%;
}
}
}
.query-list {
margin-top: 5px;
display: flex;
flex-grow: 1;
flex-direction: column;
justify-content: stretch;
.column {
&.column-username {
width: calc(50% - 75px)
}
&.column-unique-id {
width: calc(50% - 75px)
}
&.column-bound-server {
width: 150px;
flex-grow: 0;
}
}
.query-list-header {
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: row;
height: 20px;
.column {
border: 1px solid lightgray;
text-align: center;
}
}
.query-list-entries-container {
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: start;
overflow-y: auto;
min-height: 250px;
.entry {
display: flex;
flex-direction: row;
.column {
margin-left: 2px;
}
cursor: pointer;
&.selected {
background-color: blue;
}
}
&.scrollbar {
.column-username {
width: calc(50% - 75px + 30px)
}
.column-unique-id {
width: calc(50% - 75px + 30px)
}
}
}
}
.footer {
margin-top: 5px;
display: flex;
flex-direction: row;
justify-content: space-between;
}
}
}

View File

@ -520,6 +520,7 @@
.group-list {
border: lightgray solid 1px;
padding: 3px;
overflow-y: auto;
.group-entry {
display: flex;

View File

@ -53,11 +53,21 @@
.channelTree div * {vertical-align: middle;display:inline-block;height: 16px;padding: 0px;}
.channelTree div img {border: 0;}
.channelTree div > span {position: absolute; right: 0;}
.channelTree .name {
vertical-align: middle;
margin-top: 1px;
height: 14px;
display: inline;
.channelTree {
.name, .group_prefix, .group_suffix, .away {
vertical-align: middle;
margin-top: 1px;
height: 14px;
display: inline;
}
.group_prefix {
margin-right: 3px;
}
.group_suffix {
margin-left: 3px;
}
}
.channelTree .own_name {
font-weight: bold;

View File

@ -50,6 +50,7 @@
<link rel="stylesheet" href="css/ts/icons.css" type="text/css">
<link rel="stylesheet" href="css/general.css" type="text/css">
<link rel="stylesheet" href="css/modals.css" type="text/css">
<link rel="stylesheet" href="css/modal-query.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-settings.css" type="text/css">

View File

@ -19,7 +19,24 @@
<div class="button btn_disconnect" title="{{tr 'Disconnect from server' /}}" style="display: none">
<div class="icon_x32 client-disconnect"></div>
</div>
<!--<div class="button btn_disconnect"><div class="icon_x32 client-disconnect"></div></div>-->
<!--
<div class="button-dropdown btn_bookmark" title="{{tr 'Bookmarks' /}}">
<div class="buttons">
<div class="button icon_x32 client-bookmark_manager btn_bookmark_list"></div>
<div class="button-dropdown">
<div class="arrow"></div>
</div>
</div>
<div class="dropdown bookmark-dropdown" style="width: 300px">
<div class="btn_bookmark_list"><div class="icon client-bookmark_manager"></div><a>{{tr "Manage bookmarks" /}}</a></div>
<div class="btn_bookmark_add"><div class="icon client-bookmark_add"></div><a>{{tr "Add current server to bookmarks" /}}</a></div>
<div class="btn_bookmark_remove"><div class="icon client-bookmark_remove"></div><a>{{tr "Remove current server to bookmarks" /}}</a></div>
<hr>
</div>
</div>
-->
<div class="divider"></div>
<div class="button-dropdown btn_away" title="{{tr 'Toggle away status' /}}">
@ -62,6 +79,21 @@
<div class="button btn_permissions" title="{{tr 'View/edit permissions' /}}">
<div class="icon_x32 client-permission_overview"></div>
</div>
<!-- the query button -->
<div class="button-dropdown btn_query" title="{{tr 'Show/hide server queries' /}}">
<div class="buttons">
<div class="button icon_x32 client-server_query btn_query_toggle"></div>
<div class="button-dropdown">
<div class="arrow"></div>
</div>
</div>
<div class="dropdown display_left">
<div class="btn_query_toggle"><div class="icon client-toggle_server_query_clients"></div><a>{{tr "Show/hide server queries" /}}</a></div>
<div class="btn_query_manage"><div class="icon client-server_query"></div><a>{{tr "Manage server queries" /}}</a></div>
<!-- <div class="btn_query_create"><div class="icon client-away"></div><a>{{tr "Create server query login" /}}</a></div> -->
</div>
</div>
<div class="divider"></div>
<div class="button btn_open_settings" title="{{tr 'Edit global client settings' /}}">
<div class="icon_x32 client-settings"></div>
@ -1074,7 +1106,7 @@
</div>
</div>
</script>
9
<script class="jsrender-template" id="tmpl_client_ban" type="text/html">
<div class="align_column">
<div class="align_column" style="margin: 5px">
@ -1656,5 +1688,84 @@
<button class="button-close">{{tr "Close" /}}</button>
</div>
</script>
<script class="jsrender-template" id="tmpl_query_create" type="text/html">
<div class="query-create">
<a>{{tr "Set the login name for your Server Query account." /}}</a>
<a>{{tr "You'll receive your password within the next step." /}}</a>
<div class="row-name">
<a>Name:</a>
<input type="text" maxlength="64" minlength="3" class="input-name">
</div>
<div class="buttons">
<button class="button-close">{{tr "Close" /}}</button>
<button class="button-create">{{tr "Create" /}}</button>
</div>
</div>
</script>
<script class="jsrender-template" id="tmpl_query_created" type="text/html">
<div class="query-created">
<a>{{tr "Your server query credentials:" /}}</a>
<div class="property-row">
<a>Name:</a>
<input class="query_name" type="text" maxlength="64" minlength="3" value="{{>username}}">
<div class="btn_copy_name icon client-copy" title="{{tr 'Copy username' /}}"></div>
</div>
<div class="property-row">
<a>Password:</a>
<input class="query_password" type="text" value="{{>password}}">
<div class="btn_copy_password icon client-copy" title="{{tr 'Copy password' /}}"></div>
</div>
<div class="buttons">
<button class="button-close">{{tr "Close" /}}</button>
</div>
</div>
</script>
<script class="jsrender-template" id="tmpl_query_manager" type="text/html">
<div class="query-management">
<div class="container">
<div class="header">
<div class="buttons">
<button class="button button-query-create">{{tr "Create account" /}}</button>
<button class="button button-query-delete">{{tr "Delete account" /}}</button>
<button class="button button-query-rename">{{tr "Rename account" /}}</button>
<button class="button button-query-change-password">{{tr "Change password" /}}</button>
</div>
<div class="search">
<input class="input input-search" type="text" placeholder="search">
</div>
</div>
<div class="query-list">
<div class="query-list-header">
<div class="column column-username">{{tr "Username" /}}</div>
<div class="column column-unique-id">{{tr "Unique ID" /}}</div>
<div class="column column-bound-server">{{tr "Bounded Server" /}}</div>
</div>
<div class="query-list-entries-container">
<div class="query-list-entries">
</div>
</div>
</div>
<div class="footer">
<div class="info">
<a>loading...</a>
</div>
<div class="buttons">
<button class="button-refresh">{{tr "Refresh" /}}</button>
</div>
</div>
</div>
</div>
</script>
<script class="jsrender-template" id="tmpl_query_manager-list_entry" type="text/html">
<div class="entry">
<div class="column column-username">{{>username}}</div>
<div class="column column-unique-id">{{>unique_id}}</div>
<div class="column column-bound-server">{{>bounded_server}}</div>
</div>
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,10 @@
{
"key": "tr_gt",
"path": "tr_google_translate.translation"
},
{
"key": "fr_gt",
"path": "fr_google_translate.translation"
}
],
"name": "Default TeaSpeak repository",

156
shared/js/bookmarks.ts Normal file
View File

@ -0,0 +1,156 @@
namespace bookmarks {
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}
export interface ConnectIdentity {
identity_type: IdentitifyType;
}
export interface ForumConnectIdentity extends ConnectIdentity { }
export interface NicknameConnectIdentity extends ConnectIdentity { }
export interface TeamSpeakConnectIdentity extends ConnectIdentity { }
export interface ServerProperties {
server_address: string;
server_port: number;
server_password_hash?: string;
server_password?: string;
}
export enum BookmarkType {
ENTRY,
DIRECTORY
}
export interface Bookmark {
type: /* BookmarkType.ENTRY */ BookmarkType;
/* readonly directory: DirectoryBookmark; */
server_properties: ServerProperties;
display_name: string;
unique_id: string;
nickname: string;
default_channel?: number | string;
default_channel_password_hash?: string;
default_channel_password?: string;
}
export interface DirectoryBookmark {
type: /* BookmarkType.DIRECTORY */ BookmarkType;
readonly content: (Bookmark | DirectoryBookmark)[];
unique_id: string;
display_name: string;
}
interface BookmarkConfig {
root_bookmark?: DirectoryBookmark;
default_added?: boolean;
}
let _bookmark_config: BookmarkConfig;
function bookmark_config() : BookmarkConfig {
if(_bookmark_config)
return _bookmark_config;
let bookmark_json = localStorage.getItem("bookmarks");
let bookmarks = JSON.parse(bookmark_json) || {} as BookmarkConfig;
_bookmark_config = bookmarks;
_bookmark_config.root_bookmark = _bookmark_config.root_bookmark || { content: [], display_name: "root", type: BookmarkType.DIRECTORY} as DirectoryBookmark;
if(!_bookmark_config.default_added) {
_bookmark_config.default_added = true;
create_bookmark("TeaSpeak official Test-Server", _bookmark_config.root_bookmark, {
server_address: "ts.teaspeak.de",
server_port: 9987
}, "Another TeaSpeak user");
save_config();
}
return _bookmark_config;
}
function save_config() {
localStorage.setItem("bookmarks", JSON.stringify(bookmark_config()));
}
export function bookmarks() : DirectoryBookmark {
return bookmark_config().root_bookmark;
}
function find_bookmark_recursive(parent: DirectoryBookmark, uuid: string) : Bookmark | DirectoryBookmark {
for(const entry of parent.content) {
if(entry.unique_id == uuid)
return entry;
if(entry.type == BookmarkType.DIRECTORY) {
const result = find_bookmark_recursive(entry as DirectoryBookmark, uuid);
if(result) return result;
}
}
return undefined;
}
export function find_bookmark(uuid: string) : Bookmark | DirectoryBookmark | undefined {
return find_bookmark_recursive(bookmarks(), uuid);
}
export function create_bookmark(display_name: string, directory: DirectoryBookmark, server_properties: ServerProperties, nickname: string) : Bookmark {
const bookmark = {
display_name: display_name,
server_properties: server_properties,
nickname: nickname,
type: BookmarkType.ENTRY,
unique_id: guid()
} as Bookmark;
directory.content.push(bookmark);
return bookmark;
}
export function create_bookmark_directory(parent: DirectoryBookmark, name: string) : DirectoryBookmark {
const bookmark = {
type: BookmarkType.DIRECTORY,
display_name: name,
content: [],
unique_id: guid()
} as DirectoryBookmark;
parent.content.push(bookmark);
return bookmark;
}
//TODO test if the new parent is within the old bookmark
export function change_directory(parent: DirectoryBookmark, bookmark: Bookmark | DirectoryBookmark) {
delete_bookmark(bookmark)
parent.content.push(bookmark)
}
export function save_bookmark(bookmark?: Bookmark | DirectoryBookmark) {
save_config(); /* nvm we dont give a fuck... saving everything */
}
function delete_bookmark_recursive(parent: DirectoryBookmark, bookmark: Bookmark | DirectoryBookmark) {
const index = parent.content.indexOf(bookmark);
if(index != -1)
parent.content.remove(bookmark);
else
for(const entry of parent.content)
if(entry.type == BookmarkType.DIRECTORY)
delete_bookmark_recursive(entry as DirectoryBookmark, bookmark)
}
export function delete_bookmark(bookmark: Bookmark | DirectoryBookmark) {
delete_bookmark_recursive(bookmarks(), bookmark)
}
}

View File

@ -402,10 +402,24 @@ interface ClientNameFromUid {
response: ClientNameInfo[]
}
interface QueryListEntry {
username: string;
unique_id: string;
bounded_server: number;
}
interface QueryList {
flag_own: boolean;
flag_all: boolean;
queries: QueryListEntry[];
}
class CommandHelper {
readonly connection: ServerConnection;
private _callbacks_namefromuid: ClientNameFromUid[] = [];
private _who_am_i: any;
constructor(connection) {
this.connection = connection;
@ -432,6 +446,63 @@ class CommandHelper {
return req.promise;
}
request_query_list(server_id: number = undefined) : Promise<QueryList> {
return new Promise<QueryList>((resolve, reject) => {
this.connection.commandHandler["notifyquerylist"] = json => {
const result = {} as QueryList;
result.flag_all = json[0]["flag_all"];
result.flag_own = json[0]["flag_own"];
result.queries = [];
for(const entry of json) {
const rentry = {} as QueryListEntry;
rentry.bounded_server = entry["client_bounded_server"];
rentry.username = entry["client_login_name"];
rentry.unique_id = entry["client_unique_identifier"];
result.queries.push(rentry);
}
resolve(result);
this.connection.commandHandler["notifyquerylist"] = undefined;
};
let data = {};
if(server_id !== undefined)
data["server_id"] = server_id;
this.connection.sendCommand("querylist", data).catch(error => {
if(error instanceof CommandResult) {
if(error.id == 0x0501) {
resolve(undefined);
return;
}
}
reject(error);
})
});
}
/**
* @deprecated
* Its just a workaround for the query management.
* There is no garante that the whoami trick will work forever
*/
current_virtual_server_id() : Promise<number> {
if(this._who_am_i)
return Promise.resolve(parseInt(this._who_am_i["virtualserver_id"]));
return new Promise<number>((resolve, reject) => {
this.connection.commandHandler[""] = json => {
this._who_am_i = json[0];
resolve(parseInt(this._who_am_i["virtualserver_id"]));
this.connection.commandHandler[""] = undefined;
};
this.connection.sendCommand("whoami");
});
}
private handle_notifyclientnamefromuid(json: any[]) {
for(let entry of json) {
let info: ClientNameInfo = {} as any;

View File

@ -180,6 +180,8 @@ function loadDebug() {
"js/crypto/hex.js",
//Load UI
"js/ui/modal/ModalQuery.js",
"js/ui/modal/ModalQueryManage.js",
"js/ui/modal/ModalConnect.js",
"js/ui/modal/ModalSettings.js",
"js/ui/modal/ModalCreateChannel.js",
@ -219,6 +221,7 @@ function loadDebug() {
//Load general stuff
"js/settings.js",
"js/bookmarks.js",
"js/contextMenu.js",
"js/connection.js",
"js/FileManager.js",

View File

@ -21,9 +21,9 @@ const js_render = window.jsrender || $;
const native_client = window.require !== undefined;
function getUserMediaFunction() {
if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)
return (settings, success, fail) => { navigator.mediaDevices.getUserMedia(settings).then(success).catch(fail); };
return navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
if((navigator as any).mediaDevices && (navigator as any).mediaDevices.getUserMedia)
return (settings, success, fail) => { (navigator as any).mediaDevices.getUserMedia(settings).then(success).catch(fail); };
return (navigator as any).getUserMedia || (navigator as any).webkitGetUserMedia || (navigator as any).mozGetUserMedia;
}
function setup_close() {

View File

@ -143,9 +143,9 @@ class GroupManager {
group.updateProperty(key, groupData[key]);
}
group.requiredMemberRemovePower = groupData["n_member_removep"];
group.requiredMemberAddPower = groupData["n_member_addp"];
group.requiredModifyPower = groupData["n_modifyp"];
group.requiredMemberRemovePower = parseInt(groupData["n_member_removep"]);
group.requiredMemberAddPower = parseInt(groupData["n_member_addp"]);
group.requiredModifyPower = parseInt(groupData["n_modifyp"]);
if(target == GroupTarget.SERVER)
this.serverGroups.push(group);
@ -154,6 +154,8 @@ class GroupManager {
}
console.log("Got " + json.length + " new " + target + " groups:");
for(const client of this.handle.channelTree.clients)
client.update_displayed_client_groups();
}
request_permissions(group: Group) : Promise<PermissionValue[]> { //database_empty_result

View File

@ -644,11 +644,12 @@ class PermissionManager {
for(let perm of this.neededPermissions)
if(perm.type.id == key || perm.type.name == key || perm.type == key)
return perm;
log.debug(LogCategory.PERMISSIONS, tr("Could not resolve grant permission %o. Creating a new one."), key);
let info = key instanceof PermissionInfo ? key : this.resolveInfo(key);
if(!info) {
log.warn(LogCategory.PERMISSIONS, tr("Requested needed permission with invalid key! (%o)"), key);
return undefined;
return new NeededPermissionValue(undefined, -2);
}
let result = new NeededPermissionValue(info, -2);
this.neededPermissions.push(result);

View File

@ -13,6 +13,7 @@ interface JSON {
interface JQuery<TElement = HTMLElement> {
render(values?: any) : string;
renderTag(values?: any) : JQuery<TElement>;
hasScrollBar() : boolean;
}
interface JQueryStatic<TElement extends Node = HTMLElement> {
@ -106,8 +107,8 @@ if(typeof ($) !== "undefined") {
return $(document.createElement(tagName) as any);
}
}
if(!$.prototype.renderTag) {
$.prototype.renderTag = function (values?: any) : JQuery {
if(!$.fn.renderTag) {
$.fn.renderTag = function (values?: any) : JQuery {
let result;
if(this.render) {
result = $(this.render(values));
@ -126,6 +127,11 @@ if(typeof ($) !== "undefined") {
return result;
}
}
if(!$.fn.hasScrollBar)
$.fn.hasScrollBar = function() {
return this.get(0).scrollHeight > this.height();
}
}
if (!String.prototype.format) {

View File

@ -294,12 +294,14 @@ class ChannelEntry {
const sub = this.siblings(false);
sub.forEach(function (e) {
subSize += e.rootTag().outerHeight(true);
if(e.rootTag().is(":visible"))
subSize += e.rootTag().outerHeight(true);
});
const clients = this.clients(false);
clients.forEach(function (e) {
clientSize += e.tag.outerHeight(true);
if(e.tag.is(":visible"))
clientSize += e.tag.outerHeight(true);
});
this._tag_root.css({height: size + subSize + clientSize});

View File

@ -408,7 +408,9 @@ class ClientEntry {
tag.append($.spawn("div").addClass("icon_client_state").attr("title", "Client state"));
tag.append($.spawn("div").addClass("group_prefix").attr("title", "Server groups prefixes").hide());
tag.append($.spawn("div").addClass("name").text(this.clientNickName()));
tag.append($.spawn("div").addClass("group_suffix").attr("title", "Server groups suffix").hide());
tag.append($.spawn("div").addClass("away").text(this.clientNickName()));
let clientIcons = $.spawn("span");
@ -561,7 +563,7 @@ class ClientEntry {
if(variable.key == "client_icon_id")
this.updateClientIcon();
if(variable.key =="client_channel_group_id" || variable.key == "client_servergroups")
this.updateGroupIcons();
this.update_displayed_client_groups();
}
/* process updates after variables have been set */
@ -577,11 +579,39 @@ class ClientEntry {
group.end();
}
updateGroupIcons() {
update_displayed_client_groups() {
this.tag.find("span .group_icons").children().detach();
for(let id of this.assignedServerGroupIds())
this.updateGroupIcon(this.channelTree.client.groups.serverGroup(id));
this.updateGroupIcon(this.channelTree.client.groups.channelGroup(this.properties.client_channel_group_id));
let prefix_groups: string[] = [];
let suffix_groups: string[] = [];
for(const group_id of this.assignedServerGroupIds()) {
const group = this.channelTree.client.groups.serverGroup(group_id);
if(!group) continue;
if(group.properties.namemode == 1)
prefix_groups.push(group.name);
else if(group.properties.namemode == 2)
suffix_groups.push(group.name);
}
const tag_group_prefix = this.tag.find(".group_prefix");
const tag_group_suffix = this.tag.find(".group_suffix");
if(prefix_groups.length > 0) {
tag_group_prefix.text("[" + prefix_groups.join("][") + "]").show();
} else {
tag_group_prefix.hide()
}
if(suffix_groups.length > 0) {
tag_group_suffix.text("[" + suffix_groups.join("][") + "]").show();
} else {
tag_group_suffix.hide()
}
}
updateClientVariables(){

View File

@ -13,11 +13,13 @@
client_away_message Value: ''
*/
import openBanList = Modals.openBanList;
import spawnConnectModal = Modals.spawnConnectModal;
class ControlBar {
private _muteInput: boolean;
private _muteOutput: boolean;
private _away: boolean;
private _query_visible: boolean;
private _awayMessage: string;
private codec_supported: boolean = false;
@ -64,10 +66,35 @@ class ControlBar {
away.find(".btn_away_toggle").on('click', this.on_away_toggle.bind(this));
away.find(".btn_away_message").on('click', this.on_away_set_message.bind(this));
}
{
let bookmark = this.htmlTag.find(".btn_bookmark");
bookmark.find(".button-dropdown").on('click', () => {
bookmark.find(".dropdown").addClass("displayed");
});
bookmark.on('mouseleave', () => {
bookmark.find(".dropdown").removeClass("displayed");
});
this.update_bookmarks()
}
{
let query = this.htmlTag.find(".btn_query");
query.find(".button-dropdown").on('click', () => {
query.find(".dropdown").addClass("displayed");
});
query.on('mouseleave', () => {
query.find(".dropdown").removeClass("displayed");
});
query.find(".btn_query_toggle").on('click', this.on_query_visibility_toggle.bind(this));
query.find(".btn_query_create").on('click', this.on_query_create.bind(this));
query.find(".btn_query_manage").on('click', this.on_query_manage.bind(this));
}
//Need an initialise
this.muteInput = settings.global("mute_input") == "1";
this.muteOutput = settings.global("mute_output") == "1";
this.query_visibility = settings.global("show_server_queries") == "1";
}
@ -269,6 +296,77 @@ class ControlBar {
private onBanlist() {
if(!this.handle.serverConnection) return;
openBanList(this.handle);
if(this.handle.permissions.neededPermission(PermissionType.B_CLIENT_BAN_LIST).granted(1)) {
openBanList(this.handle);
} else {
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to view the ban list")).open();
sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
}
}
update_bookmarks() {
//<div class="btn_bookmark_connect" target="localhost"><a>Localhost</a></div>
let tag_bookmark = this.htmlTag.find(".btn_bookmark .dropdown");
tag_bookmark.find(".bookmark, .bookmark_directory").detach();
for(const bookmark of bookmarks.bookmarks().content) {
if(bookmark.type == bookmarks.BookmarkType.ENTRY) {
tag_bookmark.append(
$.spawn("div")
.addClass("bookmark")
/* /.attr("bookmark-uuid", bookmark.unique_id) */
.text(bookmark.display_name)
.on('click', event => {
spawnConnectModal()
})
)
}
//TODO add bookmark directories here
}
}
get query_visibility() {
return this._query_visible;
}
set query_visibility(flag: boolean) {
if(this._query_visible == flag) return;
this._query_visible = flag;
settings.global("show_server_queries", flag);
this.update_query_visibility_button();
this.handle.channelTree.toggle_server_queries(flag);
}
private on_query_visibility_toggle() {
this.query_visibility = !this._query_visible;
this.update_query_visibility_button();
}
private update_query_visibility_button() {
let tag = this.htmlTag.find(".btn_query_toggle");
if(this._query_visible) {
tag.addClass("activated");
} else {
tag.removeClass("activated");
}
}
private on_query_create() {
if(this.handle.permissions.neededPermission(PermissionType.B_CLIENT_CREATE_MODIFY_SERVERQUERY_LOGIN).granted(1)) {
Modals.spawnQueryCreate();
} else {
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to create a server query login")).open();
sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
}
}
private on_query_manage() {
if(globalClient && globalClient.connected) {
Modals.spawnQueryManage(globalClient);
} else {
createErrorModal(tr("You have to be connected"), tr("You have to be connected!")).open();
}
}
}

View File

@ -0,0 +1,84 @@
/// <reference path="../../utils/modal.ts" />
/// <reference path="../../proto.ts" />
/// <reference path="../../client.ts" />
namespace Modals {
export function spawnQueryCreate(callback_created?: (user, pass) => any) {
let modal;
modal = createModal({
header: tr("Create a server query login"),
body: () => {
let template = $("#tmpl_query_create").renderTag();
template = $.spawn("div").append(template);
template.find(".button-close").on('click', event => modal.close());
template.find(".button-create").on('click', event => {
const name = template.find(".input-name").val() as string;
if(name.length < 3 || name.length > 64) {
createErrorModal(tr("Invalid username"), tr("Please enter a valid name!")).open();
return;
}
//client_login_password
globalClient.serverConnection.commandHandler["notifyquerycreated"] = json => {
json = json[0];
spawnQueryCreated({
username: name,
password: json.client_login_password
}, true);
if(callback_created)
callback_created(name, json.client_login_password);
};
globalClient.serverConnection.sendCommand("querycreate", {
client_login_name: name
}).catch(error => {
if(error instanceof CommandResult)
error = error.extra_message || error.message;
createErrorModal(tr("Unable to create account"), tr("Failed to create account<br>Message: ") + error).open();
});
modal.close();
//TODO create account
});
return template;
},
footer: undefined,
width: 750
});
modal.open();
}
export function spawnQueryCreated(credentials: {
username: string,
password: string
}, yust_created: boolean) {
let modal;
modal = createModal({
header: yust_created ? tr("Server query credentials") : tr("New server query credentials"),
body: () => {
let template = $("#tmpl_query_created").renderTag(credentials);
template = $.spawn("div").append(template);
template.find(".button-close").on('click', event => modal.close());
template.find(".query_name").text(credentials.username);
template.find(".query_password").text(credentials.password);
template.find(".btn_copy_name").on('click', () => {
template.find(".query_name").select();
document.execCommand("copy");
});
template.find(".btn_copy_password").on('click', () => {
template.find(".query_password").select();
document.execCommand("copy");
});
return template;
},
footer: undefined,
width: 750
});
modal.open();
}
}

View File

@ -0,0 +1,156 @@
/// <reference path="../../utils/modal.ts" />
/// <reference path="../../proto.ts" />
/// <reference path="../../client.ts" />
namespace Modals {
export function spawnQueryManage(client: TSClient) {
let modal: Modal;
let selected_query: QueryListEntry;
const update_selected = () => {
const buttons = modal.htmlTag.find(".header .buttons");
//TODO gray out if no permissions (Server needs to send that... :D)
buttons.find(".button-query-delete").prop("disabled", selected_query === undefined);
buttons.find(".button-query-rename").prop("disabled", selected_query === undefined);
buttons.find(".button-query-change-password").prop("disabled", selected_query === undefined);
};
const update_list = () => {
const info_tag = modal.htmlTag.find(".footer .info a");
info_tag.text("loading...");
client.serverConnection.helper.current_virtual_server_id().then(server_id => {
client.serverConnection.helper.request_query_list(server_id).then(result => {
selected_query = undefined;
const entries_tag = modal.htmlTag.find(".query-list-entries");
const entry_template = $("#tmpl_query_manager-list_entry");
entries_tag.empty();
for(const query of result.queries || []) {
entries_tag.append(entry_template.renderTag(query).on('click', event => {
entries_tag.find(".entry.selected").removeClass("selected");
$(event.target).parent(".entry").addClass("selected");
selected_query = query;
update_selected();
}));
}
const entry_container = modal.htmlTag.find(".query-list-entries-container");
if(entry_container.hasScrollBar())
entry_container.addClass("scrollbar");
if(!result || result.flag_all) {
info_tag.text("Showing all server queries");
} else {
info_tag.text("Showing your server queries")
}
update_selected();
});
});
//TODO error handling
};
modal = createModal({
header: tr("Manage query accounts"),
body: () => {
let template = $("#tmpl_query_manager").renderTag();
template = $.spawn("div").append(template);
/* first open the modal */
setTimeout(() => {
const entry_container = template.find(".query-list-entries-container");
if(entry_container.hasScrollBar())
entry_container.addClass("scrollbar");
}, 100);
template.find(".footer .buttons .button-refresh").on('click', update_list);
template.find(".button-query-create").on('click', () => {
Modals.spawnQueryCreate((user, pass) => update_list());
});
template.find(".button-query-rename").on('click', () => {
if(!selected_query) return;
createInputModal(tr("Change account name"), tr("Enter the new name for the login:<br>"), text => text.length >= 3, result => {
if(result) {
client.serverConnection.sendCommand("queryrename", {
client_login_name: selected_query.username,
client_new_login_name: result
}).catch(error => {
if(error instanceof CommandResult)
error = error.extra_message || error.message;
createErrorModal(tr("Unable to rename account"), tr("Failed to rename account<br>Message: ") + error).open();
}).then(() => {
createInfoModal(tr("Account successfully renamed"), tr("The query account has been renamed!")).open();
update_list();
});
}
}).open();
});
template.find(".button-query-change-password").on('click', () => {
if(!selected_query) return;
createInputModal(tr("Change account's password"), tr("Enter a new password (leave blank for auto generation):<br>"), text => true, result => {
if(result !== false) {
client.serverConnection.sendCommand("querychangepassword", {
client_login_name: selected_query.username,
client_login_password: result
}).catch(error => {
if(error instanceof CommandResult)
error = error.extra_message || error.message;
createErrorModal(tr("Unable to change password"), tr("Failed to change password<br>Message: ") + error).open();
});
client.serverConnection.commandHandler["notifyquerypasswordchanges"] = json => {
Modals.spawnQueryCreated({
username: json[0]["client_login_name"],
password: json[0]["client_login_password"]
}, false);
client.serverConnection.commandHandler["notifyquerypasswordchanges"] = undefined;
};
}
}).open();
});
template.find(".button-query-delete").on('click', () => {
if(!selected_query) return;
Modals.spawnYesNo(tr("Are you sure?"), tr("Do you really want to delete this account?"), result => {
if(result) {
client.serverConnection.sendCommand("querydelete", {
client_login_name: selected_query.username
}).catch(error => {
if(error instanceof CommandResult)
error = error.extra_message || error.message;
createErrorModal(tr("Unable to delete account"), tr("Failed to delete account<br>Message: ") + error).open();
}).then(() => {
createInfoModal(tr("Account successfully deleted"), tr("The query account has been successfully deleted!")).open();
update_list();
});
}
});
});
template.find(".input-search").on('change keyup', () => {
const text = (template.find(".input-search").val() as string || "").toLowerCase();
if(text.length == 0) {
template.find(".query-list-entries .entry").show();
} else {
template.find(".query-list-entries .entry").each((_, e) => {
const element = $(e);
if(element.text().toLowerCase().indexOf(text) == -1)
element.hide();
else
element.show();
})
}
});
return template;
},
footer: undefined,
width: 750
});
update_list();
modal.open();
}
}

View File

@ -18,6 +18,7 @@ class ChannelTree {
currently_selected_context_callback: (event) => any = undefined;
readonly client_mover: ClientMover;
private _show_queries: boolean;
private channel_last?: ChannelEntry;
private channel_first?: ChannelEntry;
@ -290,6 +291,10 @@ class ChannelTree {
if(newClient) client = newClient; //Got new client :)
else
this.clients.push(client);
if(!this._show_queries && client.properties.client_type == ClientType.CLIENT_QUERY)
client.tag.hide();
client.channelTree = this;
client["_channel"] = channel;
@ -709,4 +714,23 @@ class ChannelTree {
}
}
}
toggle_server_queries(flag: boolean) {
if(this._show_queries == flag) return;
this._show_queries = flag;
//FIXME resize channels
const channels: ChannelEntry[] = []
for(const client of this.clients)
if(client.properties.client_type == ClientType.CLIENT_QUERY) {
if(this._show_queries)
client.tag.show();
else
client.tag.hide();
if(channels.indexOf(client.currentChannel()) == -1)
channels.push(client.currentChannel());
}
for(const channel of channels)
channel.adjustSize();
}
}

View File

@ -1 +1,2 @@
nan
Add connect profiles for bookmarks, like TeaSpeak-Forum or multiple TeamSpeak Identities
Fix server group management dialog for a lots of groups (May even add a search function?)

View File

@ -321,11 +321,30 @@ generators[SyntaxKind.ClassDeclaration] = (settings, stack, node: ts.ClassDeclar
return ts.createClassDeclaration(node.decorators, append_export(append_declare(node.modifiers, !stack.flag_declare), stack.flag_namespace), node.name, node.typeParameters, node.heritageClauses, members as any);
};
generators[SyntaxKind.PropertySignature] = (settings, stack, node: ts.PropertySignature) => {
console.log(SyntaxKind[node.type.kind]);
let type: ts.TypeNode = node.type;
switch (node.type.kind) {
case SyntaxKind.LiteralType:
type = ts.createIdentifier("any") as any;
}
return ts.createPropertySignature(node.modifiers, node.name, node.questionToken, type, undefined);
};
generators[SyntaxKind.InterfaceDeclaration] = (settings, stack, node: ts.InterfaceDeclaration) => {
if(settings.remove_private.field && has_private(node.modifiers)) return;
if(stack.flag_namespace && !has_modifier(node.modifiers, SyntaxKind.ExportKeyword)) return;
return node;
const members: any[] = [];
for(const member of node.members) {
if(generators[member.kind])
members.push(generators[member.kind](settings, stack, member));
else
members.push(member);
}
return ts.createInterfaceDeclaration(undefined, append_export(append_declare(node.modifiers, !stack.flag_declare), stack.flag_namespace), node.name, node.typeParameters, node.heritageClauses, members);
};
generators[SyntaxKind.VariableDeclaration] = (settings, stack, node: ts.VariableDeclaration) => {

View File

@ -0,0 +1,9 @@
enum YY {
H = "C",
B = "Y"
}
interface X {
type: "",
c: YY.B
}

2
vendor/bbcode vendored

@ -1 +1 @@
Subproject commit 7b931ed61cf265937dc742579f9070e7c4e50775
Subproject commit 0221bd137ef5bbc846018ff86deda0aca38aed26