Refactored the app to use webpack

This commit is contained in:
WolverinDEV 2020-03-30 13:44:18 +02:00
parent 447e84ed0f
commit 0e3a415983
153 changed files with 34926 additions and 34209 deletions

169
file.ts
View file

@ -444,6 +444,7 @@ const CLIENT_APP_FILE_LIST = [
...APP_FILE_LIST_CLIENT_SOURCE
];
/*
const WEB_APP_FILE_LIST = [
...APP_FILE_LIST_SHARED_SOURCE,
...APP_FILE_LIST_SHARED_VENDORS,
@ -451,6 +452,174 @@ const WEB_APP_FILE_LIST = [
...APP_FILE_LIST_WEB_TEASPEAK,
...CERTACCEPT_FILE_LIST,
];
*/
const WEB_APP_FILE_LIST = [
...APP_FILE_LIST_SHARED_VENDORS,
{ /* shared html and php files */
"type": "html",
"search-pattern": /^.*([a-zA-Z]+)\.(html|php|json)$/,
"build-target": "dev|rel",
"path": "./",
"local-path": "./shared/html/"
},
{ /* javascript loader for releases */
"type": "js",
"search-pattern": /.*$/,
"build-target": "dev|rel",
"path": "js/",
"local-path": "./dist/"
},
{ /* shared javascript files (WebRTC adapter) */
"type": "js",
"search-pattern": /.*\.js$/,
"build-target": "dev|rel",
"path": "adapter/",
"local-path": "./shared/adapter/"
},
{ /* shared generated worker codec */
"type": "js",
"search-pattern": /(WorkerPOW.js)$/,
"build-target": "dev|rel",
"path": "js/workers/",
"local-path": "./shared/js/workers/"
},
{ /* shared developer single css files */
"type": "css",
"search-pattern": /.*\.css$/,
"build-target": "dev",
"path": "css/",
"local-path": "./shared/css/"
},
{ /* shared css mapping files (development mode only) */
"type": "css",
"search-pattern": /.*\.(css.map|scss)$/,
"build-target": "dev",
"path": "css/",
"local-path": "./shared/css/",
"req-parm": ["--mappings"]
},
{ /* shared release css files */
"type": "css",
"search-pattern": /.*\.css$/,
"build-target": "rel",
"path": "css/",
"local-path": "./shared/generated/"
},
{ /* shared release css files */
"type": "css",
"search-pattern": /.*\.css$/,
"build-target": "rel",
"path": "css/loader/",
"local-path": "./shared/css/loader/"
},
{ /* shared release css files */
"type": "css",
"search-pattern": /.*\.css$/,
"build-target": "dev|rel",
"path": "css/theme/",
"local-path": "./shared/css/theme/"
},
{ /* 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 */
"type": "img",
"search-pattern": /.*\.(svg|png)/,
"build-target": "dev|rel",
"path": "img/",
"local-path": "./shared/img/"
},
{ /* own webassembly files */
"type": "wasm",
"search-pattern": /.*\.(wasm)/,
"build-target": "dev|rel",
"path": "wat/",
"local-path": "./shared/wat/"
},
/* web specific */
{ /* generated assembly files */
"web-only": true,
"type": "wasm",
"search-pattern": /.*\.(wasm)/,
"build-target": "dev|rel",
"path": "wasm/",
"local-path": "./asm/generated/"
},
{ /* generated assembly javascript files */
"web-only": true,
"type": "js",
"search-pattern": /.*\.(js)/,
"build-target": "dev|rel",
"path": "wasm/",
"local-path": "./asm/generated/"
},
{ /* web generated worker codec */
"web-only": true,
"type": "js",
"search-pattern": /(WorkerCodec.js)$/,
"build-target": "dev|rel",
"path": "js/workers/",
"local-path": "./web/js/workers/"
},
{ /* web css files */
"web-only": true,
"type": "css",
"search-pattern": /.*\.css$/,
"build-target": "dev|rel",
"path": "css/",
"local-path": "./web/css/"
},
{ /* web html files */
"web-only": true,
"type": "html",
"search-pattern": /.*\.(php|html)/,
"build-target": "dev|rel",
"path": "./",
"local-path": "./web/html/"
},
{ /* translations */
"web-only": true, /* Only required for the web client */
"type": "i18n",
"search-pattern": /.*\.(translation|json)/,
"build-target": "dev|rel",
"path": "i18n/",
"local-path": "./shared/i18n/"
}
] as any;
//@ts-ignore
declare module "fs-extra" {

View file

@ -1,22 +1,12 @@
/// <reference path="loader.ts" />
import * as loader from "./loader";
declare global {
interface Window {
$: JQuery;
native_client: boolean;
}
}
namespace app {
export enum Type {
UNKNOWN,
CLIENT_RELEASE,
CLIENT_DEBUG,
WEB_DEBUG,
WEB_RELEASE
}
export let type: Type = Type.UNKNOWN;
export function is_web() {
return type == Type.WEB_RELEASE || type == Type.WEB_DEBUG;
}
const node_require: typeof require = window.require;
let _ui_version;
export function ui_version() {
@ -31,11 +21,19 @@ namespace app {
}
return _ui_version;
}
}
/* all javascript loaders */
const loader_javascript = {
detect_type: async () => {
//TODO: Detect real version!
loader.set_version({
backend: "-",
ui: ui_version(),
debug_mode: true,
type: "web"
});
window.native_client = false;
return;
if(window.require) {
const request = new Request("js/proto.js");
let file_path = request.url;
@ -43,11 +41,11 @@ const loader_javascript = {
throw "Invalid file path (" + file_path + ")";
file_path = file_path.substring(process.platform === "win32" ? 8 : 7);
const fs = require('fs');
const fs = node_require('fs');
if(fs.existsSync(file_path)) {
app.type = app.Type.CLIENT_DEBUG;
//type = Type.CLIENT_DEBUG;
} else {
app.type = app.Type.CLIENT_RELEASE;
//type = Type.CLIENT_RELEASE;
}
} else {
/* test if js/proto.js is available. If so we're in debug mode */
@ -58,9 +56,9 @@ const loader_javascript = {
request.onreadystatechange = () => {
if (request.readyState === 4){
if (request.status === 404) {
app.type = app.Type.WEB_RELEASE;
//type = Type.WEB_RELEASE;
} else {
app.type = app.Type.WEB_DEBUG;
//type = Type.WEB_DEBUG;
}
resolve();
}
@ -101,7 +99,7 @@ const loader_javascript = {
["vendor/emoji-picker/src/jquery.lsxemojipicker.js"]
]);
if(app.type == app.Type.WEB_RELEASE || app.type == app.Type.CLIENT_RELEASE) {
if(!loader.version().debug_mode) {
loader.register_task(loader.Stage.JAVASCRIPT, {
name: "scripts release",
priority: 20,
@ -116,176 +114,8 @@ const loader_javascript = {
}
},
load_scripts_debug: async () => {
/* test if we're loading as TeaClient or WebClient */
if(!window.require) {
loader.register_task(loader.Stage.JAVASCRIPT, {
name: "javascript web",
priority: 10,
function: loader_javascript.load_scripts_debug_web
});
} else {
loader.register_task(loader.Stage.JAVASCRIPT, {
name: "javascript client",
priority: 10,
function: loader_javascript.load_scripts_debug_client
});
}
/* load some extends classes */
await loader.load_scripts([
["js/connection/ConnectionBase.js"]
]);
/* load the main app */
await loader.load_scripts([
//Load general API's
"js/proto.js",
"js/i18n/localize.js",
"js/i18n/country.js",
"js/log.js",
"js/events.js",
"js/sound/Sounds.js",
"js/utils/helpers.js",
"js/crypto/sha.js",
"js/crypto/hex.js",
"js/crypto/asn1.js",
"js/crypto/crc32.js",
//load the profiles
"js/profiles/ConnectionProfile.js",
"js/profiles/Identity.js",
"js/profiles/identities/teaspeak-forum.js",
//Basic UI elements
"js/ui/elements/context_divider.js",
"js/ui/elements/context_menu.js",
"js/ui/elements/modal.js",
"js/ui/elements/tab.js",
"js/ui/elements/slider.js",
"js/ui/elements/tooltip.js",
"js/ui/elements/net_graph.js",
//Load permissions
"js/permission/PermissionManager.js",
"js/permission/GroupManager.js",
//Load UI
"js/ui/modal/ModalAbout.js",
"js/ui/modal/ModalAvatar.js",
"js/ui/modal/ModalAvatarList.js",
"js/ui/modal/ModalClientInfo.js",
"js/ui/modal/ModalChannelInfo.js",
"js/ui/modal/ModalServerInfo.js",
"js/ui/modal/ModalServerInfoBandwidth.js",
"js/ui/modal/ModalQuery.js",
"js/ui/modal/ModalQueryManage.js",
"js/ui/modal/ModalPlaylistList.js",
"js/ui/modal/ModalPlaylistEdit.js",
"js/ui/modal/ModalBookmarks.js",
"js/ui/modal/ModalConnect.js",
"js/ui/modal/ModalSettings.js",
"js/ui/modal/ModalNewcomer.js",
"js/ui/modal/ModalCreateChannel.js",
"js/ui/modal/ModalServerEdit.js",
"js/ui/modal/ModalChangeVolume.js",
"js/ui/modal/ModalChangeLatency.js",
"js/ui/modal/ModalBanClient.js",
"js/ui/modal/ModalIconSelect.js",
"js/ui/modal/ModalInvite.js",
"js/ui/modal/ModalIdentity.js",
"js/ui/modal/ModalBanList.js",
"js/ui/modal/ModalMusicManage.js",
"js/ui/modal/ModalYesNo.js",
"js/ui/modal/ModalPoke.js",
"js/ui/modal/ModalKeySelect.js",
"js/ui/modal/ModalGroupAssignment.js",
"js/ui/modal/permission/ModalPermissionEdit.js",
{url: "js/ui/modal/permission/SenselessPermissions.js", depends: ["js/permission/PermissionManager.js"]},
{url: "js/ui/modal/permission/CanvasPermissionEditor.js", depends: ["js/ui/modal/permission/ModalPermissionEdit.js"]},
{url: "js/ui/modal/permission/HTMLPermissionEditor.js", depends: ["js/ui/modal/permission/ModalPermissionEdit.js"]},
"js/channel-tree/channel.js",
"js/channel-tree/client.js",
"js/channel-tree/server.js",
"js/channel-tree/view.js",
"js/ui/client_move.js",
"js/ui/htmltags.js",
"js/ui/frames/side/chat_helper.js",
"js/ui/frames/side/chat_box.js",
"js/ui/frames/side/client_info.js",
"js/ui/frames/side/music_info.js",
"js/ui/frames/side/conversations.js",
"js/ui/frames/side/private_conversations.js",
"js/ui/frames/ControlBar.js",
"js/ui/frames/chat.js",
"js/ui/frames/chat_frame.js",
"js/ui/frames/connection_handlers.js",
"js/ui/frames/server_log.js",
"js/ui/frames/hostbanner.js",
"js/ui/frames/MenuBar.js",
"js/ui/frames/image_preview.js",
//Load audio
"js/voice/RecorderBase.js",
"js/voice/RecorderProfile.js",
//Load general stuff
"js/settings.js",
"js/bookmarks.js",
"js/FileManager.js",
"js/ConnectionHandler.js",
"js/BrowserIPC.js",
"js/MessageFormatter.js",
//Connection
"js/connection/CommandHandler.js",
"js/connection/CommandHelper.js",
"js/connection/HandshakeHandler.js",
"js/connection/ServerConnectionDeclaration.js",
"js/stats.js",
"js/PPTListener.js",
"js/profiles/identities/NameIdentity.js", //Depends on Identity
"js/profiles/identities/TeaForumIdentity.js", //Depends on Identity
"js/profiles/identities/TeamSpeakIdentity.js", //Depends on Identity
]);
await loader.load_script("js/main.js");
await loader.load_scripts(["js/shared-app.js"])
},
load_scripts_debug_web: async () => {
await loader.load_scripts([
"js/audio/AudioPlayer.js",
"js/audio/sounds.js",
"js/audio/WebCodec.js",
"js/WebPPTListener.js",
"js/voice/AudioResampler.js",
"js/voice/JavascriptRecorder.js",
"js/voice/VoiceHandler.js",
"js/voice/VoiceClient.js",
//Connection
"js/connection/ServerConnection.js",
"js/dns_impl.js",
//Load codec
"js/codec/Codec.js",
"js/codec/BasicCodec.js",
{url: "js/codec/CodecWrapperWorker.js", depends: ["js/codec/BasicCodec.js"]},
]);
},
load_scripts_debug_client: async () => {
await loader.load_scripts([
]);
},
load_release: async () => {
console.log("Load for release!");
@ -329,7 +159,7 @@ const loader_style = {
["vendor/highlight/styles/darcula.css", ""], /* empty string means not required */
]);
if(app.type == app.Type.WEB_DEBUG || app.type == app.Type.CLIENT_DEBUG) {
if(loader.version().debug_mode) {
await loader_style.load_style_debug();
} else {
await loader_style.load_style_release();
@ -494,23 +324,10 @@ loader.register_task(loader.Stage.TEMPLATES, {
loader.register_task(loader.Stage.LOADED, {
name: "loaded handler",
function: async () => {
fadeoutLoader();
},
function: async () => loader.hide_overlay(),
priority: 5
});
loader.register_task(loader.Stage.LOADED, {
name: "error task",
function: async () => {
if(Settings.instance.static(Settings.KEY_LOAD_DUMMY_ERROR, false)) {
loader.critical_error("The tea is cold!", "Argh, this is evil! Cold tea dosn't taste good.");
throw "The tea is cold!";
}
},
priority: 20
});
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "lsx emoji picker setup",
function: async () => await (window as any).setup_lsx_emoji_picker({twemoji: typeof(window.twemoji) !== "undefined"}),
@ -576,23 +393,17 @@ loader.register_task(loader.Stage.SETUP, {
priority: 100
});
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "log enabled initialisation",
function: async () => log.initialize(app.type === app.Type.CLIENT_DEBUG || app.type === app.Type.WEB_DEBUG ? log.LogType.TRACE : log.LogType.INFO),
priority: 150
});
export function run() {
window["Module"] = (window["Module"] || {}) as any;
/* TeaClient */
if(window.require) {
const path = require("path");
const remote = require('electron').remote;
if(node_require) {
const path = node_require("path");
const remote = node_require('electron').remote;
module.paths.push(path.join(remote.app.getAppPath(), "/modules"));
module.paths.push(path.join(path.dirname(remote.getGlobal("browser-root")), "js"));
const connector = require("renderer");
console.log(connector);
//TODO: HERE!
const connector = node_require("renderer");
loader.register_task(loader.Stage.INITIALIZING, {
name: "teaclient initialize",
function: connector.initialize,
@ -604,3 +415,4 @@ if(!loader.running()) {
/* we know that we want to load the app */
loader.execute_managed();
}
}

View file

@ -1,4 +1,4 @@
/// <reference path="loader.ts" />
import * as loader from "./loader";
let is_debug = false;
@ -25,34 +25,8 @@ const loader_javascript = {
load_scripts: async () => {
await loader.load_script(["vendor/jquery/jquery.min.js"]);
if(!is_debug) {
loader.register_task(loader.Stage.JAVASCRIPT, {
name: "scripts release",
priority: 20,
function: loader_javascript.load_release
});
} else {
loader.register_task(loader.Stage.JAVASCRIPT, {
name: "scripts debug",
priority: 20,
function: loader_javascript.load_scripts_debug
});
}
},
load_scripts_debug: async () => {
await loader.load_scripts([
["js/proto.js"],
["js/log.js"],
["js/BrowserIPC.js"],
["js/settings.js"],
["js/main.js"]
]);
},
load_release: async () => {
await loader.load_scripts([
["js/certaccept.min.js", "js/certaccept.js"]
["dist/certificate-popup.js"],
]);
}
};
@ -99,9 +73,7 @@ loader.register_task(loader.Stage.STYLE, {
loader.register_task(loader.Stage.LOADED, {
name: "loaded handler",
function: async () => {
fadeoutLoader();
},
function: async () => loader.hide_overlay(),
priority: 0
});
@ -123,25 +95,6 @@ loader.register_task(loader.Stage.INITIALIZING, {
priority: 50
});
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "settings initialisation",
function: async () => Settings.initialize(),
priority: 200
});
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "bipc initialisation",
function: async () => bipc.setup(),
priority: 100
});
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "log enabled initialisation",
function: async () => log.initialize(is_debug ? log.LogType.TRACE : log.LogType.INFO),
priority: 150
});
if(!loader.running()) {
/* we know that we want to load the app */
loader.execute_managed();

5
loader/app/index.ts Normal file
View file

@ -0,0 +1,5 @@
import * as loader from "./app";
import * as loader_base from "./loader";
export = loader_base;
loader.run();

712
loader/app/loader.ts Normal file
View file

@ -0,0 +1,712 @@
import {AppVersion} from "../exports/loader";
import {type} from "os";
declare global {
interface Window {
tr(message: string) : string;
tra(message: string, ...args: any[]);
log: any;
StaticSettings: any;
}
}
export interface Config {
loader_groups: boolean;
verbose: boolean;
error: boolean;
}
export let config: Config = {
loader_groups: false,
verbose: false,
error: true
};
export type Task = {
name: string,
priority: number, /* tasks with the same priority will be executed in sync */
function: () => Promise<void>
};
export enum Stage {
/*
loading loader required files (incl this)
*/
INITIALIZING,
/*
setting up the loading process
*/
SETUP,
/*
loading all style sheet files
*/
STYLE,
/*
loading all javascript files
*/
JAVASCRIPT,
/*
loading all template files
*/
TEMPLATES,
/*
initializing static/global stuff
*/
JAVASCRIPT_INITIALIZING,
/*
finalizing load process
*/
FINALIZING,
/*
invoking main task
*/
LOADED,
DONE
}
let cache_tag: string | undefined;
let current_stage: Stage = undefined;
const tasks: {[key:number]:Task[]} = {};
/* test if all files shall be load from cache or fetch again */
function loader_cache_tag() {
const app_version = (() => {
const version_node = document.getElementById("app_version");
if(!version_node) return undefined;
const version = version_node.hasAttribute("value") ? version_node.getAttribute("value") : undefined;
if(!version) return undefined;
if(!version || version == "unknown" || version.replace(/0+/, "").length == 0)
return undefined;
return version;
})();
if(config.verbose) console.log("Found current app version: %o", app_version);
if(!app_version) {
/* TODO add warning */
cache_tag = "?_ts=" + Date.now();
return;
}
const cached_version = localStorage.getItem("cached_version");
if(!cached_version || cached_version != app_version) {
register_task(Stage.LOADED, {
priority: 0,
name: "cached version updater",
function: async () => {
localStorage.setItem("cached_version", app_version);
}
});
}
cache_tag = "?_version=" + app_version;
}
export function get_cache_version() { return cache_tag; }
export function finished() {
return current_stage == Stage.DONE;
}
export function running() { return typeof(current_stage) !== "undefined"; }
export function register_task(stage: Stage, task: Task) {
if(current_stage > stage) {
if(config.error)
console.warn("Register loading task, but it had already been finished. Executing task anyways!");
task.function().catch(error => {
if(config.error) {
console.error("Failed to execute delayed loader task!");
console.log(" - %s: %o", task.name, error);
}
critical_error(error);
});
return;
}
const task_array = tasks[stage] || [];
task_array.push(task);
tasks[stage] = task_array.sort((a, b) => a.priority - b.priority);
}
export async function execute() {
document.getElementById("loader-overlay").classList.add("started");
loader_cache_tag();
const load_begin = Date.now();
let begin: number = 0;
let end: number = Date.now();
while(current_stage <= Stage.LOADED || typeof(current_stage) === "undefined") {
let current_tasks: Task[] = [];
while((tasks[current_stage] || []).length > 0) {
if(current_tasks.length == 0 || current_tasks[0].priority == tasks[current_stage][0].priority) {
current_tasks.push(tasks[current_stage].pop());
} else break;
}
const errors: {
error: any,
task: Task
}[] = [];
const promises: Promise<void>[] = [];
for(const task of current_tasks) {
try {
if(config.verbose) console.debug("Executing loader %s (%d)", task.name, task.priority);
promises.push(task.function().catch(error => {
errors.push({
task: task,
error: error
});
return Promise.resolve();
}));
} catch(error) {
errors.push({
task: task,
error: error
});
}
}
if(promises.length > 0) {
await Promise.all([...promises]);
}
if(errors.length > 0) {
if(config.loader_groups) console.groupEnd();
console.error("Failed to execute loader. The following tasks failed (%d):", errors.length);
for(const error of errors)
console.error(" - %s: %o", error.task.name, error.error);
throw "failed to process step " + Stage[current_stage];
}
if(current_tasks.length == 0) {
if(typeof(current_stage) === "undefined") {
current_stage = -1;
if(config.verbose) console.debug("[loader] Booting app");
} else if(current_stage < Stage.INITIALIZING) {
if(config.loader_groups) console.groupEnd();
if(config.verbose) console.debug("[loader] Entering next state (%s). Last state took %dms", Stage[current_stage + 1], (end = Date.now()) - begin);
} else {
if(config.loader_groups) console.groupEnd();
if(config.verbose) console.debug("[loader] Finish invoke took %dms", (end = Date.now()) - begin);
}
begin = end;
current_stage += 1;
if(current_stage != Stage.DONE && config.loader_groups)
console.groupCollapsed("Executing loading stage %s", Stage[current_stage]);
}
}
/* cleanup */
{
_script_promises = {};
}
if(config.verbose) console.debug("[loader] finished loader. (Total time: %dms)", Date.now() - load_begin);
}
export function execute_managed() {
execute().then(() => {
if(config.verbose) {
let message;
if(typeof(window.tr) !== "undefined")
message = tr("App loaded successfully!");
else
message = "App loaded successfully!";
if(typeof(window.log) !== "undefined") {
/* We're having our log module */
window.log.info(window.log.LogCategory.GENERAL, message);
} else {
console.log(message);
}
}
}).catch(error => {
console.error("App loading failed: %o", error);
critical_error("Failed to execute loader", "Lookup the console for more detail");
});
}
export type DependSource = {
url: string;
depends: string[];
}
export type SourcePath = string | DependSource | string[];
function script_name(path: SourcePath, html: boolean) {
if(Array.isArray(path)) {
let buffer = "";
let _or = " or ";
for(let entry of path)
buffer += _or + script_name(entry, html);
return buffer.slice(_or.length);
} else if(typeof(path) === "string")
return html ? "<code>" + path + "</code>" : path;
else
return html ? "<code>" + path.url + "</code>" : path.url;
}
class SyntaxError {
source: any;
constructor(source: any) {
this.source = source;
}
}
let _script_promises: {[key: string]: Promise<void>} = {};
export async function load_script(path: SourcePath) : Promise<void> {
if(Array.isArray(path)) { //We have some fallback
return load_script(path[0]).catch(error => {
console.log(typeof error + " - " + (error instanceof SyntaxError));
if(error instanceof SyntaxError)
return Promise.reject(error);
if(path.length > 1)
return load_script(path.slice(1));
return Promise.reject(error);
});
} else {
const source = typeof(path) === "string" ? {url: path, depends: []} : path;
if(source.url.length == 0) return Promise.resolve();
return _script_promises[source.url] = (async () => {
/* await depends */
for(const depend of source.depends) {
if(!_script_promises[depend])
throw "Missing dependency " + depend;
await _script_promises[depend];
}
const tag: HTMLScriptElement = document.createElement("script");
await new Promise((resolve, reject) => {
let error = false;
const error_handler = (event: ErrorEvent) => {
if(event.filename == tag.src && event.message.indexOf("Illegal constructor") == -1) { //Our tag throw an uncaught error
if(config.verbose) console.log("msg: %o, url: %o, line: %o, col: %o, error: %o", event.message, event.filename, event.lineno, event.colno, event.error);
window.removeEventListener('error', error_handler as any);
reject(new SyntaxError(event.error));
event.preventDefault();
error = true;
}
};
window.addEventListener('error', error_handler as any);
const cleanup = () => {
tag.onerror = undefined;
tag.onload = undefined;
clearTimeout(timeout_handle);
window.removeEventListener('error', error_handler as any);
};
const timeout_handle = setTimeout(() => {
cleanup();
reject("timeout");
}, 5000);
tag.type = "application/javascript";
tag.async = true;
tag.defer = true;
tag.onerror = error => {
cleanup();
tag.remove();
reject(error);
};
tag.onload = () => {
cleanup();
if(config.verbose) console.debug("Script %o loaded", path);
setTimeout(resolve, 100);
};
document.getElementById("scripts").appendChild(tag);
tag.src = source.url + (cache_tag || "");
});
})();
}
}
export async function load_scripts(paths: SourcePath[]) : Promise<void> {
const promises: Promise<void>[] = [];
const errors: {
script: SourcePath,
error: any
}[] = [];
for(const script of paths)
promises.push(load_script(script).catch(error => {
errors.push({
script: script,
error: error
});
return Promise.resolve();
}));
await Promise.all([...promises]);
if(errors.length > 0) {
if(config.error) {
console.error("Failed to load the following scripts:");
for(const script of errors) {
const sname = script_name(script.script, false);
if(script.error instanceof SyntaxError) {
const source = script.error.source as Error;
if(source.name === "TypeError") {
let prefix = "";
while(prefix.length < sname.length + 7) prefix += " ";
console.log(" - %s: %s:\n%s", sname, source.message, source.stack.split("\n").map(e => prefix + e.trim()).slice(1).join("\n"));
} else {
console.log(" - %s: %o", sname, source);
}
} else {
console.log(" - %s: %o", sname, script.error);
}
}
}
critical_error("Failed to load script " + script_name(errors[0].script, true) + " <br>" + "View the browser console for more information!");
throw "failed to load script " + script_name(errors[0].script, false);
}
}
export async function load_style(path: SourcePath) : Promise<void> {
if(Array.isArray(path)) { //We have some fallback
return load_style(path[0]).catch(error => {
if(error instanceof SyntaxError)
return Promise.reject(error.source);
if(path.length > 1)
return load_script(path.slice(1));
return Promise.reject(error);
});
} else {
if(!path) {
return Promise.resolve();
}
return new Promise<void>((resolve, reject) => {
const tag: HTMLLinkElement = document.createElement("link");
let error = false;
const error_handler = (event: ErrorEvent) => {
if(config.verbose) console.log("msg: %o, url: %o, line: %o, col: %o, error: %o", event.message, event.filename, event.lineno, event.colno, event.error);
if(event.filename == tag.href) { //FIXME!
window.removeEventListener('error', error_handler as any);
reject(new SyntaxError(event.error));
event.preventDefault();
error = true;
}
};
window.addEventListener('error', error_handler as any);
tag.type = "text/css";
tag.rel = "stylesheet";
const cleanup = () => {
tag.onerror = undefined;
tag.onload = undefined;
clearTimeout(timeout_handle);
window.removeEventListener('error', error_handler as any);
};
const timeout_handle = setTimeout(() => {
cleanup();
reject("timeout");
}, 5000);
tag.onerror = error => {
cleanup();
tag.remove();
if(config.error)
console.error("File load error for file %s: %o", path, error);
reject("failed to load file " + path);
};
tag.onload = () => {
cleanup();
{
const css: CSSStyleSheet = tag.sheet as CSSStyleSheet;
const rules = css.cssRules;
const rules_remove: number[] = [];
const rules_add: string[] = [];
for(let index = 0; index < rules.length; index++) {
const rule = rules.item(index);
let rule_text = rule.cssText;
if(rule.cssText.indexOf("%%base_path%%") != -1) {
rules_remove.push(index);
rules_add.push(rule_text.replace("%%base_path%%", document.location.origin + document.location.pathname));
}
}
for(const index of rules_remove.sort((a, b) => b > a ? 1 : 0)) {
if(css.removeRule)
css.removeRule(index);
else
css.deleteRule(index);
}
for(const rule of rules_add)
css.insertRule(rule, rules_remove[0]);
}
if(config.verbose) console.debug("Style sheet %o loaded", path);
setTimeout(resolve, 100);
};
document.getElementById("style").appendChild(tag);
tag.href = path + (cache_tag || "");
});
}
}
export async function load_styles(paths: SourcePath[]) : Promise<void> {
const promises: Promise<void>[] = [];
const errors: {
sheet: SourcePath,
error: any
}[] = [];
for(const sheet of paths)
promises.push(load_style(sheet).catch(error => {
errors.push({
sheet: sheet,
error: error
});
return Promise.resolve();
}));
await Promise.all([...promises]);
if(errors.length > 0) {
if(config.error) {
console.error("Failed to load the following style sheet:");
for(const sheet of errors)
console.log(" - %o: %o", sheet.sheet, sheet.error);
}
critical_error("Failed to load style sheet " + script_name(errors[0].sheet, true) + " <br>" + "View the browser console for more information!");
throw "failed to load style sheet " + script_name(errors[0].sheet, false);
}
}
export async function load_template(path: SourcePath) : Promise<void> {
try {
const response = await $.ajax(path + (cache_tag || ""));
let node = document.createElement("html");
node.innerHTML = response;
let tags: HTMLCollection;
if(node.getElementsByTagName("body").length > 0)
tags = node.getElementsByTagName("body")[0].children;
else
tags = node.children;
let root = document.getElementById("templates");
if(!root) {
critical_error("Failed to find template tag!");
throw "Failed to find template tag";
}
while(tags.length > 0){
let tag = tags.item(0);
root.appendChild(tag);
}
} catch(error) {
let msg;
if('responseText' in error)
msg = error.responseText;
else if(error instanceof Error)
msg = error.message;
critical_error("failed to load template " + script_name(path, true), msg);
throw "template error";
}
}
export async function load_templates(paths: SourcePath[]) : Promise<void> {
const promises: Promise<void>[] = [];
const errors: {
template: SourcePath,
error: any
}[] = [];
for(const template of paths)
promises.push(load_template(template).catch(error => {
errors.push({
template: template,
error: error
});
return Promise.resolve();
}));
await Promise.all([...promises]);
if(errors.length > 0) {
if (config.error) {
console.error("Failed to load the following templates:");
for (const sheet of errors)
console.log(" - %s: %o", script_name(sheet.template, false), sheet.error);
}
critical_error("Failed to load template " + script_name(errors[0].template, true) + " <br>" + "View the browser console for more information!");
throw "failed to load template " + script_name(errors[0].template, false);
}
}
let version_: AppVersion;
export function version() : AppVersion { return version_; }
export function set_version(version: AppVersion) { version_ = version; }
export type ErrorHandler = (message: string, detail: string) => void;
let _callback_critical_error: ErrorHandler;
let _callback_critical_called: boolean = false;
export function critical_error(message: string, detail?: string) {
if(_callback_critical_called) {
console.warn("[CRITICAL] %s", message);
if(typeof(detail) === "string")
console.warn("[CRITICAL] %s", detail);
return;
}
_callback_critical_called = true;
if(_callback_critical_error) {
_callback_critical_error(message, detail);
return;
}
/* default handling */
let tag = document.getElementById("critical-load");
{
const error_tags = tag.getElementsByClassName("error");
error_tags[0].innerHTML = message;
}
if(typeof(detail) === "string") {
let node_detail = tag.getElementsByClassName("detail")[0];
node_detail.innerHTML = detail;
}
tag.classList.add("shown");
}
export function critical_error_handler(handler?: ErrorHandler, override?: boolean) : ErrorHandler {
if((typeof(handler) === "object" && handler !== _callback_critical_error) || override)
_callback_critical_error = handler;
return _callback_critical_error;
}
let _fadeout_warned;
export function hide_overlay() {
if(typeof($) === "undefined") {
if(!_fadeout_warned)
console.warn("Could not fadeout loader screen. Missing jquery functions.");
_fadeout_warned = true;
return;
}
const animation_duration = 750;
$(".loader .bookshelf_wrapper").animate({top: 0, opacity: 0}, animation_duration);
$(".loader .half").animate({width: 0}, animation_duration, () => {
$(".loader").detach();
});
}
{
const hello_world = () => {
const clog = console.log;
const print_security = () => {
{
const css = [
"display: block",
"text-align: center",
"font-size: 42px",
"font-weight: bold",
"-webkit-text-stroke: 2px black",
"color: red"
].join(";");
clog("%c ", "font-size: 100px;");
clog("%cSecurity warning:", css);
}
{
const css = [
"display: block",
"text-align: center",
"font-size: 18px",
"font-weight: bold"
].join(";");
clog("%cPasting anything in here could give attackers access to your data.", css);
clog("%cUnless you understand exactly what you are doing, close this window and stay safe.", css);
clog("%c ", "font-size: 100px;");
}
};
/* print the hello world */
{
const css = [
"display: block",
"text-align: center",
"font-size: 72px",
"font-weight: bold",
"-webkit-text-stroke: 2px black",
"color: #18BC9C"
].join(";");
clog("%cHey, hold on!", css);
}
{
const css = [
"display: block",
"text-align: center",
"font-size: 26px",
"font-weight: bold"
].join(";");
const css_2 = [
"display: block",
"text-align: center",
"font-size: 26px",
"font-weight: bold",
"color: blue"
].join(";");
const display_detect = /./;
display_detect.toString = function() { print_security(); return ""; };
clog("%cLovely to see you using and debugging the TeaSpeak-Web client.", css);
clog("%cIf you have some good ideas or already done some incredible changes,", css);
clog("%cyou'll be may interested to share them here: %chttps://github.com/TeaSpeak/TeaWeb", css, css_2);
clog("%c ", display_detect);
}
};
try { /* lets try to print it as VM code :)*/
let hello_world_code = hello_world.toString();
hello_world_code = hello_world_code.substr(hello_world_code.indexOf('() => {') + 8);
hello_world_code = hello_world_code.substring(0, hello_world_code.lastIndexOf("}"));
//Look aheads are not possible with firefox
//hello_world_code = hello_world_code.replace(/(?<!const|let)(?<=^([^"'/]|"[^"]*"|'[^']*'|`[^`]*`|\/[^/]*\/)*) /gm, ""); /* replace all spaces */
hello_world_code = hello_world_code.replace(/[\n\r]/g, ""); /* replace as new lines */
eval(hello_world_code);
} catch(e) {
console.error(e);
hello_world();
}
}

85
loader/exports/loader.d.ts vendored Normal file
View file

@ -0,0 +1,85 @@
export interface Config {
loader_groups: boolean;
verbose: boolean;
error: boolean;
}
export enum BackendType {
WEB,
NATIVE
}
export interface AppVersion {
ui: string;
backend: string;
type: "web" | "native";
debug_mode: boolean;
}
export let config: Config;
export type Task = {
name: string,
priority: number, /* tasks with the same priority will be executed in sync */
function: () => Promise<void>
};
export enum Stage {
/*
loading loader required files (incl this)
*/
INITIALIZING,
/*
setting up the loading process
*/
SETUP,
/*
loading all style sheet files
*/
STYLE,
/*
loading all javascript files
*/
JAVASCRIPT,
/*
loading all template files
*/
TEMPLATES,
/*
initializing static/global stuff
*/
JAVASCRIPT_INITIALIZING,
/*
finalizing load process
*/
FINALIZING,
/*
invoking main task
*/
LOADED,
DONE
}
export function version() : AppVersion;
export function finished();
export function running();
export function register_task(stage: Stage, task: Task);
export function execute() : Promise<void>;
export function execute_managed();
export type DependSource = {
url: string;
depends: string[];
}
export type SourcePath = string | DependSource | string[];
export function load_script(path: SourcePath) : Promise<void>;
export function load_scripts(paths: SourcePath[]) : Promise<void>;
export function load_style(path: SourcePath) : Promise<void>;
export function load_styles(paths: SourcePath[]) : Promise<void>;
export function load_template(path: SourcePath) : Promise<void>;
export function load_templates(paths: SourcePath[]) : Promise<void>;
export type ErrorHandler = (message: string, detail: string) => void;
export function critical_error(message: string, detail?: string);
export function critical_error_handler(handler?: ErrorHandler, override?: boolean);
export function hide_overlay();

62
loader/webpack.config.js Normal file
View file

@ -0,0 +1,62 @@
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const isDevelopment = process.env.NODE_ENV === 'development';
module.exports = {
entry: path.join(__dirname, "app/index.ts"),
devtool: 'inline-source-map',
mode: "development",
plugins: [
new MiniCssExtractPlugin({
filename: isDevelopment ? '[name].css' : '[name].[hash].css',
chunkFilename: isDevelopment ? '[id].css' : '[id].[hash].css'
})
],
module: {
rules: [
{
test: /\.s[ac]ss$/,
loader: [
//isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
sourceMap: isDevelopment
}
},
{
loader: 'sass-loader',
options: {
sourceMap: isDevelopment
}
}
]
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
loader: [
{
loader: 'ts-loader',
options: {
transpileOnly: true
}
}
]
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js', ".scss"],
},
output: {
filename: 'loader.js',
path: path.resolve(__dirname, '../dist'),
library: "loader",
//libraryTarget: "umd" //"var" | "assign" | "this" | "window" | "self" | "global" | "commonjs" | "commonjs2" | "commonjs-module" | "amd" | "amd-require" | "umd" | "umd2" | "jsonp" | "system"
},
optimization: { }
};

5
package-lock.json generated
View file

@ -1087,6 +1087,11 @@
"safe-buffer": "^5.0.1"
}
},
"circular-dependency-plugin": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.0.tgz",
"integrity": "sha512-7p4Kn/gffhQaavNfyDFg7LS5S/UT1JAjyGd4UqR2+jzoYF02eDkj0Ec3+48TsIa4zghjLY87nQHIh/ecK9qLdw=="
},
"clap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz",

View file

@ -19,7 +19,9 @@
"minify-web-rel-file": "terser --compress --mangle --ecma 6 --keep_classnames --keep_fnames --output",
"start": "npm run compile-file-helper && node file.js ndevelop",
"build": "webpack --config webpack.config.js",
"watch": "webpack --watch"
"build-loader": "webpack --config loader/webpack.config.js",
"watch": "webpack --watch",
"watch-loader": "webpack --watch --config loader/webpack.config.js"
},
"author": "TeaSpeak (WolverinDEV)",
"license": "ISC",
@ -63,6 +65,7 @@
"homepage": "https://www.teaspeak.de",
"dependencies": {
"@types/fs-extra": "^8.0.1",
"circular-dependency-plugin": "^5.2.0",
"react": "^16.13.1",
"react-dom": "^16.13.1"
}

19
shared/backend.d/audio/player.d.ts vendored Normal file
View file

@ -0,0 +1,19 @@
import {Device} from "tc-shared/audio/player";
export function initialize() : boolean;
export function initialized() : boolean;
export function context() : AudioContext;
export function get_master_volume() : number;
export function set_master_volume(volume: number);
export function destination() : AudioNode;
export function on_ready(cb: () => any);
export function available_devices() : Promise<Device[]>;
export function set_device(device_id: string) : Promise<void>;
export function current_device() : Device;
export function initializeFromGesture();

9
shared/backend.d/audio/recorder.d.ts vendored Normal file
View file

@ -0,0 +1,9 @@
import {AbstractInput, InputDevice, LevelMeter} from "tc-shared/voice/RecorderBase";
export function devices() : InputDevice[];
export function device_refresh_available() : boolean;
export function refresh_devices() : Promise<void>;
export function create_input() : AbstractInput;
export function create_levelmeter(device: InputDevice) : Promise<LevelMeter>;

3
shared/backend.d/audio/sounds.d.ts vendored Normal file
View file

@ -0,0 +1,3 @@
import {SoundFile} from "tc-shared/sound/Sounds";
export function play_sound(file: SoundFile) : Promise<void>;

5
shared/backend.d/connection.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
export function spawn_server_connection(handle: ConnectionHandler) : AbstractServerConnection;
export function destroy_server_connection(handle: AbstractServerConnection);

5
shared/backend.d/dns.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
import {AddressTarget, ResolveOptions} from "tc-shared/dns";
import {ServerAddress} from "tc-shared/ui/server";
export function supported();
export function resolve_address(address: ServerAddress, options?: ResolveOptions) : Promise<AddressTarget>;

12
shared/backend.d/ppt.d.ts vendored Normal file
View file

@ -0,0 +1,12 @@
import {KeyEvent, KeyHook, SpecialKey} from "tc-shared/PPTListener";
export function initialize() : Promise<void>;
export function finalize(); // most the times not really required
export function register_key_listener(listener: (_: KeyEvent) => any);
export function unregister_key_listener(listener: (_: KeyEvent) => any);
export function register_key_hook(hook: KeyHook);
export function unregister_key_hook(hook: KeyHook);
export function key_pressed(code: string | SpecialKey) : boolean;

View file

@ -1,35 +0,0 @@
declare namespace audio {
export namespace player {
export function initialize() : boolean;
export function initialized() : boolean;
export function context() : AudioContext;
export function get_master_volume() : number;
export function set_master_volume(volume: number);
export function destination() : AudioNode;
export function on_ready(cb: () => any);
export function available_devices() : Promise<Device[]>;
export function set_device(device_id: string) : Promise<void>;
export function current_device() : Device;
export function initializeFromGesture();
}
export namespace recorder {
export function devices() : InputDevice[];
export function device_refresh_available() : boolean;
export function refresh_devices() : Promise<void>;
export function create_input() : AbstractInput;
export function create_levelmeter(device: InputDevice) : Promise<LevelMeter>;
}
export namespace sounds {
export function play_sound(file: sound.SoundFile) : Promise<void>;
}
}

View file

@ -1,4 +0,0 @@
declare namespace connection {
export function spawn_server_connection(handle: ConnectionHandler) : AbstractServerConnection;
export function destroy_server_connection(handle: AbstractServerConnection);
}

View file

@ -1,4 +0,0 @@
declare namespace dns {
export function supported();
export function resolve_address(address: ServerAddress, options?: ResolveOptions) : Promise<AddressTarget>;
}

View file

@ -1,12 +0,0 @@
declare namespace ppt {
export function initialize() : Promise<void>;
export function finalize(); // most the times not really required
export function register_key_listener(listener: (_: KeyEvent) => any);
export function unregister_key_listener(listener: (_: KeyEvent) => any);
export function register_key_hook(hook: KeyHook);
export function unregister_key_hook(hook: KeyHook);
export function key_pressed(code: string | SpecialKey) : boolean;
}

View file

@ -94,7 +94,96 @@
}
&.channel {
display: flex;
flex-direction: column;
.container-channel {
position: relative;
display: flex;
flex-direction: row;
justify-content: stretch;
width: 100%;
min-height: 16px;
align-items: center;
cursor: pointer;
.channel-type {
flex-grow: 0;
flex-shrink: 0;
margin-right: 2px;
}
.container-channel-name {
display: flex;
flex-direction: row;
flex-grow: 1;
flex-shrink: 1;
justify-content: left;
max-width: 100%; /* important for the repetitive channel name! */
overflow-x: hidden;
height: 16px;
&.align-right {
justify-content: right;
}
&.align-center, &.align-repetitive {
justify-content: center;
}
.channel-name {
align-self: center;
color: $channel_tree_entry_text_color;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&.align-repetitive {
.channel-name {
text-overflow: clip;
}
}
}
.icons {
display: flex;
flex-direction: row;
flex-grow: 0;
flex-shrink: 0;
}
&.move-selected {
border-bottom: 1px solid black;
}
.show-channel-normal-only {
display: none;
&.channel-normal {
display: block;
}
}
.icon_no_sound {
display: flex;
}
}
.container-clients {
display: flex;
flex-direction: column;
}
}
&.client {
@ -183,6 +272,40 @@
}
}
&.channel .container-channel, &.client, &.server {
.marker-text-unread {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 1px;
background-color: #a814147F;
opacity: 1;
&:before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 24px;
background: -moz-linear-gradient(left, rgba(168,20,20,.18) 0%, rgba(168,20,20,0) 100%); /* FF3.6-15 */
background: -webkit-linear-gradient(left, rgba(168,20,20,.18) 0%,rgba(168,20,20,0) 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(to right, rgba(168,20,20,.18) 0%,rgba(168,20,20,0) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
}
&.hidden {
opacity: 0;
}
@include transition(opacity $button_hover_animation_time);
}
}
}
}

View file

@ -145,9 +145,11 @@
<meta name="app-loader-target" content="app">
<div id="scripts">
<!--
<script type="application/javascript" src="loader/loader_app.min.js" async defer></script>
<script type="application/javascript" src="loader/loader_app.js" async defer></script>
<script type="application/javascript" src="loader/loader.js?_<?php echo time() ?>" async defer></script>
-->
<script type="application/javascript" src="js/loader.js?_<?php echo time() ?>" async defer></script>
</div>
<!-- Loading screen -->

View file

@ -2736,13 +2736,13 @@
</div>
{{else type == "default" }}
<div class="entry default {{if selected}}selected{{/if}}">
<div class="country flag-gb" title="{{*:i18n.country_name('gb')}}"></div>
<div class="country flag-gb" title="{{>fallback_country_name}}"></div>
<div class="name">{{tr "English (Default / Fallback)" /}}</div>
</div>
{{else}}
<div class="entry translation {{if selected}}selected{{/if}}" parent-repository="{{:id}}">
<div class="country flag-{{:country_code}}"
title="{{*:i18n.country_name(data.country_code || 'XX')}}"></div>
title="{{>country_name}}"></div>
<div class="name">{{> name}}</div>
<div class="button button-info">
<div class="icon client-about"></div>

View file

@ -1,8 +1,6 @@
interface Window {
BroadcastChannel: BroadcastChannel;
}
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
namespace bipc {
export interface BroadcastMessage {
timestamp: number;
receiver: string;
@ -724,4 +722,3 @@ namespace bipc {
/* ios does not support this */
return typeof(window.BroadcastChannel) !== "undefined";
}
}

View file

@ -1,14 +1,38 @@
/// <reference path="log.ts" />
/// <reference path="proto.ts" />
/// <reference path="ui/view.ts" />
/// <reference path="settings.ts" />
/// <reference path="FileManager.ts" />
/// <reference path="permission/PermissionManager.ts" />
/// <reference path="permission/GroupManager.ts" />
/// <reference path="ui/frames/ControlBar.ts" />
/// <reference path="connection/ConnectionBase.ts" />
import {ChannelTree} from "tc-shared/ui/view";
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
import {PermissionManager} from "tc-shared/permission/PermissionManager";
import {GroupManager} from "tc-shared/permission/GroupManager";
import {ServerSettings, Settings, StaticSettings} from "tc-shared/settings";
import {Sound, SoundManager} from "tc-shared/sound/Sounds";
import {LocalClientEntry} from "tc-shared/ui/client";
import {ServerLog} from "tc-shared/ui/frames/server_log";
import {ConnectionProfile, default_profile, find_profile} from "tc-shared/profiles/ConnectionProfile";
import {ServerAddress} from "tc-shared/ui/server";
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import * as server_log from "tc-shared/ui/frames/server_log";
import {createErrorModal, createInfoModal, createInputModal, Modal} from "tc-shared/ui/elements/Modal";
import {hashPassword} from "tc-shared/utils/helpers";
import {HandshakeHandler} from "tc-shared/connection/HandshakeHandler";
import * as htmltags from "./ui/htmltags";
import {ChannelEntry} from "tc-shared/ui/channel";
import {InputStartResult, InputState} from "tc-shared/voice/RecorderBase";
import {CommandResult, ErrorID} from "tc-shared/connection/ServerConnectionDeclaration";
import {guid} from "tc-shared/crypto/uid";
import * as bipc from "./BrowserIPC";
import {FileManager, spawn_upload_transfer, UploadKey} from "tc-shared/FileManager";
import {RecorderProfile} from "tc-shared/voice/RecorderProfile";
import {Frame} from "tc-shared/ui/frames/chat_frame";
import {Hostbanner} from "tc-shared/ui/frames/hostbanner";
import {server_connections} from "tc-shared/ui/frames/connection_handlers";
import {connection_log, Regex} from "tc-shared/ui/modal/ModalConnect";
import {control_bar} from "tc-shared/ui/frames/ControlBar";
import {formatMessage} from "tc-shared/ui/frames/chat";
import {spawnAvatarUpload} from "tc-shared/ui/modal/ModalAvatar";
import * as connection from "tc-backend/connection";
import * as dns from "tc-backend/dns";
enum DisconnectReason {
export enum DisconnectReason {
HANDLER_DESTROYED,
REQUESTED,
DNS_FAILED,
@ -28,7 +52,7 @@ enum DisconnectReason {
UNKNOWN
}
enum ConnectionState {
export enum ConnectionState {
UNCONNECTED,
CONNECTING,
INITIALISING,
@ -36,7 +60,7 @@ enum ConnectionState {
DISCONNECTING
}
enum ViewReasonId {
export enum ViewReasonId {
VREASON_USER_ACTION = 0,
VREASON_MOVED = 1,
VREASON_SYSTEM = 2,
@ -51,7 +75,7 @@ enum ViewReasonId {
VREASON_SERVER_SHUTDOWN = 11
}
interface VoiceStatus {
export interface VoiceStatus {
input_hardware: boolean;
input_muted: boolean;
output_muted: boolean;
@ -68,7 +92,7 @@ interface VoiceStatus {
queries_visible: boolean;
}
interface ConnectParameters {
export interface ConnectParameters {
nickname?: string;
channel?: {
target: string | number;
@ -79,20 +103,21 @@ interface ConnectParameters {
auto_reconnect_attempt?: boolean;
}
class ConnectionHandler {
declare const native_client;
export class ConnectionHandler {
channelTree: ChannelTree;
serverConnection: connection.AbstractServerConnection;
serverConnection: AbstractServerConnection;
fileManager: FileManager;
permissions: PermissionManager;
groups: GroupManager;
side_bar: chat.Frame;
side_bar: Frame;
settings: ServerSettings;
sound: sound.SoundManager;
sound: SoundManager;
hostbanner: Hostbanner;
@ -121,15 +146,15 @@ class ConnectionHandler {
};
invoke_resized_on_activate: boolean = false;
log: log.ServerLog;
log: ServerLog;
constructor() {
this.settings = new ServerSettings();
this.log = new log.ServerLog(this);
this.log = new ServerLog(this);
this.channelTree = new ChannelTree(this);
this.side_bar = new chat.Frame(this);
this.sound = new sound.SoundManager(this);
this.side_bar = new Frame(this);
this.sound = new SoundManager(this);
this.hostbanner = new Hostbanner(this);
this.serverConnection = connection.spawn_server_connection(this);
@ -169,7 +194,7 @@ class ConnectionHandler {
setup() { }
async startConnection(addr: string, profile: profiles.ConnectionProfile, user_action: boolean, parameters: ConnectParameters) {
async startConnection(addr: string, profile: ConnectionProfile, user_action: boolean, parameters: ConnectParameters) {
this.tab_set_name(tr("Connecting"));
this.cancel_reconnect(false);
this._reconnect_attempt = parameters.auto_reconnect_attempt || false;
@ -192,7 +217,7 @@ class ConnectionHandler {
}
}
log.info(LogCategory.CLIENT, tr("Start connection to %s:%d"), server_address.host, server_address.port);
this.log.log(log.server.Type.CONNECTION_BEGIN, {
this.log.log(server_log.Type.CONNECTION_BEGIN, {
address: {
server_hostname: server_address.host,
server_port: server_address.port
@ -203,7 +228,7 @@ class ConnectionHandler {
if(parameters.password && !parameters.password.hashed){
try {
const password = await helpers.hashPassword(parameters.password.password);
const password = await hashPassword(parameters.password.password);
parameters.password = {
hashed: true,
password: password
@ -221,9 +246,9 @@ class ConnectionHandler {
}
const original_address = {host: server_address.host, port: server_address.port};
if(dns.supported() && !server_address.host.match(Modals.Regex.IP_V4) && !server_address.host.match(Modals.Regex.IP_V6)) {
if(dns.supported() && !server_address.host.match(Regex.IP_V4) && !server_address.host.match(Regex.IP_V6)) {
const id = ++this._connect_initialize_id;
this.log.log(log.server.Type.CONNECTION_HOSTNAME_RESOLVE, {});
this.log.log(server_log.Type.CONNECTION_HOSTNAME_RESOLVE, {});
try {
const resolved = await dns.resolve_address(server_address, { timeout: 5000 }) || {} as any;
if(id != this._connect_initialize_id)
@ -231,7 +256,7 @@ class ConnectionHandler {
server_address.host = typeof(resolved.target_ip) === "string" ? resolved.target_ip : server_address.host;
server_address.port = typeof(resolved.target_port) === "number" ? resolved.target_port : server_address.port;
this.log.log(log.server.Type.CONNECTION_HOSTNAME_RESOLVED, {
this.log.log(server_log.Type.CONNECTION_HOSTNAME_RESOLVED, {
address: {
server_port: server_address.port,
server_hostname: server_address.host
@ -245,7 +270,7 @@ class ConnectionHandler {
}
}
await this.serverConnection.connect(server_address, new connection.HandshakeHandler(profile, parameters));
await this.serverConnection.connect(server_address, new HandshakeHandler(profile, parameters));
setTimeout(() => {
const connected = this.serverConnection.connected();
if(user_action && connected) {
@ -270,7 +295,7 @@ class ConnectionHandler {
return this._clientId;
}
getServerConnection() : connection.AbstractServerConnection { return this.serverConnection; }
getServerConnection() : AbstractServerConnection { return this.serverConnection; }
/**
@ -380,7 +405,7 @@ class ConnectionHandler {
popup.close(); /* no need, but nicer */
const profile = profiles.find_profile(properties.connect_profile) || profiles.default_profile();
const profile = find_profile(properties.connect_profile) || default_profile();
const cprops = this.reconnect_properties(profile);
this.startConnection(properties.connect_address, profile, true, cprops);
});
@ -418,12 +443,12 @@ class ConnectionHandler {
case DisconnectReason.HANDLER_DESTROYED:
if(data) {
this.sound.play(Sound.CONNECTION_DISCONNECTED);
this.log.log(log.server.Type.DISCONNECTED, {});
this.log.log(server_log.Type.DISCONNECTED, {});
}
break;
case DisconnectReason.DNS_FAILED:
log.error(LogCategory.CLIENT, tr("Failed to resolve hostname: %o"), data);
this.log.log(log.server.Type.CONNECTION_HOSTNAME_RESOLVE_ERROR, {
this.log.log(server_log.Type.CONNECTION_HOSTNAME_RESOLVE_ERROR, {
message: data as any
});
this.sound.play(Sound.CONNECTION_REFUSED);
@ -431,7 +456,7 @@ class ConnectionHandler {
case DisconnectReason.CONNECT_FAILURE:
if(this._reconnect_attempt) {
auto_reconnect = true;
this.log.log(log.server.Type.CONNECTION_FAILED, {});
this.log.log(server_log.Type.CONNECTION_FAILED, {});
break;
}
if(data)
@ -452,7 +477,7 @@ class ConnectionHandler {
this._certificate_modal = createErrorModal(
tr("Could not connect"),
MessageHelper.formatMessage(tr(error_message_format), this.generate_ssl_certificate_accept())
formatMessage(tr(error_message_format), this.generate_ssl_certificate_accept())
);
this._certificate_modal.close_listener.push(() => this._certificate_modal = undefined);
this._certificate_modal.open();
@ -470,7 +495,7 @@ class ConnectionHandler {
case DisconnectReason.HANDSHAKE_TEAMSPEAK_REQUIRED:
createErrorModal(
tr("Target server is a TeamSpeak server"),
MessageHelper.formatMessage(tr("The target server is a TeamSpeak 3 server!{:br:}Only TeamSpeak 3 based identities are able to connect.{:br:}Please select another profile or change the identify type."))
formatMessage(tr("The target server is a TeamSpeak 3 server!{:br:}Only TeamSpeak 3 based identities are able to connect.{:br:}Please select another profile or change the identify type."))
).open();
this.sound.play(Sound.CONNECTION_DISCONNECTED);
auto_reconnect = false;
@ -478,7 +503,7 @@ class ConnectionHandler {
case DisconnectReason.IDENTITY_TOO_LOW:
createErrorModal(
tr("Identity level is too low"),
MessageHelper.formatMessage(tr("You've been disconnected, because your Identity level is too low.{:br:}You need at least a level of {0}"), data["extra_message"])
formatMessage(tr("You've been disconnected, because your Identity level is too low.{:br:}You need at least a level of {0}"), data["extra_message"])
).open();
this.sound.play(Sound.CONNECTION_DISCONNECTED);
@ -506,7 +531,7 @@ class ConnectionHandler {
break;
case DisconnectReason.SERVER_CLOSED:
this.log.log(log.server.Type.SERVER_CLOSED, {message: data.reasonmsg});
this.log.log(server_log.Type.SERVER_CLOSED, {message: data.reasonmsg});
createErrorModal(
tr("Server closed"),
@ -518,7 +543,7 @@ class ConnectionHandler {
auto_reconnect = true;
break;
case DisconnectReason.SERVER_REQUIRES_PASSWORD:
this.log.log(log.server.Type.SERVER_REQUIRES_PASSWORD, {});
this.log.log(server_log.Type.SERVER_REQUIRES_PASSWORD, {});
createInputModal(tr("Server password"), tr("Enter server password:"), password => password.length != 0, password => {
if(!(typeof password === "string")) return;
@ -540,7 +565,7 @@ class ConnectionHandler {
const have_invoker = typeof(data["invokerid"]) !== "undefined" && parseInt(data["invokerid"]) !== 0;
const modal = createErrorModal(
tr("You've been kicked"),
MessageHelper.formatMessage(
formatMessage(
have_invoker ? tr("You've been kicked from the server by {0}:{:br:}{1}") : tr("You've been kicked from the server:{:br:}{1}"),
have_invoker ?
htmltags.generate_client_object({ client_id: parseInt(data["invokerid"]), client_unique_id: data["invokeruid"], client_name: data["invokername"]}) :
@ -559,7 +584,7 @@ class ConnectionHandler {
this.sound.play(Sound.CONNECTION_BANNED);
break;
case DisconnectReason.CLIENT_BANNED:
this.log.log(log.server.Type.SERVER_BANNED, {
this.log.log(server_log.Type.SERVER_BANNED, {
invoker: {
client_name: data["invokername"],
client_id: parseInt(data["invokerid"]),
@ -592,7 +617,7 @@ class ConnectionHandler {
log.info(LogCategory.NETWORKING, tr("Allowed to auto reconnect but cant reconnect because we dont have any information left..."));
return;
}
this.log.log(log.server.Type.RECONNECT_SCHEDULED, {timeout: 5000});
this.log.log(server_log.Type.RECONNECT_SCHEDULED, {timeout: 5000});
log.info(LogCategory.NETWORKING, tr("Allowed to auto reconnect. Reconnecting in 5000ms"));
const server_address = this.serverConnection.remote_address();
@ -600,7 +625,7 @@ class ConnectionHandler {
this._reconnect_timer = setTimeout(() => {
this._reconnect_timer = undefined;
this.log.log(log.server.Type.RECONNECT_EXECUTE, {});
this.log.log(server_log.Type.RECONNECT_EXECUTE, {});
log.info(LogCategory.NETWORKING, tr("Reconnecting..."));
this.startConnection(server_address.host + ":" + server_address.port, profile, false, Object.assign(this.reconnect_properties(profile), {auto_reconnect_attempt: true}));
@ -610,7 +635,7 @@ class ConnectionHandler {
cancel_reconnect(log_event: boolean) {
if(this._reconnect_timer) {
if(log_event) this.log.log(log.server.Type.RECONNECT_CANCELED, {});
if(log_event) this.log.log(server_log.Type.RECONNECT_CANCELED, {});
clearTimeout(this._reconnect_timer);
this._reconnect_timer = undefined;
}
@ -665,7 +690,7 @@ class ConnectionHandler {
if(Object.keys(property_update).length > 0) {
this.serverConnection.send_command("clientupdate", property_update).catch(error => {
log.warn(LogCategory.GENERAL, tr("Failed to update client audio hardware properties. Error: %o"), error);
this.log.log(log.server.Type.ERROR_CUSTOM, {message: tr("Failed to update audio hardware properties.")});
this.log.log(server_log.Type.ERROR_CUSTOM, {message: tr("Failed to update audio hardware properties.")});
/* Update these properties anyways (for case the server fails to handle the command) */
const updates = [];
@ -708,15 +733,15 @@ class ConnectionHandler {
const input = vconnection.voice_recorder().input;
if(input) {
if(active && this.serverConnection.connected()) {
if(input.current_state() === audio.recorder.InputState.PAUSED) {
if(input.current_state() === InputState.PAUSED) {
input.start().then(result => {
if(result != audio.recorder.InputStartResult.EOK)
if(result != InputStartResult.EOK)
throw result;
}).catch(error => {
log.warn(LogCategory.VOICE, tr("Failed to start microphone input (%s)."), error);
if(Date.now() - (this._last_record_error_popup || 0) > 10 * 1000) {
this._last_record_error_popup = Date.now();
createErrorModal(tr("Failed to start recording"), MessageHelper.formatMessage(tr("Microphone start failed.{:br:}Error: {}"), error)).open();
createErrorModal(tr("Failed to start recording"), formatMessage(tr("Microphone start failed.{:br:}Error: {}"), error)).open();
}
});
}
@ -741,7 +766,7 @@ class ConnectionHandler {
client_output_hardware: this.client_status.sound_playback_supported
}).catch(error => {
log.warn(LogCategory.GENERAL, tr("Failed to sync handler state with server. Error: %o"), error);
this.log.log(log.server.Type.ERROR_CUSTOM, {message: tr("Failed to sync handler state with server.")});
this.log.log(server_log.Type.ERROR_CUSTOM, {message: tr("Failed to sync handler state with server.")});
});
}
@ -761,7 +786,7 @@ class ConnectionHandler {
client_away_message: typeof(this.client_status.away) === "string" ? this.client_status.away : "",
}).catch(error => {
log.warn(LogCategory.GENERAL, tr("Failed to update away status. Error: %o"), error);
this.log.log(log.server.Type.ERROR_CUSTOM, {message: tr("Failed to update away status.")});
this.log.log(server_log.Type.ERROR_CUSTOM, {message: tr("Failed to update away status.")});
});
control_bar.update_button_away();
@ -781,10 +806,10 @@ class ConnectionHandler {
});
}
reconnect_properties(profile?: profiles.ConnectionProfile) : ConnectParameters {
reconnect_properties(profile?: ConnectionProfile) : ConnectParameters {
const name = (this.getClient() ? this.getClient().clientNickName() : "") ||
(this.serverConnection && this.serverConnection.handshake_handler() ? this.serverConnection.handshake_handler().parameters.nickname : "") ||
settings.static_global(Settings.KEY_CONNECT_USERNAME, profile ? profile.default_username : undefined) ||
StaticSettings.instance.static(Settings.KEY_CONNECT_USERNAME, profile ? profile.default_username : undefined) ||
"Another TeaSpeak user";
const channel = (this.getClient() && this.getClient().currentChannel() ? this.getClient().currentChannel().channelId : 0) ||
(this.serverConnection && this.serverConnection.handshake_handler() ? (this.serverConnection.handshake_handler().parameters.channel || {} as any).target : "");
@ -798,7 +823,7 @@ class ConnectionHandler {
}
update_avatar() {
Modals.spawnAvatarUpload(data => {
spawnAvatarUpload(data => {
if(typeof(data) === "undefined")
return;
if(data === null) {
@ -814,16 +839,16 @@ class ConnectionHandler {
let message;
if(error instanceof CommandResult)
message = MessageHelper.formatMessage(tr("Failed to delete avatar.{:br:}Error: {0}"), error.extra_message || error.message);
message = formatMessage(tr("Failed to delete avatar.{:br:}Error: {0}"), error.extra_message || error.message);
if(!message)
message = MessageHelper.formatMessage(tr("Failed to delete avatar.{:br:}Lookup the console for more details"));
message = formatMessage(tr("Failed to delete avatar.{:br:}Lookup the console for more details"));
createErrorModal(tr("Failed to delete avatar"), message).open();
return;
});
} else {
log.info(LogCategory.CLIENT, tr("Uploading new avatar"));
(async () => {
let key: transfer.UploadKey;
let key: UploadKey;
try {
key = await this.fileManager.upload_file({
size: data.byteLength,
@ -840,28 +865,28 @@ class ConnectionHandler {
//TODO: Resolve permission name
//i_client_max_avatar_filesize
if(error.id == ErrorID.PERMISSION_ERROR) {
message = MessageHelper.formatMessage(tr("Failed to initialize avatar upload.{:br:}Missing permission {0}"), error["failed_permid"]);
message = formatMessage(tr("Failed to initialize avatar upload.{:br:}Missing permission {0}"), error["failed_permid"]);
} else {
message = MessageHelper.formatMessage(tr("Failed to initialize avatar upload.{:br:}Error: {0}"), error.extra_message || error.message);
message = formatMessage(tr("Failed to initialize avatar upload.{:br:}Error: {0}"), error.extra_message || error.message);
}
}
if(!message)
message = MessageHelper.formatMessage(tr("Failed to initialize avatar upload.{:br:}Lookup the console for more details"));
message = formatMessage(tr("Failed to initialize avatar upload.{:br:}Lookup the console for more details"));
createErrorModal(tr("Failed to upload avatar"), message).open();
return;
}
try {
await transfer.spawn_upload_transfer(key).put_data(data);
await spawn_upload_transfer(key).put_data(data);
} catch(error) {
log.error(LogCategory.GENERAL, tr("Failed to upload avatar: %o"), error);
let message;
if(typeof(error) === "string")
message = MessageHelper.formatMessage(tr("Failed to upload avatar.{:br:}Error: {0}"), error);
message = formatMessage(tr("Failed to upload avatar.{:br:}Error: {0}"), error);
if(!message)
message = MessageHelper.formatMessage(tr("Failed to initialize avatar upload.{:br:}Lookup the console for more details"));
message = formatMessage(tr("Failed to initialize avatar upload.{:br:}Lookup the console for more details"));
createErrorModal(tr("Failed to upload avatar"), message).open();
return;
}
@ -874,9 +899,9 @@ class ConnectionHandler {
let message;
if(error instanceof CommandResult)
message = MessageHelper.formatMessage(tr("Failed to update avatar flag.{:br:}Error: {0}"), error.extra_message || error.message);
message = formatMessage(tr("Failed to update avatar flag.{:br:}Error: {0}"), error.extra_message || error.message);
if(!message)
message = MessageHelper.formatMessage(tr("Failed to update avatar flag.{:br:}Lookup the console for more details"));
message = formatMessage(tr("Failed to update avatar flag.{:br:}Lookup the console for more details"));
createErrorModal(tr("Failed to set avatar"), message).open();
return;
}

View file

@ -1,21 +1,26 @@
/// <reference path="connection/CommandHandler.ts" />
/// <reference path="connection/ConnectionBase.ts" />
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import {ChannelEntry} from "tc-shared/ui/channel";
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {ServerCommand} from "tc-shared/connection/ConnectionBase";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import {ClientEntry} from "tc-shared/ui/client";
import {AbstractCommandHandler} from "tc-shared/connection/AbstractCommandHandler";
class FileEntry {
export class FileEntry {
name: string;
datetime: number;
type: number;
size: number;
}
class FileListRequest {
export class FileListRequest {
path: string;
entries: FileEntry[];
callback: (entries: FileEntry[]) => void;
}
namespace transfer {
export interface TransferKey {
client_transfer_id: number;
server_transfer_id: number;
@ -65,12 +70,11 @@ namespace transfer {
export function spawn_upload_transfer(key: UploadKey) : UploadTransfer {
return new RequestFileUpload(key);
}
}
class RequestFileDownload implements transfer.DownloadTransfer {
readonly transfer_key: transfer.DownloadKey;
export class RequestFileDownload implements DownloadTransfer {
readonly transfer_key: DownloadKey;
constructor(key: transfer.DownloadKey) {
constructor(key: DownloadKey) {
this.transfer_key = key;
}
@ -97,18 +101,18 @@ class RequestFileDownload implements transfer.DownloadTransfer {
return response;
}
get_key(): transfer.DownloadKey {
get_key(): DownloadKey {
return this.transfer_key;
}
}
class RequestFileUpload implements transfer.UploadTransfer {
readonly transfer_key: transfer.UploadKey;
constructor(key: transfer.DownloadKey) {
export class RequestFileUpload implements UploadTransfer {
readonly transfer_key: UploadKey;
constructor(key: DownloadKey) {
this.transfer_key = key;
}
get_key(): transfer.UploadKey {
get_key(): UploadKey {
return this.transfer_key;
}
@ -152,14 +156,14 @@ class RequestFileUpload implements transfer.UploadTransfer {
}
}
class FileManager extends connection.AbstractCommandHandler {
export class FileManager extends AbstractCommandHandler {
handle: ConnectionHandler;
icons: IconManager;
avatars: AvatarManager;
private listRequests: FileListRequest[] = [];
private pending_download_requests: transfer.DownloadKey[] = [];
private pending_upload_requests: transfer.UploadKey[] = [];
private pending_download_requests: DownloadKey[] = [];
private pending_upload_requests: UploadKey[] = [];
private transfer_counter : number = 1;
@ -191,7 +195,7 @@ class FileManager extends connection.AbstractCommandHandler {
this.avatars = undefined;
}
handle_command(command: connection.ServerCommand): boolean {
handle_command(command: ServerCommand): boolean {
switch (command.command) {
case "notifyfilelist":
this.notifyFileList(command.arguments);
@ -276,15 +280,15 @@ class FileManager extends connection.AbstractCommandHandler {
/******************************** File download/upload ********************************/
download_file(path: string, file: string, channel?: ChannelEntry, password?: string) : Promise<transfer.DownloadKey> {
const transfer_data: transfer.DownloadKey = {
download_file(path: string, file: string, channel?: ChannelEntry, password?: string) : Promise<DownloadKey> {
const transfer_data: DownloadKey = {
file_name: file,
file_path: path,
client_transfer_id: this.transfer_counter++
} as any;
this.pending_download_requests.push(transfer_data);
return new Promise<transfer.DownloadKey>((resolve, reject) => {
return new Promise<DownloadKey>((resolve, reject) => {
transfer_data["_callback"] = resolve;
this.handle.serverConnection.send_command("ftinitdownload", {
"path": path,
@ -301,8 +305,8 @@ class FileManager extends connection.AbstractCommandHandler {
});
}
upload_file(options: transfer.UploadOptions) : Promise<transfer.UploadKey> {
const transfer_data: transfer.UploadKey = {
upload_file(options: UploadOptions) : Promise<UploadKey> {
const transfer_data: UploadKey = {
file_path: options.path,
file_name: options.name,
client_transfer_id: this.transfer_counter++,
@ -310,7 +314,7 @@ class FileManager extends connection.AbstractCommandHandler {
} as any;
this.pending_upload_requests.push(transfer_data);
return new Promise<transfer.UploadKey>((resolve, reject) => {
return new Promise<UploadKey>((resolve, reject) => {
transfer_data["_callback"] = resolve;
this.handle.serverConnection.send_command("ftinitupload", {
"path": options.path,
@ -333,7 +337,7 @@ class FileManager extends connection.AbstractCommandHandler {
json = json[0];
let clientftfid = parseInt(json["clientftfid"]);
let transfer: transfer.DownloadKey;
let transfer: DownloadKey;
for(let e of this.pending_download_requests)
if(e.client_transfer_id == clientftfid) {
transfer = e;
@ -355,14 +359,14 @@ class FileManager extends connection.AbstractCommandHandler {
if(transfer.peer.hosts[0].length == 0 || transfer.peer.hosts[0] == '0.0.0.0')
transfer.peer.hosts[0] = this.handle.serverConnection.remote_address().host;
(transfer["_callback"] as (val: transfer.DownloadKey) => void)(transfer);
(transfer["_callback"] as (val: DownloadKey) => void)(transfer);
this.pending_download_requests.remove(transfer);
}
private notifyStartUpload(json) {
json = json[0];
let transfer: transfer.UploadKey;
let transfer: UploadKey;
let clientftfid = parseInt(json["clientftfid"]);
for(let e of this.pending_upload_requests)
if(e.client_transfer_id == clientftfid) {
@ -384,7 +388,7 @@ class FileManager extends connection.AbstractCommandHandler {
if(transfer.peer.hosts[0].length == 0 || transfer.peer.hosts[0] == '0.0.0.0')
transfer.peer.hosts[0] = this.handle.serverConnection.remote_address().host;
(transfer["_callback"] as (val: transfer.UploadKey) => void)(transfer);
(transfer["_callback"] as (val: UploadKey) => void)(transfer);
this.pending_upload_requests.remove(transfer);
}
@ -411,12 +415,12 @@ class FileManager extends connection.AbstractCommandHandler {
}
}
class Icon {
export class Icon {
id: number;
url: string;
}
enum ImageType {
export enum ImageType {
UNKNOWN,
BITMAP,
PNG,
@ -425,7 +429,7 @@ enum ImageType {
JPEG
}
function media_image_type(type: ImageType, file?: boolean) {
export function media_image_type(type: ImageType, file?: boolean) {
switch (type) {
case ImageType.BITMAP:
return "bmp";
@ -442,7 +446,7 @@ function media_image_type(type: ImageType, file?: boolean) {
}
}
function image_type(encoded_data: string | ArrayBuffer, base64_encoded?: boolean) {
export function image_type(encoded_data: string | ArrayBuffer, base64_encoded?: boolean) {
const ab2str10 = () => {
const buf = new Uint8Array(encoded_data as ArrayBuffer);
if(buf.byteLength < 10)
@ -472,7 +476,7 @@ function image_type(encoded_data: string | ArrayBuffer, base64_encoded?: boolean
return ImageType.UNKNOWN;
}
class CacheManager {
export class CacheManager {
readonly cache_name: string;
private _cache_category: Cache;
@ -547,7 +551,7 @@ class CacheManager {
}
}
class IconManager {
export class IconManager {
private static cache: CacheManager = new CacheManager("icons");
handle: FileManager;
@ -590,7 +594,7 @@ class IconManager {
return this.handle.requestFileList("/icons");
}
create_icon_download(id: number) : Promise<transfer.DownloadKey> {
create_icon_download(id: number) : Promise<DownloadKey> {
return this.handle.download_file("", "/icon_" + id);
}
@ -665,7 +669,7 @@ class IconManager {
private async _load_icon(id: number) : Promise<Icon> {
try {
let download_key: transfer.DownloadKey;
let download_key: DownloadKey;
try {
download_key = await this.create_icon_download(id);
} catch(error) {
@ -673,7 +677,7 @@ class IconManager {
throw "Failed to request icon";
}
const downloader = transfer.spawn_download_transfer(download_key);
const downloader = spawn_download_transfer(download_key);
let response: Response;
try {
response = await downloader.request_file();
@ -801,14 +805,14 @@ class IconManager {
}
}
class Avatar {
export class Avatar {
client_avatar_id: string; /* the base64 uid thing from a-m */
avatar_id: string; /* client_flag_avatar */
url: string;
type: ImageType;
}
class AvatarManager {
export class AvatarManager {
handle: FileManager;
private static cache: CacheManager;
@ -867,14 +871,14 @@ class AvatarManager {
};
}
create_avatar_download(client_avatar_id: string) : Promise<transfer.DownloadKey> {
create_avatar_download(client_avatar_id: string) : Promise<DownloadKey> {
log.debug(LogCategory.GENERAL, "Requesting download for avatar %s", client_avatar_id);
return this.handle.download_file("", "/avatar_" + client_avatar_id);
}
private async _load_avatar(client_avatar_id: string, avatar_version: string) {
try {
let download_key: transfer.DownloadKey;
let download_key: DownloadKey;
try {
download_key = await this.create_avatar_download(client_avatar_id);
} catch(error) {
@ -882,7 +886,7 @@ class AvatarManager {
throw "failed to request avatar download";
}
const downloader = transfer.spawn_download_transfer(download_key);
const downloader = spawn_download_transfer(download_key);
let response: Response;
try {
response = await downloader.request_file();

View file

@ -1,4 +1,11 @@
namespace messages.formatter {
import {Settings, settings} from "tc-shared/settings";
import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
import {copy_to_clipboard} from "tc-shared/utils/helpers";
import {guid} from "tc-shared/crypto/uid";
import * as loader from "tc-loader";
import * as image_preview from "./ui/frames/image_preview"
declare const xbbcode;
export namespace bbcode {
const sanitizer_escaped = (key: string) => "[-- sescaped: " + key + " --]";
const sanitizer_escaped_regex = /\[-- sescaped: ([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}) --]/;
@ -118,7 +125,7 @@ namespace messages.formatter {
},
name: tr("Open URL in Browser"),
type: contextmenu.MenuEntryType.ENTRY,
visible: !app.is_web() && false // Currently not possible
visible: loader.version().type === "native" && false // Currently not possible
}, contextmenu.Entry.HR(), {
callback: () => copy_to_clipboard(url),
name: tr("Copy URL to clipboard"),
@ -247,4 +254,27 @@ namespace messages.formatter {
]
})).text();
}
export function formatDate(secs: number) : string {
let years = Math.floor(secs / (60 * 60 * 24 * 365));
let days = Math.floor(secs / (60 * 60 * 24)) % 365;
let hours = Math.floor(secs / (60 * 60)) % 24;
let minutes = Math.floor(secs / 60) % 60;
let seconds = Math.floor(secs % 60);
let result = "";
if(years > 0)
result += years + " " + tr("years") + " ";
if(years > 0 || days > 0)
result += days + " " + tr("days") + " ";
if(years > 0 || days > 0 || hours > 0)
result += hours + " " + tr("hours") + " ";
if(years > 0 || days > 0 || hours > 0 || minutes > 0)
result += minutes + " " + tr("minutes") + " ";
if(years > 0 || days > 0 || hours > 0 || minutes > 0 || seconds > 0)
result += seconds + " " + tr("seconds") + " ";
else
result = tr("now") + " ";
return result.substr(0, result.length - 1);
}

View file

@ -1,4 +1,4 @@
enum KeyCode {
export enum KeyCode {
KEY_CANCEL = 3,
KEY_HELP = 6,
KEY_BACK_SPACE = 8,
@ -118,7 +118,6 @@ enum KeyCode {
KEY_META = 224
}
namespace ppt {
export enum EventType {
KEY_PRESS,
KEY_RELEASE,
@ -173,4 +172,3 @@ namespace ppt {
result += " + " + key.key_code;
return result.substr(3);
}
}

View file

@ -1,10 +0,0 @@
namespace audio {
export namespace player {
export interface Device {
device_id: string;
driver: string;
name: string;
}
}
}

View file

@ -0,0 +1,6 @@
export interface Device {
device_id: string;
driver: string;
name: string;
}

View file

@ -1,16 +1,15 @@
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();
}
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import {guid} from "tc-shared/crypto/uid";
import {createErrorModal, createInfoModal, createInputModal} from "tc-shared/ui/elements/Modal";
import {default_profile, find_profile} from "tc-shared/profiles/ConnectionProfile";
import {server_connections} from "tc-shared/ui/frames/connection_handlers";
import {spawnConnectModal} from "tc-shared/ui/modal/ModalConnect";
import {control_bar} from "tc-shared/ui/frames/ControlBar";
import * as top_menu from "./ui/frames/MenuBar";
export const boorkmak_connect = (mark: Bookmark, new_tab?: boolean) => {
const profile = profiles.find_profile(mark.connect_profile) || profiles.default_profile();
const profile = find_profile(mark.connect_profile) || default_profile();
if(profile.valid()) {
const connection = (typeof(new_tab) !== "boolean" || !new_tab) ? server_connections.active_connection_handler() : server_connections.spawn_server_connection_handler();
server_connections.set_active_connection_handler(connection);
@ -30,7 +29,7 @@ namespace bookmarks {
}
);
} else {
Modals.spawnConnectModal({}, {
spawnConnectModal({}, {
url: mark.server_properties.server_address + ":" + mark.server_properties.server_port,
enforce: true
}, {
@ -259,4 +258,3 @@ namespace bookmarks {
createErrorModal(tr("You have to be connected"), tr("You have to be connected!")).open();
}
}
}

View file

@ -0,0 +1,98 @@
import {
AbstractServerConnection,
ServerCommand,
SingleCommandHandler
} from "tc-shared/connection/ConnectionBase";
export abstract class AbstractCommandHandler {
readonly connection: AbstractServerConnection;
handler_boss: AbstractCommandHandlerBoss | undefined;
volatile_handler_boss: boolean = false; /* if true than the command handler could be registered twice to two or more handlers */
ignore_consumed: boolean = false;
protected constructor(connection: AbstractServerConnection) {
this.connection = connection;
}
/**
* @return If the command should be consumed
*/
abstract handle_command(command: ServerCommand) : boolean;
}
export abstract class AbstractCommandHandlerBoss {
readonly connection: AbstractServerConnection;
protected command_handlers: AbstractCommandHandler[] = [];
/* TODO: Timeout */
protected single_command_handler: SingleCommandHandler[] = [];
protected constructor(connection: AbstractServerConnection) {
this.connection = connection;
}
destroy() {
this.command_handlers = undefined;
this.single_command_handler = undefined;
}
register_handler(handler: AbstractCommandHandler) {
if(!handler.volatile_handler_boss && handler.handler_boss)
throw "handler already registered";
this.command_handlers.remove(handler); /* just to be sure */
this.command_handlers.push(handler);
handler.handler_boss = this;
}
unregister_handler(handler: AbstractCommandHandler) {
if(!handler.volatile_handler_boss && handler.handler_boss !== this) {
console.warn(tr("Tried to unregister command handler which does not belong to the handler boss"));
return;
}
this.command_handlers.remove(handler);
handler.handler_boss = undefined;
}
register_single_handler(handler: SingleCommandHandler) {
this.single_command_handler.push(handler);
}
remove_single_handler(handler: SingleCommandHandler) {
this.single_command_handler.remove(handler);
}
handlers() : AbstractCommandHandler[] {
return this.command_handlers;
}
invoke_handle(command: ServerCommand) : boolean {
let flag_consumed = false;
for(const handler of this.command_handlers) {
try {
if(!flag_consumed || handler.ignore_consumed)
flag_consumed = flag_consumed || handler.handle_command(command);
} catch(error) {
console.error(tr("Failed to invoke command handler. Invocation results in an exception: %o"), error);
}
}
for(const handler of [...this.single_command_handler]) {
if(handler.command && handler.command != command.command)
continue;
try {
if(handler.function(command))
this.single_command_handler.remove(handler);
} catch(error) {
console.error(tr("Failed to invoke single command handler. Invocation results in an exception: %o"), error);
}
}
return flag_consumed;
}
}

View file

@ -1,8 +1,28 @@
/// <reference path="ConnectionBase.ts" />
namespace connection {
import Conversation = chat.channel.Conversation;
import MusicInfo = chat.MusicInfo;
import * as log from "tc-shared/log";
import * as server_log from "tc-shared/ui/frames/server_log";
import {
AbstractServerConnection, CommandOptions, ServerCommand
} from "tc-shared/connection/ConnectionBase";
import {Sound} from "tc-shared/sound/Sounds";
import {CommandResult, ErrorID} from "tc-shared/connection/ServerConnectionDeclaration";
import {LogCategory} from "tc-shared/log";
import {createErrorModal, createInfoModal, createInputModal, createModal} from "tc-shared/ui/elements/Modal";
import {
ClientConnectionInfo,
ClientEntry,
ClientType,
LocalClientEntry,
MusicClientEntry,
SongInfo
} from "tc-shared/ui/client";
import {ChannelEntry} from "tc-shared/ui/channel";
import {ConnectionHandler, DisconnectReason, ViewReasonId} from "tc-shared/ConnectionHandler";
import {bbcode_chat, formatMessage} from "tc-shared/ui/frames/chat";
import {server_connections} from "tc-shared/ui/frames/connection_handlers";
import {spawnPoke} from "tc-shared/ui/modal/ModalPoke";
import {PrivateConversationState} from "tc-shared/ui/frames/side/private_conversations";
import {Conversation} from "tc-shared/ui/frames/side/conversations";
import {AbstractCommandHandler, AbstractCommandHandlerBoss} from "tc-shared/connection/AbstractCommandHandler";
export class ServerConnectionCommandBoss extends AbstractCommandHandlerBoss {
constructor(connection: AbstractServerConnection) {
@ -65,7 +85,7 @@ namespace connection {
this["notifyplaylistsongloaded"] = this.handleNotifyPlaylistSongLoaded;
}
private loggable_invoker(unique_id, client_id, name) : log.server.base.Client | undefined {
private loggable_invoker(unique_id, client_id, name) : server_log.base.Client | undefined {
const id = parseInt(client_id);
if(typeof(client_id) === "undefined" || Number.isNaN(id))
return undefined;
@ -84,7 +104,7 @@ namespace connection {
};
}
proxy_command_promise(promise: Promise<CommandResult>, options: connection.CommandOptions) {
proxy_command_promise(promise: Promise<CommandResult>, options: CommandOptions) {
if(!options.process_result)
return promise;
@ -96,18 +116,18 @@ namespace connection {
if(res.id == ErrorID.PERMISSION_ERROR) { //Permission error
const permission = this.connection_handler.permissions.resolveInfo(res.json["failed_permid"] as number);
res.message = tr("Insufficient client permissions. Failed on permission ") + (permission ? permission.name : "unknown");
this.connection_handler.log.log(log.server.Type.ERROR_PERMISSION, {
this.connection_handler.log.log(server_log.Type.ERROR_PERMISSION, {
permission: this.connection_handler.permissions.resolveInfo(res.json["failed_permid"] as number)
});
this.connection_handler.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
} else if(res.id != ErrorID.EMPTY_RESULT) {
this.connection_handler.log.log(log.server.Type.ERROR_CUSTOM, {
this.connection_handler.log.log(server_log.Type.ERROR_CUSTOM, {
message: res.extra_message.length == 0 ? res.message : res.extra_message
});
}
}
} else if(typeof(ex) === "string") {
this.connection_handler.log.log(log.server.Type.CONNECTION_COMMAND_ERROR, {error: ex});
this.connection_handler.log.log(server_log.Type.CONNECTION_COMMAND_ERROR, {error: ex});
} else {
log.error(LogCategory.NETWORKING, tr("Invalid promise result type: %s. Result: %o"), typeof (ex), ex);
}
@ -190,21 +210,21 @@ namespace connection {
if(properties.virtualserver_hostmessage_mode > 0) {
if(properties.virtualserver_hostmessage_mode == 1) {
/* show in log */
this.connection_handler.log.log(log.server.Type.SERVER_HOST_MESSAGE, {
this.connection_handler.log.log(server_log.Type.SERVER_HOST_MESSAGE, {
message: properties.virtualserver_hostmessage
});
} else {
/* create modal/create modal and quit */
createModal({
header: tr("Host message"),
body: MessageHelper.bbcode_chat(properties.virtualserver_hostmessage),
body: bbcode_chat(properties.virtualserver_hostmessage),
footer: undefined
}).open();
if(properties.virtualserver_hostmessage_mode == 3) {
/* first let the client initialize his stuff */
setTimeout(() => {
this.connection_handler.log.log(log.server.Type.SERVER_HOST_MESSAGE_DISCONNECT, {
this.connection_handler.log.log(server_log.Type.SERVER_HOST_MESSAGE_DISCONNECT, {
message: properties.virtualserver_welcomemessage
});
@ -218,7 +238,7 @@ namespace connection {
/* welcome message */
if(properties.virtualserver_welcomemessage) {
this.connection_handler.log.log(log.server.Type.SERVER_WELCOME_MESSAGE, {
this.connection_handler.log.log(server_log.Type.SERVER_WELCOME_MESSAGE, {
message: properties.virtualserver_welcomemessage
});
}
@ -235,12 +255,12 @@ namespace connection {
}).then(() => {
createInfoModal(tr("Use privilege key"), tr("Privilege key successfully used!")).open();
}).catch(error => {
createErrorModal(tr("Use privilege key"), MessageHelper.formatMessage(tr("Failed to use privilege key: {}"), error instanceof CommandResult ? error.message : error)).open();
createErrorModal(tr("Use privilege key"), formatMessage(tr("Failed to use privilege key: {}"), error instanceof CommandResult ? error.message : error)).open();
});
}, { field_placeholder: 'Enter Privilege Key' }).open();
}
this.connection_handler.log.log(log.server.Type.CONNECTION_CONNECTED, {
this.connection_handler.log.log(server_log.Type.CONNECTION_CONNECTED, {
own_client: this.connection_handler.getClient().log_data()
});
this.connection_handler.sound.play(Sound.CONNECTION_CONNECTED);
@ -414,7 +434,7 @@ namespace connection {
if(this.connection_handler.client_status.queries_visible || client.properties.client_type != ClientType.CLIENT_QUERY) {
const own_channel = this.connection.client.getClient().currentChannel();
this.connection_handler.log.log(log.server.Type.CLIENT_VIEW_ENTER, {
this.connection_handler.log.log(server_log.Type.CLIENT_VIEW_ENTER, {
channel_from: old_channel ? old_channel.log_data() : undefined,
channel_to: channel ? channel.log_data() : undefined,
client: client.log_data(),
@ -521,7 +541,7 @@ namespace connection {
let channel_to = tree.findChannel(entry["ctid"]);
const is_own_channel = channel_from == own_channel;
this.connection_handler.log.log(log.server.Type.CLIENT_VIEW_LEAVE, {
this.connection_handler.log.log(server_log.Type.CLIENT_VIEW_LEAVE, {
channel_from: channel_from ? channel_from.log_data() : undefined,
channel_to: channel_to ? channel_to.log_data() : undefined,
client: client.log_data(),
@ -564,7 +584,7 @@ namespace connection {
attach: false
});
if(conversation) {
conversation.set_state(chat.PrivateConversationState.DISCONNECTED);
conversation.set_state(PrivateConversationState.DISCONNECTED);
}
}
}
@ -635,7 +655,7 @@ namespace connection {
}
const own_channel = this.connection.client.getClient().currentChannel();
this.connection_handler.log.log(log.server.Type.CLIENT_VIEW_MOVE, {
this.connection_handler.log.log(server_log.Type.CLIENT_VIEW_MOVE, {
channel_from: channel_from ? {
channel_id: channel_from.channelId,
channel_name: channel_from.channelName()
@ -750,7 +770,7 @@ namespace connection {
const target_own = target_client_id === this.connection.client.getClientId();
if(target_own && target_client_id === json["invokerid"]) {
log.error(LogCategory.NETWORKING, tr("Received conversation message from invalid client id. Data: %o", json));
log.error(LogCategory.NETWORKING, tr("Received conversation message from invalid client id. Data: %o"), json);
return;
}
@ -808,7 +828,7 @@ namespace connection {
if(conversation.is_unread() && channel)
channel.flag_text_unread = true;
} else if(mode == 3) {
this.connection_handler.log.log(log.server.Type.GLOBAL_MESSAGE, {
this.connection_handler.log.log(server_log.Type.GLOBAL_MESSAGE, {
message: json["msg"],
sender: {
client_unique_id: json["invokeruid"],
@ -871,7 +891,7 @@ namespace connection {
log.warn(LogCategory.GENERAL, tr("Received chat close for client, but we haven't a chat open."));
return;
}
conversation.set_state(chat.PrivateConversationState.CLOSED);
conversation.set_state(PrivateConversationState.CLOSED);
}
handleNotifyClientUpdated(json) {
@ -944,7 +964,7 @@ namespace connection {
handleNotifyClientPoke(json) {
json = json[0];
Modals.spawnPoke(this.connection_handler, {
spawnPoke(this.connection_handler, {
id: parseInt(json["invokerid"]),
name: json["invokername"],
unique_id: json["invokeruid"]
@ -1157,4 +1177,3 @@ namespace connection {
});
}
}
}

View file

@ -1,4 +1,18 @@
namespace connection {
import {ServerCommand, SingleCommandHandler} from "tc-shared/connection/ConnectionBase";
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import {
ClientNameInfo,
CommandResult,
ErrorID, Playlist, PlaylistInfo, PlaylistSong,
QueryList,
QueryListEntry, ServerGroupClient
} from "tc-shared/connection/ServerConnectionDeclaration";
import {ChannelEntry} from "tc-shared/ui/channel";
import {ClientEntry} from "tc-shared/ui/client";
import {ChatType} from "tc-shared/ui/frames/chat";
import {AbstractCommandHandler} from "tc-shared/connection/AbstractCommandHandler";
export class CommandHelper extends AbstractCommandHandler {
private _who_am_i: any;
private _awaiters_unique_ids: {[unique_id: string]:((resolved: ClientNameInfo) => any)[]} = {};
@ -23,7 +37,7 @@ namespace connection {
this._awaiters_unique_ids = undefined;
}
handle_command(command: connection.ServerCommand): boolean {
handle_command(command: ServerCommand): boolean {
if(command.command == "notifyclientnamefromuid")
this.handle_notifyclientnamefromuid(command.arguments);
if(command.command == "notifyclientgetnamefromdbid")
@ -445,4 +459,3 @@ namespace connection {
});
}
}
}

View file

@ -1,4 +1,11 @@
namespace connection {
import {CommandHelper} from "tc-shared/connection/CommandHelper";
import {HandshakeHandler} from "tc-shared/connection/HandshakeHandler";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import {ServerAddress} from "tc-shared/ui/server";
import {RecorderProfile} from "tc-shared/voice/RecorderProfile";
import {ConnectionHandler, ConnectionState} from "tc-shared/ConnectionHandler";
import {AbstractCommandHandlerBoss} from "tc-shared/connection/AbstractCommandHandler";
export interface CommandOptions {
flagset?: string[]; /* default: [] */
process_result?: boolean; /* default: true */
@ -112,24 +119,6 @@ namespace connection {
arguments: any[];
}
export abstract class AbstractCommandHandler {
readonly connection: AbstractServerConnection;
handler_boss: AbstractCommandHandlerBoss | undefined;
volatile_handler_boss: boolean = false; /* if true than the command handler could be registered twice to two or more handlers */
ignore_consumed: boolean = false;
protected constructor(connection: AbstractServerConnection) {
this.connection = connection;
}
/**
* @return If the command should be consumed
*/
abstract handle_command(command: ServerCommand) : boolean;
}
export interface SingleCommandHandler {
name?: string;
command?: string;
@ -138,79 +127,3 @@ namespace connection {
/* if the return is true then the command handler will be removed */
function: (command: ServerCommand) => boolean;
}
export abstract class AbstractCommandHandlerBoss {
readonly connection: AbstractServerConnection;
protected command_handlers: AbstractCommandHandler[] = [];
/* TODO: Timeout */
protected single_command_handler: SingleCommandHandler[] = [];
protected constructor(connection: AbstractServerConnection) {
this.connection = connection;
}
destroy() {
this.command_handlers = undefined;
this.single_command_handler = undefined;
}
register_handler(handler: AbstractCommandHandler) {
if(!handler.volatile_handler_boss && handler.handler_boss)
throw "handler already registered";
this.command_handlers.remove(handler); /* just to be sure */
this.command_handlers.push(handler);
handler.handler_boss = this;
}
unregister_handler(handler: AbstractCommandHandler) {
if(!handler.volatile_handler_boss && handler.handler_boss !== this) {
console.warn(tr("Tried to unregister command handler which does not belong to the handler boss"));
return;
}
this.command_handlers.remove(handler);
handler.handler_boss = undefined;
}
register_single_handler(handler: SingleCommandHandler) {
this.single_command_handler.push(handler);
}
remove_single_handler(handler: SingleCommandHandler) {
this.single_command_handler.remove(handler);
}
handlers() : AbstractCommandHandler[] {
return this.command_handlers;
}
invoke_handle(command: ServerCommand) : boolean {
let flag_consumed = false;
for(const handler of this.command_handlers) {
try {
if(!flag_consumed || handler.ignore_consumed)
flag_consumed = flag_consumed || handler.handle_command(command);
} catch(error) {
console.error(tr("Failed to invoke command handler. Invocation results in an exception: %o"), error);
}
}
for(const handler of [...this.single_command_handler]) {
if(handler.command && handler.command != command.command)
continue;
try {
if(handler.function(command))
this.single_command_handler.remove(handler);
} catch(error) {
console.error(tr("Failed to invoke single command handler. Invocation results in an exception: %o"), error);
}
}
return flag_consumed;
}
}
}

View file

@ -1,4 +1,11 @@
namespace connection {
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import {IdentitifyType} from "tc-shared/profiles/Identity";
import {TeaSpeakIdentity} from "tc-shared/profiles/identities/TeamSpeakIdentity";
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
import {ConnectionProfile} from "tc-shared/profiles/ConnectionProfile";
import {settings} from "tc-shared/settings";
import {ConnectParameters, DisconnectReason} from "tc-shared/ConnectionHandler";
export interface HandshakeIdentityHandler {
connection: AbstractServerConnection;
@ -6,15 +13,16 @@ namespace connection {
register_callback(callback: (success: boolean, message?: string) => any);
}
declare const native_client;
export class HandshakeHandler {
private connection: AbstractServerConnection;
private handshake_handler: HandshakeIdentityHandler;
private failed = false;
readonly profile: profiles.ConnectionProfile;
readonly profile: ConnectionProfile;
readonly parameters: ConnectParameters;
constructor(profile: profiles.ConnectionProfile, parameters: ConnectParameters) {
constructor(profile: ConnectionProfile, parameters: ConnectParameters) {
this.profile = profile;
this.parameters = parameters;
}
@ -48,7 +56,7 @@ namespace connection {
on_teamspeak() {
const type = this.profile.selected_type();
if(type == profiles.identities.IdentitifyType.TEAMSPEAK)
if(type == IdentitifyType.TEAMSPEAK)
this.handshake_finished();
else {
@ -122,8 +130,8 @@ namespace connection {
}
/* required to keep compatibility */
if(this.profile.selected_type() === profiles.identities.IdentitifyType.TEAMSPEAK) {
data["client_key_offset"] = (this.profile.selected_identity() as profiles.identities.TeaSpeakIdentity).hash_number;
if(this.profile.selected_type() === IdentitifyType.TEAMSPEAK) {
data["client_key_offset"] = (this.profile.selected_identity() as TeaSpeakIdentity).hash_number;
}
this.connection.send_command("clientinit", data).catch(error => {
@ -143,4 +151,3 @@ namespace connection {
});
}
}
}

View file

@ -1,4 +1,6 @@
enum ErrorID {
import {LaterPromise} from "tc-shared/utils/LaterPromise";
export enum ErrorID {
NOT_IMPLEMENTED = 0x2,
COMMAND_NOT_FOUND = 0x100,
@ -15,7 +17,7 @@ enum ErrorID {
CONVERSATION_IS_PRIVATE = 0x2202
}
class CommandResult {
export class CommandResult {
success: boolean;
id: number;
message: string;
@ -35,39 +37,39 @@ class CommandResult {
}
}
interface ClientNameInfo {
export interface ClientNameInfo {
//cluid=tYzKUryn\/\/Y8VBMf8PHUT6B1eiE= name=Exp clname=Exp cldbid=9
client_unique_id: string;
client_nickname: string;
client_database_id: number;
}
interface ClientNameFromUid {
export interface ClientNameFromUid {
promise: LaterPromise<ClientNameInfo[]>,
keys: string[],
response: ClientNameInfo[]
}
interface ServerGroupClient {
export interface ServerGroupClient {
client_nickname: string;
client_unique_identifier: string;
client_database_id: number;
}
interface QueryListEntry {
export interface QueryListEntry {
username: string;
unique_id: string;
bounded_server: number;
}
interface QueryList {
export interface QueryList {
flag_own: boolean;
flag_all: boolean;
queries: QueryListEntry[];
}
interface Playlist {
export interface Playlist {
playlist_id: number;
playlist_bot_id: number;
playlist_title: string;
@ -83,7 +85,7 @@ interface Playlist {
needed_power_song_remove: number;
}
interface PlaylistInfo {
export interface PlaylistInfo {
playlist_id: number,
playlist_title: string,
playlist_description: string,
@ -100,7 +102,7 @@ interface PlaylistInfo {
playlist_max_songs: number
}
interface PlaylistSong {
export interface PlaylistSong {
song_id: number;
song_previous_song_id: number;
song_invoker: string;

View file

@ -14,7 +14,6 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
namespace asn1 {
declare class Int10 {
constructor(value?: any);
@ -544,4 +543,3 @@ namespace asn1 {
export function decode(stream: string | ArrayBuffer) {
return decode0(new Stream(stream, 0));
}
}

View file

@ -1,4 +1,4 @@
class Crc32 {
export class Crc32 {
private static readonly lookup = [
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,

View file

@ -1,4 +1,3 @@
namespace hex {
export function encode(buffer) {
let hexCodes = [];
let view = new DataView(buffer);
@ -17,4 +16,3 @@ namespace hex {
return hexCodes.join("");
}
}

View file

@ -10,7 +10,6 @@ interface Window {
}
*/
namespace sha {
/*
* [js-sha1]{@link https://github.com/emn178/js-sha1}
*
@ -24,12 +23,6 @@ namespace sha {
'use strict';
let root: any = typeof window === 'object' ? window : {};
let NODE_JS = !root.JS_SHA1_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node;
if (NODE_JS) {
root = global;
}
let COMMON_JS = !root.JS_SHA1_NO_COMMON_JS && typeof module === 'object' && module.exports;
let AMD = typeof define === 'function' && (define as any).amd;
let HEX_CHARS = '0123456789abcdef'.split('');
let EXTRA = [-2147483648, 8388608, 32768, 128];
let SHIFT = [24, 16, 8, 0];
@ -45,9 +38,6 @@ namespace sha {
let createMethod = function () {
let method: any = createOutputMethod('hex');
if (NODE_JS) {
method = nodeWrap(method);
}
method.create = function () {
return new (Sha1 as any)();
};
@ -61,22 +51,6 @@ namespace sha {
return method;
};
var nodeWrap = function (method) {
var crypto = eval("require('crypto')");
var Buffer = eval("require('buffer').Buffer");
var nodeMethod = function (message) {
if (typeof message === 'string') {
return crypto.createHash('sha1').update(message, 'utf8').digest('hex');
} else if (message.constructor === ArrayBuffer) {
message = new Uint8Array(message);
} else if (message.length === undefined) {
return method(message);
}
return crypto.createHash('sha1').update(new Buffer(message)).digest('hex');
};
return nodeMethod;
};
function Sha1(sharedMemory) {
if (sharedMemory) {
blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] =
@ -359,8 +333,8 @@ namespace sha {
Sha1.prototype.arrayBuffer = function () {
this.finalize();
var buffer = new ArrayBuffer(20);
var dataView = new DataView(buffer);
const buffer = new ArrayBuffer(20);
const dataView = new DataView(buffer);
dataView.setUint32(0, this.h0);
dataView.setUint32(4, this.h1);
dataView.setUint32(8, this.h2);
@ -369,18 +343,7 @@ namespace sha {
return buffer;
};
var exports = createMethod();
if (COMMON_JS) {
module.exports = exports;
} else {
root._sha1 = exports;
if (AMD) {
define(function () {
return exports;
});
}
}
createMethod();
})();
export function encode_text(buffer: string) : ArrayBuffer {
@ -394,6 +357,7 @@ namespace sha {
}
return result.buffer;
}
export function sha1(message: string | ArrayBuffer) : PromiseLike<ArrayBuffer> {
if(!(typeof(message) === "string" || message instanceof ArrayBuffer)) throw "Invalid type!";
@ -406,5 +370,3 @@ namespace sha {
else
return crypto.subtle.digest("SHA-1", buffer);
}
}

10
shared/js/crypto/uid.ts Normal file
View file

@ -0,0 +1,10 @@
function s4() {
return Math
.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
export function guid() {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}

View file

@ -1,4 +1,3 @@
namespace dns {
export interface AddressTarget {
target_ip: string;
target_port?: number;
@ -21,4 +20,3 @@ namespace dns {
allow_cache: true,
max_depth: 5
};
}

View file

@ -1,4 +1,8 @@
namespace events {
//TODO: Combine EventConvert and Event?
import {MusicClientEntry, SongInfo} from "tc-shared/ui/client";
import {PlaylistSong} from "tc-shared/connection/ServerConnectionDeclaration";
import {guid} from "tc-shared/crypto/uid";
export interface EventConvert<All> {
as<T extends keyof All>() : All[T];
}
@ -119,10 +123,6 @@ namespace events {
}
}
namespace global {
}
export namespace channel_tree {
export interface client {
"enter_view": {},
@ -549,7 +549,7 @@ namespace events {
vad_type: string,
vad_ppt: {
key: ppt.KeyDescriptor,
key: any, /* ppt.KeyDescriptor */
release_delay: number,
release_delay_active: boolean
},
@ -595,10 +595,11 @@ namespace events {
}
}
}
/*
//Some test code
const eclient = new events.Registry<events.channel_tree.client>();
const emusic = new events.Registry<events.sidebar.music>();
eclient.connect("playlist_song_loaded", emusic);
eclient.connect("playlist_song_loaded", emusic);
*/

View file

@ -1,5 +1,3 @@
namespace i18n {
interface CountryInfo {
name: string;
alpha_2: string;
@ -1501,4 +1499,3 @@ namespace i18n {
fill_country_infos(country_infos);
for(const country of country_infos)
alpha_2_map[country.alpha_2] = country;
}

View file

@ -1,13 +1,11 @@
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import {guid} from "tc-shared/crypto/uid";
import {StaticSettings} from "tc-shared/settings";
import {createErrorModal} from "tc-shared/ui/elements/Modal";
import * as loader from "tc-loader";
import {formatMessage} from "tc-shared/ui/frames/chat";
namespace i18n {
export interface TranslationKey {
message: string;
line?: number;
@ -73,7 +71,7 @@ namespace i18n {
export function tra(message: string, ...args: any[]) {
message = tr(message);
return MessageHelper.formatMessage(message, ...args);
return formatMessage(message, ...args);
}
async function load_translation_file(url: string, path: string) : Promise<TranslationFile> {
@ -288,7 +286,7 @@ namespace i18n {
config.save_translation_config();
}
/* ATTENTION: This method is called before most other library inizialisations! */
/* ATTENTION: This method is called before most other library initialisations! */
export async function initialize() {
const rcfg = config.repository_config(); /* initialize */
const cfg = config.translation_config();
@ -314,11 +312,6 @@ namespace i18n {
// await load_file("http://localhost/home/TeaSpeak/TeaSpeak/Web-Client/web/environment/development/i18n/de_DE.translation");
// await load_file("http://localhost/home/TeaSpeak/TeaSpeak/Web-Client/web/environment/development/i18n/test.json");
}
}
// @ts-ignore
const tr: typeof i18n.tr = i18n.tr;
const tra: typeof i18n.tra = i18n.tra;
(window as any).tr = i18n.tr;
(window as any).tra = i18n.tra;
window.tr = tr;
window.tra = tra;

View file

@ -1,6 +1,8 @@
//Used by CertAccept popup
import {settings} from "tc-shared/settings";
import * as loader from "tc-loader";
enum LogCategory {
export enum LogCategory {
CHANNEL,
CHANNEL_PROPERTIES, /* separating channel and channel properties because on channel init logging is a big bottleneck */
CLIENT,
@ -19,7 +21,6 @@ enum LogCategory {
DNS
}
namespace log {
export enum LogType {
TRACE,
DEBUG,
@ -157,10 +158,10 @@ namespace log {
return new Group(group_mode, level, category, name, optionalParams);
}
export function table(level: LogType, category: LogCategory, title: string, arguments: any) {
export function table(level: LogType, category: LogCategory, title: string, args: any) {
if(group_mode == GroupMode.NATIVE) {
console.groupCollapsed(title);
console.table(arguments);
console.table(args);
console.groupEnd();
} else {
if(!enabled_mapping.get(category) || !level_mapping.get(level))
@ -246,6 +247,12 @@ namespace log {
this._log_prefix = prefix;
}
}
}
import LogType = log.LogType;
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "log enabled initialisation",
function: async () => initialize(loader.version().debug_mode ? LogType.TRACE : LogType.INFO),
priority: 150
});
/* initialize global logging system, use by the loader for example */
window.log = module.exports;

View file

@ -1,32 +1,42 @@
/// <reference path="ui/frames/chat.ts" />
/// <reference path="ui/modal/ModalConnect.ts" />
/// <reference path="ui/modal/ModalCreateChannel.ts" />
/// <reference path="ui/modal/ModalBanClient.ts" />
/// <reference path="ui/modal/ModalYesNo.ts" />
/// <reference path="ui/modal/ModalBanList.ts" />
/// <reference path="settings.ts" />
/// <reference path="log.ts" />
/// <reference path="PPTListener.ts" />
import * as loader from "tc-loader";
import {settings, Settings} from "tc-shared/settings";
import * as profiles from "tc-shared/profiles/ConnectionProfile";
import {LogCategory} from "tc-shared/log";
import * as log from "tc-shared/log";
import * as bipc from "./BrowserIPC";
import * as sound from "./sound/Sounds";
import * as i18n from "./i18n/localize";
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {createInfoModal} from "tc-shared/ui/elements/Modal";
import {tra} from "./i18n/localize";
import {RequestFileUpload} from "tc-shared/FileManager";
import * as stats from "./stats";
import * as fidentity from "./profiles/identities/TeaForumIdentity";
import {default_recorder, RecorderProfile, set_default_recorder} from "tc-shared/voice/RecorderProfile";
import * as cmanager from "tc-shared/ui/frames/connection_handlers";
import {server_connections, ServerConnectionManager} from "tc-shared/ui/frames/connection_handlers";
import * as control_bar from "tc-shared/ui/frames/ControlBar";
import {spawnConnectModal} from "tc-shared/ui/modal/ModalConnect";
import * as top_menu from "./ui/frames/MenuBar";
import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
import {formatMessage} from "tc-shared/ui/frames/chat";
import {openModalNewcomer} from "tc-shared/ui/modal/ModalNewcomer";
import * as aplayer from "tc-backend/audio/player";
import * as arecorder from "tc-backend/audio/recorder";
import * as ppt from "tc-backend/ppt";
import spawnYesNo = Modals.spawnYesNo;
/* required import for init */
require("./proto").initialize();
require("./ui/elements/ContextDivider").initialize();
const js_render = window.jsrender || $;
const native_client = window.require !== undefined;
function getUserMediaFunctionPromise() : (constraints: MediaStreamConstraints) => Promise<MediaStream> {
if('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices)
return constraints => navigator.mediaDevices.getUserMedia(constraints);
const _callbacked_function = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
if(!_callbacked_function)
return undefined;
return constraints => new Promise<MediaStream>((resolve, reject) => _callbacked_function(constraints, resolve, reject));
}
declare global {
interface Window {
open_connected_question: () => Promise<boolean>;
}
}
function setup_close() {
window.onbeforeunload = event => {
@ -50,7 +60,7 @@ function setup_close() {
}));
const exit = () => {
const {remote} = require('electron');
const {remote} = window.require('electron');
remote.getCurrentWindow().close();
};
@ -133,7 +143,7 @@ async function initialize_app() {
try { //Initialize main template
const main = $("#tmpl_main").renderTag({
multi_session: !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION),
app_version: app.ui_version()
app_version: loader.version().ui
}).dividerfy();
$("body").append(main);
@ -143,21 +153,21 @@ async function initialize_app() {
return;
}
control_bar = new ControlBar($("#control_bar")); /* setup the control bar */
control_bar.set_control_bar(new control_bar.ControlBar($("#control_bar"))); /* setup the control bar */
if(!audio.player.initialize())
if(!aplayer.initialize())
console.warn(tr("Failed to initialize audio controller!"));
audio.player.on_ready(() => {
if(audio.player.set_master_volume)
audio.player.on_ready(() => audio.player.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER) / 100));
aplayer.on_ready(() => {
if(aplayer.set_master_volume)
aplayer.on_ready(() => aplayer.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER) / 100));
else
log.warn(LogCategory.GENERAL, tr("Client does not support audio.player.set_master_volume()... May client is too old?"));
if(audio.recorder.device_refresh_available())
audio.recorder.refresh_devices();
log.warn(LogCategory.GENERAL, tr("Client does not support aplayer.set_master_volume()... May client is too old?"));
if(arecorder.device_refresh_available())
arecorder.refresh_devices();
});
default_recorder = new RecorderProfile("default");
set_default_recorder(new RecorderProfile("default"));
default_recorder.initialize().catch(error => {
log.error(LogCategory.AUDIO, tr("Failed to initialize default recorder: %o"), error);
});
@ -180,78 +190,6 @@ async function initialize_app() {
setup_close();
}
function str2ab8(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
/* FIXME Dont use atob, because it sucks for non UTF-8 tings */
function arrayBufferBase64(base64: string) {
base64 = atob(base64);
const buf = new ArrayBuffer(base64.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = base64.length; i < strLen; i++) {
bufView[i] = base64.charCodeAt(i);
}
return buf;
}
function base64_encode_ab(source: ArrayBufferLike) {
const encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let base64 = "";
const bytes = new Uint8Array(source);
const byte_length = bytes.byteLength;
const byte_reminder = byte_length % 3;
const main_length = byte_length - byte_reminder;
let a, b, c, d;
let chunk;
// Main loop deals with bytes in chunks of 3
for (let i = 0; i < main_length; i = i + 3) {
// Combine the three bytes into a single integer
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
// Use bitmasks to extract 6-bit segments from the triplet
a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
d = (chunk & 63) >> 0; // 63 = (2^6 - 1) << 0
// Convert the raw binary segments to the appropriate ASCII encoding
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
}
// Deal with the remaining bytes and padding
if (byte_reminder == 1) {
chunk = bytes[main_length];
a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
// Set the 4 least significant bits to zero
b = (chunk & 3) << 4; // 3 = 2^2 - 1
base64 += encodings[a] + encodings[b] + '==';
} else if (byte_reminder == 2) {
chunk = (bytes[main_length] << 8) | bytes[main_length + 1];
a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
// Set the 2 least significant bits to zero
c = (chunk & 15) << 2; // 15 = 2^4 - 1
base64 += encodings[a] + encodings[b] + encodings[c] + '=';
}
return base64
}
/*
class TestProxy extends bipc.MethodProxy {
constructor(params: bipc.MethodProxyConnectParameters) {
@ -313,7 +251,7 @@ function handle_connect_request(properties: bipc.connect.ConnectRequestData, con
});
server_connections.set_active_connection_handler(connection);
} else {
Modals.spawnConnectModal({},{
spawnConnectModal({},{
url: properties.address,
enforce: true
}, {
@ -356,14 +294,14 @@ function main() {
top_menu.initialize();
server_connections = new ServerConnectionManager($("#connection-handlers"));
control_bar.initialise(); /* before connection handler to allow property apply */
cmanager.initialize(new ServerConnectionManager($("#connection-handlers")));
control_bar.control_bar.initialise(); /* before connection handler to allow property apply */
const initial_handler = server_connections.spawn_server_connection_handler();
initial_handler.acquire_recorder(default_recorder, false);
control_bar.set_connection_handler(initial_handler);
control_bar.control_bar.set_connection_handler(initial_handler);
/** Setup the XF forum identity **/
profiles.identities.update_forum();
fidentity.update_forum();
let _resize_timeout: NodeJS.Timer;
$(window).on('resize', event => {
@ -481,7 +419,7 @@ function main() {
/* for testing */
if(settings.static_global(Settings.KEY_USER_IS_NEW)) {
const modal = Modals.openModalNewcomer();
const modal = openModalNewcomer();
modal.close_listener.push(() => settings.changeGlobal(Settings.KEY_USER_IS_NEW, false));
}
}
@ -492,12 +430,12 @@ const task_teaweb_starter: loader.Task = {
try {
await initialize_app();
main();
if(!audio.player.initialized()) {
if(!aplayer.initialized()) {
log.info(LogCategory.VOICE, tr("Initialize audio controller later!"));
if(!audio.player.initializeFromGesture) {
console.error(tr("Missing audio.player.initializeFromGesture"));
if(!aplayer.initializeFromGesture) {
console.error(tr("Missing aplayer.initializeFromGesture"));
} else
$(document).one('click', event => audio.player.initializeFromGesture());
$(document).one('click', event => aplayer.initializeFromGesture());
}
} catch (ex) {
console.error(ex.stack);
@ -543,7 +481,7 @@ const task_connect_handler: loader.Task = {
"You could now close this page.";
createInfoModal(
tr("Connecting successfully within other instance"),
MessageHelper.formatMessage(tr(message), connect_data.address),
formatMessage(tr(message), connect_data.address),
{
closeable: false,
footer: undefined
@ -610,7 +548,7 @@ const task_certificate_callback: loader.Task = {
"This page will close in {0} seconds.";
createInfoModal(
tr("Certificate acccepted successfully"),
MessageHelper.formatMessage(tr(message), seconds_tag),
formatMessage(tr(message), seconds_tag),
{
closeable: false,
footer: undefined
@ -650,7 +588,7 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
try {
await initialize();
if(app.is_web()) {
if(loader.version().type == "web") {
loader.register_task(loader.Stage.LOADED, task_certificate_callback);
} else {
loader.register_task(loader.Stage.LOADED, task_teaweb_starter);
@ -667,3 +605,15 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
priority: 1000
});
loader.register_task(loader.Stage.LOADED, {
name: "error task",
function: async () => {
if(Settings.instance.static(Settings.KEY_LOAD_DUMMY_ERROR, false)) {
loader.critical_error("The tea is cold!", "Argh, this is evil! Cold tea dosn't taste good.");
throw "The tea is cold!";
}
},
priority: 20
});
export = {};

View file

@ -1,17 +1,24 @@
/// <reference path="../connection/ConnectionBase.ts" />
import {LaterPromise} from "tc-shared/utils/LaterPromise";
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import {PermissionManager, PermissionValue} from "tc-shared/permission/PermissionManager";
import {ServerCommand} from "tc-shared/connection/ConnectionBase";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {AbstractCommandHandler} from "tc-shared/connection/AbstractCommandHandler";
enum GroupType {
export enum GroupType {
QUERY,
TEMPLATE,
NORMAL
}
enum GroupTarget {
export enum GroupTarget {
SERVER,
CHANNEL
}
class GroupProperties {
export class GroupProperties {
iconid: number = 0;
sortid: number = 0;
@ -19,12 +26,12 @@ class GroupProperties {
namemode: number = 0;
}
class GroupPermissionRequest {
export class GroupPermissionRequest {
group_id: number;
promise: LaterPromise<PermissionValue[]>;
}
class Group {
export class Group {
properties: GroupProperties = new GroupProperties();
readonly handle: GroupManager;
@ -63,7 +70,7 @@ class Group {
}
}
class GroupManager extends connection.AbstractCommandHandler {
export class GroupManager extends AbstractCommandHandler {
readonly handle: ConnectionHandler;
serverGroups: Group[] = [];
@ -83,7 +90,7 @@ class GroupManager extends connection.AbstractCommandHandler {
this.channelGroups = undefined;
}
handle_command(command: connection.ServerCommand): boolean {
handle_command(command: ServerCommand): boolean {
switch (command.command) {
case "notifyservergrouplist":
case "notifychannelgrouplist":
@ -160,7 +167,7 @@ class GroupManager extends connection.AbstractCommandHandler {
}
let group = new Group(this,parseInt(target == GroupTarget.SERVER ? groupData["sgid"] : groupData["cgid"]), target, type, groupData["name"]);
for(let key in groupData as any) {
for(let key in Object.keys(groupData)) {
if(key == "sgid") continue;
if(key == "cgid") continue;
if(key == "type") continue;

View file

@ -1,358 +1,13 @@
/// <reference path="../ConnectionHandler.ts" />
/// <reference path="../connection/ConnectionBase.ts" />
/// <reference path="../i18n/localize.ts" />
import * as log from "tc-shared/log";
import {LogCategory, LogType} from "tc-shared/log";
import PermissionType from "tc-shared/permission/PermissionType";
import {LaterPromise} from "tc-shared/utils/LaterPromise";
import {ServerCommand} from "tc-shared/connection/ConnectionBase";
import {CommandResult, ErrorID} from "tc-shared/connection/ServerConnectionDeclaration";
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {AbstractCommandHandler} from "tc-shared/connection/AbstractCommandHandler";
enum PermissionType {
B_SERVERINSTANCE_HELP_VIEW = "b_serverinstance_help_view",
B_SERVERINSTANCE_VERSION_VIEW = "b_serverinstance_version_view",
B_SERVERINSTANCE_INFO_VIEW = "b_serverinstance_info_view",
B_SERVERINSTANCE_VIRTUALSERVER_LIST = "b_serverinstance_virtualserver_list",
B_SERVERINSTANCE_BINDING_LIST = "b_serverinstance_binding_list",
B_SERVERINSTANCE_PERMISSION_LIST = "b_serverinstance_permission_list",
B_SERVERINSTANCE_PERMISSION_FIND = "b_serverinstance_permission_find",
B_VIRTUALSERVER_CREATE = "b_virtualserver_create",
B_VIRTUALSERVER_DELETE = "b_virtualserver_delete",
B_VIRTUALSERVER_START_ANY = "b_virtualserver_start_any",
B_VIRTUALSERVER_STOP_ANY = "b_virtualserver_stop_any",
B_VIRTUALSERVER_CHANGE_MACHINE_ID = "b_virtualserver_change_machine_id",
B_VIRTUALSERVER_CHANGE_TEMPLATE = "b_virtualserver_change_template",
B_SERVERQUERY_LOGIN = "b_serverquery_login",
B_SERVERINSTANCE_TEXTMESSAGE_SEND = "b_serverinstance_textmessage_send",
B_SERVERINSTANCE_LOG_VIEW = "b_serverinstance_log_view",
B_SERVERINSTANCE_LOG_ADD = "b_serverinstance_log_add",
B_SERVERINSTANCE_STOP = "b_serverinstance_stop",
B_SERVERINSTANCE_MODIFY_SETTINGS = "b_serverinstance_modify_settings",
B_SERVERINSTANCE_MODIFY_QUERYGROUP = "b_serverinstance_modify_querygroup",
B_SERVERINSTANCE_MODIFY_TEMPLATES = "b_serverinstance_modify_templates",
B_VIRTUALSERVER_SELECT = "b_virtualserver_select",
B_VIRTUALSERVER_SELECT_GODMODE = "b_virtualserver_select_godmode",
B_VIRTUALSERVER_INFO_VIEW = "b_virtualserver_info_view",
B_VIRTUALSERVER_CONNECTIONINFO_VIEW = "b_virtualserver_connectioninfo_view",
B_VIRTUALSERVER_CHANNEL_LIST = "b_virtualserver_channel_list",
B_VIRTUALSERVER_CHANNEL_SEARCH = "b_virtualserver_channel_search",
B_VIRTUALSERVER_CLIENT_LIST = "b_virtualserver_client_list",
B_VIRTUALSERVER_CLIENT_SEARCH = "b_virtualserver_client_search",
B_VIRTUALSERVER_CLIENT_DBLIST = "b_virtualserver_client_dblist",
B_VIRTUALSERVER_CLIENT_DBSEARCH = "b_virtualserver_client_dbsearch",
B_VIRTUALSERVER_CLIENT_DBINFO = "b_virtualserver_client_dbinfo",
B_VIRTUALSERVER_PERMISSION_FIND = "b_virtualserver_permission_find",
B_VIRTUALSERVER_CUSTOM_SEARCH = "b_virtualserver_custom_search",
B_VIRTUALSERVER_START = "b_virtualserver_start",
B_VIRTUALSERVER_STOP = "b_virtualserver_stop",
B_VIRTUALSERVER_TOKEN_LIST = "b_virtualserver_token_list",
B_VIRTUALSERVER_TOKEN_ADD = "b_virtualserver_token_add",
B_VIRTUALSERVER_TOKEN_USE = "b_virtualserver_token_use",
B_VIRTUALSERVER_TOKEN_DELETE = "b_virtualserver_token_delete",
B_VIRTUALSERVER_LOG_VIEW = "b_virtualserver_log_view",
B_VIRTUALSERVER_LOG_ADD = "b_virtualserver_log_add",
B_VIRTUALSERVER_JOIN_IGNORE_PASSWORD = "b_virtualserver_join_ignore_password",
B_VIRTUALSERVER_NOTIFY_REGISTER = "b_virtualserver_notify_register",
B_VIRTUALSERVER_NOTIFY_UNREGISTER = "b_virtualserver_notify_unregister",
B_VIRTUALSERVER_SNAPSHOT_CREATE = "b_virtualserver_snapshot_create",
B_VIRTUALSERVER_SNAPSHOT_DEPLOY = "b_virtualserver_snapshot_deploy",
B_VIRTUALSERVER_PERMISSION_RESET = "b_virtualserver_permission_reset",
B_VIRTUALSERVER_MODIFY_NAME = "b_virtualserver_modify_name",
B_VIRTUALSERVER_MODIFY_WELCOMEMESSAGE = "b_virtualserver_modify_welcomemessage",
B_VIRTUALSERVER_MODIFY_MAXCLIENTS = "b_virtualserver_modify_maxclients",
B_VIRTUALSERVER_MODIFY_RESERVED_SLOTS = "b_virtualserver_modify_reserved_slots",
B_VIRTUALSERVER_MODIFY_PASSWORD = "b_virtualserver_modify_password",
B_VIRTUALSERVER_MODIFY_DEFAULT_SERVERGROUP = "b_virtualserver_modify_default_servergroup",
B_VIRTUALSERVER_MODIFY_DEFAULT_MUSICGROUP = "b_virtualserver_modify_default_musicgroup",
B_VIRTUALSERVER_MODIFY_DEFAULT_CHANNELGROUP = "b_virtualserver_modify_default_channelgroup",
B_VIRTUALSERVER_MODIFY_DEFAULT_CHANNELADMINGROUP = "b_virtualserver_modify_default_channeladmingroup",
B_VIRTUALSERVER_MODIFY_CHANNEL_FORCED_SILENCE = "b_virtualserver_modify_channel_forced_silence",
B_VIRTUALSERVER_MODIFY_COMPLAIN = "b_virtualserver_modify_complain",
B_VIRTUALSERVER_MODIFY_ANTIFLOOD = "b_virtualserver_modify_antiflood",
B_VIRTUALSERVER_MODIFY_FT_SETTINGS = "b_virtualserver_modify_ft_settings",
B_VIRTUALSERVER_MODIFY_FT_QUOTAS = "b_virtualserver_modify_ft_quotas",
B_VIRTUALSERVER_MODIFY_HOSTMESSAGE = "b_virtualserver_modify_hostmessage",
B_VIRTUALSERVER_MODIFY_HOSTBANNER = "b_virtualserver_modify_hostbanner",
B_VIRTUALSERVER_MODIFY_HOSTBUTTON = "b_virtualserver_modify_hostbutton",
B_VIRTUALSERVER_MODIFY_PORT = "b_virtualserver_modify_port",
B_VIRTUALSERVER_MODIFY_HOST = "b_virtualserver_modify_host",
B_VIRTUALSERVER_MODIFY_DEFAULT_MESSAGES = "b_virtualserver_modify_default_messages",
B_VIRTUALSERVER_MODIFY_AUTOSTART = "b_virtualserver_modify_autostart",
B_VIRTUALSERVER_MODIFY_NEEDED_IDENTITY_SECURITY_LEVEL = "b_virtualserver_modify_needed_identity_security_level",
B_VIRTUALSERVER_MODIFY_PRIORITY_SPEAKER_DIMM_MODIFICATOR = "b_virtualserver_modify_priority_speaker_dimm_modificator",
B_VIRTUALSERVER_MODIFY_LOG_SETTINGS = "b_virtualserver_modify_log_settings",
B_VIRTUALSERVER_MODIFY_MIN_CLIENT_VERSION = "b_virtualserver_modify_min_client_version",
B_VIRTUALSERVER_MODIFY_ICON_ID = "b_virtualserver_modify_icon_id",
B_VIRTUALSERVER_MODIFY_WEBLIST = "b_virtualserver_modify_weblist",
B_VIRTUALSERVER_MODIFY_CODEC_ENCRYPTION_MODE = "b_virtualserver_modify_codec_encryption_mode",
B_VIRTUALSERVER_MODIFY_TEMPORARY_PASSWORDS = "b_virtualserver_modify_temporary_passwords",
B_VIRTUALSERVER_MODIFY_TEMPORARY_PASSWORDS_OWN = "b_virtualserver_modify_temporary_passwords_own",
B_VIRTUALSERVER_MODIFY_CHANNEL_TEMP_DELETE_DELAY_DEFAULT = "b_virtualserver_modify_channel_temp_delete_delay_default",
B_VIRTUALSERVER_MODIFY_MUSIC_BOT_LIMIT = "b_virtualserver_modify_music_bot_limit",
B_VIRTUALSERVER_MODIFY_COUNTRY_CODE = "b_virtualserver_modify_country_code",
I_CHANNEL_MIN_DEPTH = "i_channel_min_depth",
I_CHANNEL_MAX_DEPTH = "i_channel_max_depth",
B_CHANNEL_GROUP_INHERITANCE_END = "b_channel_group_inheritance_end",
I_CHANNEL_PERMISSION_MODIFY_POWER = "i_channel_permission_modify_power",
I_CHANNEL_NEEDED_PERMISSION_MODIFY_POWER = "i_channel_needed_permission_modify_power",
B_CHANNEL_INFO_VIEW = "b_channel_info_view",
B_CHANNEL_CREATE_CHILD = "b_channel_create_child",
B_CHANNEL_CREATE_PERMANENT = "b_channel_create_permanent",
B_CHANNEL_CREATE_SEMI_PERMANENT = "b_channel_create_semi_permanent",
B_CHANNEL_CREATE_TEMPORARY = "b_channel_create_temporary",
B_CHANNEL_CREATE_PRIVATE = "b_channel_create_private",
B_CHANNEL_CREATE_WITH_TOPIC = "b_channel_create_with_topic",
B_CHANNEL_CREATE_WITH_DESCRIPTION = "b_channel_create_with_description",
B_CHANNEL_CREATE_WITH_PASSWORD = "b_channel_create_with_password",
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX8 = "b_channel_create_modify_with_codec_speex8",
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX16 = "b_channel_create_modify_with_codec_speex16",
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX32 = "b_channel_create_modify_with_codec_speex32",
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_CELTMONO48 = "b_channel_create_modify_with_codec_celtmono48",
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSVOICE = "b_channel_create_modify_with_codec_opusvoice",
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSMUSIC = "b_channel_create_modify_with_codec_opusmusic",
I_CHANNEL_CREATE_MODIFY_WITH_CODEC_MAXQUALITY = "i_channel_create_modify_with_codec_maxquality",
I_CHANNEL_CREATE_MODIFY_WITH_CODEC_LATENCY_FACTOR_MIN = "i_channel_create_modify_with_codec_latency_factor_min",
I_CHANNEL_CREATE_MODIFY_CONVERSATION_HISTORY_LENGTH = "i_channel_create_modify_conversation_history_length",
B_CHANNEL_CREATE_MODIFY_CONVERSATION_HISTORY_UNLIMITED = "b_channel_create_modify_conversation_history_unlimited",
B_CHANNEL_CREATE_MODIFY_CONVERSATION_PRIVATE = "b_channel_create_modify_conversation_private",
B_CHANNEL_CREATE_WITH_MAXCLIENTS = "b_channel_create_with_maxclients",
B_CHANNEL_CREATE_WITH_MAXFAMILYCLIENTS = "b_channel_create_with_maxfamilyclients",
B_CHANNEL_CREATE_WITH_SORTORDER = "b_channel_create_with_sortorder",
B_CHANNEL_CREATE_WITH_DEFAULT = "b_channel_create_with_default",
B_CHANNEL_CREATE_WITH_NEEDED_TALK_POWER = "b_channel_create_with_needed_talk_power",
B_CHANNEL_CREATE_MODIFY_WITH_FORCE_PASSWORD = "b_channel_create_modify_with_force_password",
I_CHANNEL_CREATE_MODIFY_WITH_TEMP_DELETE_DELAY = "i_channel_create_modify_with_temp_delete_delay",
B_CHANNEL_MODIFY_PARENT = "b_channel_modify_parent",
B_CHANNEL_MODIFY_MAKE_DEFAULT = "b_channel_modify_make_default",
B_CHANNEL_MODIFY_MAKE_PERMANENT = "b_channel_modify_make_permanent",
B_CHANNEL_MODIFY_MAKE_SEMI_PERMANENT = "b_channel_modify_make_semi_permanent",
B_CHANNEL_MODIFY_MAKE_TEMPORARY = "b_channel_modify_make_temporary",
B_CHANNEL_MODIFY_NAME = "b_channel_modify_name",
B_CHANNEL_MODIFY_TOPIC = "b_channel_modify_topic",
B_CHANNEL_MODIFY_DESCRIPTION = "b_channel_modify_description",
B_CHANNEL_MODIFY_PASSWORD = "b_channel_modify_password",
B_CHANNEL_MODIFY_CODEC = "b_channel_modify_codec",
B_CHANNEL_MODIFY_CODEC_QUALITY = "b_channel_modify_codec_quality",
B_CHANNEL_MODIFY_CODEC_LATENCY_FACTOR = "b_channel_modify_codec_latency_factor",
B_CHANNEL_MODIFY_MAXCLIENTS = "b_channel_modify_maxclients",
B_CHANNEL_MODIFY_MAXFAMILYCLIENTS = "b_channel_modify_maxfamilyclients",
B_CHANNEL_MODIFY_SORTORDER = "b_channel_modify_sortorder",
B_CHANNEL_MODIFY_NEEDED_TALK_POWER = "b_channel_modify_needed_talk_power",
I_CHANNEL_MODIFY_POWER = "i_channel_modify_power",
I_CHANNEL_NEEDED_MODIFY_POWER = "i_channel_needed_modify_power",
B_CHANNEL_MODIFY_MAKE_CODEC_ENCRYPTED = "b_channel_modify_make_codec_encrypted",
B_CHANNEL_MODIFY_TEMP_DELETE_DELAY = "b_channel_modify_temp_delete_delay",
B_CHANNEL_DELETE_PERMANENT = "b_channel_delete_permanent",
B_CHANNEL_DELETE_SEMI_PERMANENT = "b_channel_delete_semi_permanent",
B_CHANNEL_DELETE_TEMPORARY = "b_channel_delete_temporary",
B_CHANNEL_DELETE_FLAG_FORCE = "b_channel_delete_flag_force",
I_CHANNEL_DELETE_POWER = "i_channel_delete_power",
B_CHANNEL_CONVERSATION_MESSAGE_DELETE = "b_channel_conversation_message_delete",
I_CHANNEL_NEEDED_DELETE_POWER = "i_channel_needed_delete_power",
B_CHANNEL_JOIN_PERMANENT = "b_channel_join_permanent",
B_CHANNEL_JOIN_SEMI_PERMANENT = "b_channel_join_semi_permanent",
B_CHANNEL_JOIN_TEMPORARY = "b_channel_join_temporary",
B_CHANNEL_JOIN_IGNORE_PASSWORD = "b_channel_join_ignore_password",
B_CHANNEL_JOIN_IGNORE_MAXCLIENTS = "b_channel_join_ignore_maxclients",
B_CHANNEL_IGNORE_VIEW_POWER = "b_channel_ignore_view_power",
I_CHANNEL_JOIN_POWER = "i_channel_join_power",
I_CHANNEL_NEEDED_JOIN_POWER = "i_channel_needed_join_power",
B_CHANNEL_IGNORE_JOIN_POWER = "b_channel_ignore_join_power",
B_CHANNEL_IGNORE_DESCRIPTION_VIEW_POWER = "b_channel_ignore_description_view_power",
I_CHANNEL_VIEW_POWER = "i_channel_view_power",
I_CHANNEL_NEEDED_VIEW_POWER = "i_channel_needed_view_power",
I_CHANNEL_SUBSCRIBE_POWER = "i_channel_subscribe_power",
I_CHANNEL_NEEDED_SUBSCRIBE_POWER = "i_channel_needed_subscribe_power",
I_CHANNEL_DESCRIPTION_VIEW_POWER = "i_channel_description_view_power",
I_CHANNEL_NEEDED_DESCRIPTION_VIEW_POWER = "i_channel_needed_description_view_power",
I_ICON_ID = "i_icon_id",
I_MAX_ICON_FILESIZE = "i_max_icon_filesize",
I_MAX_PLAYLIST_SIZE = "i_max_playlist_size",
I_MAX_PLAYLISTS = "i_max_playlists",
B_ICON_MANAGE = "b_icon_manage",
B_GROUP_IS_PERMANENT = "b_group_is_permanent",
I_GROUP_AUTO_UPDATE_TYPE = "i_group_auto_update_type",
I_GROUP_AUTO_UPDATE_MAX_VALUE = "i_group_auto_update_max_value",
I_GROUP_SORT_ID = "i_group_sort_id",
I_GROUP_SHOW_NAME_IN_TREE = "i_group_show_name_in_tree",
B_VIRTUALSERVER_SERVERGROUP_CREATE = "b_virtualserver_servergroup_create",
B_VIRTUALSERVER_SERVERGROUP_LIST = "b_virtualserver_servergroup_list",
B_VIRTUALSERVER_SERVERGROUP_PERMISSION_LIST = "b_virtualserver_servergroup_permission_list",
B_VIRTUALSERVER_SERVERGROUP_CLIENT_LIST = "b_virtualserver_servergroup_client_list",
B_VIRTUALSERVER_CHANNELGROUP_CREATE = "b_virtualserver_channelgroup_create",
B_VIRTUALSERVER_CHANNELGROUP_LIST = "b_virtualserver_channelgroup_list",
B_VIRTUALSERVER_CHANNELGROUP_PERMISSION_LIST = "b_virtualserver_channelgroup_permission_list",
B_VIRTUALSERVER_CHANNELGROUP_CLIENT_LIST = "b_virtualserver_channelgroup_client_list",
B_VIRTUALSERVER_CLIENT_PERMISSION_LIST = "b_virtualserver_client_permission_list",
B_VIRTUALSERVER_CHANNEL_PERMISSION_LIST = "b_virtualserver_channel_permission_list",
B_VIRTUALSERVER_CHANNELCLIENT_PERMISSION_LIST = "b_virtualserver_channelclient_permission_list",
B_VIRTUALSERVER_PLAYLIST_PERMISSION_LIST = "b_virtualserver_playlist_permission_list",
I_SERVER_GROUP_MODIFY_POWER = "i_server_group_modify_power",
I_SERVER_GROUP_NEEDED_MODIFY_POWER = "i_server_group_needed_modify_power",
I_SERVER_GROUP_MEMBER_ADD_POWER = "i_server_group_member_add_power",
I_SERVER_GROUP_SELF_ADD_POWER = "i_server_group_self_add_power",
I_SERVER_GROUP_NEEDED_MEMBER_ADD_POWER = "i_server_group_needed_member_add_power",
I_SERVER_GROUP_MEMBER_REMOVE_POWER = "i_server_group_member_remove_power",
I_SERVER_GROUP_SELF_REMOVE_POWER = "i_server_group_self_remove_power",
I_SERVER_GROUP_NEEDED_MEMBER_REMOVE_POWER = "i_server_group_needed_member_remove_power",
I_CHANNEL_GROUP_MODIFY_POWER = "i_channel_group_modify_power",
I_CHANNEL_GROUP_NEEDED_MODIFY_POWER = "i_channel_group_needed_modify_power",
I_CHANNEL_GROUP_MEMBER_ADD_POWER = "i_channel_group_member_add_power",
I_CHANNEL_GROUP_SELF_ADD_POWER = "i_channel_group_self_add_power",
I_CHANNEL_GROUP_NEEDED_MEMBER_ADD_POWER = "i_channel_group_needed_member_add_power",
I_CHANNEL_GROUP_MEMBER_REMOVE_POWER = "i_channel_group_member_remove_power",
I_CHANNEL_GROUP_SELF_REMOVE_POWER = "i_channel_group_self_remove_power",
I_CHANNEL_GROUP_NEEDED_MEMBER_REMOVE_POWER = "i_channel_group_needed_member_remove_power",
I_GROUP_MEMBER_ADD_POWER = "i_group_member_add_power",
I_GROUP_NEEDED_MEMBER_ADD_POWER = "i_group_needed_member_add_power",
I_GROUP_MEMBER_REMOVE_POWER = "i_group_member_remove_power",
I_GROUP_NEEDED_MEMBER_REMOVE_POWER = "i_group_needed_member_remove_power",
I_GROUP_MODIFY_POWER = "i_group_modify_power",
I_GROUP_NEEDED_MODIFY_POWER = "i_group_needed_modify_power",
I_PERMISSION_MODIFY_POWER = "i_permission_modify_power",
B_PERMISSION_MODIFY_POWER_IGNORE = "b_permission_modify_power_ignore",
B_VIRTUALSERVER_SERVERGROUP_DELETE = "b_virtualserver_servergroup_delete",
B_VIRTUALSERVER_CHANNELGROUP_DELETE = "b_virtualserver_channelgroup_delete",
I_CLIENT_PERMISSION_MODIFY_POWER = "i_client_permission_modify_power",
I_CLIENT_NEEDED_PERMISSION_MODIFY_POWER = "i_client_needed_permission_modify_power",
I_CLIENT_MAX_CLONES_UID = "i_client_max_clones_uid",
I_CLIENT_MAX_CLONES_IP = "i_client_max_clones_ip",
I_CLIENT_MAX_CLONES_HWID = "i_client_max_clones_hwid",
I_CLIENT_MAX_IDLETIME = "i_client_max_idletime",
I_CLIENT_MAX_AVATAR_FILESIZE = "i_client_max_avatar_filesize",
I_CLIENT_MAX_CHANNEL_SUBSCRIPTIONS = "i_client_max_channel_subscriptions",
I_CLIENT_MAX_CHANNELS = "i_client_max_channels",
I_CLIENT_MAX_TEMPORARY_CHANNELS = "i_client_max_temporary_channels",
I_CLIENT_MAX_SEMI_CHANNELS = "i_client_max_semi_channels",
I_CLIENT_MAX_PERMANENT_CHANNELS = "i_client_max_permanent_channels",
B_CLIENT_USE_PRIORITY_SPEAKER = "b_client_use_priority_speaker",
B_CLIENT_SKIP_CHANNELGROUP_PERMISSIONS = "b_client_skip_channelgroup_permissions",
B_CLIENT_FORCE_PUSH_TO_TALK = "b_client_force_push_to_talk",
B_CLIENT_IGNORE_BANS = "b_client_ignore_bans",
B_CLIENT_IGNORE_VPN = "b_client_ignore_vpn",
B_CLIENT_IGNORE_ANTIFLOOD = "b_client_ignore_antiflood",
B_CLIENT_ENFORCE_VALID_HWID = "b_client_enforce_valid_hwid",
B_CLIENT_ALLOW_INVALID_PACKET = "b_client_allow_invalid_packet",
B_CLIENT_ALLOW_INVALID_BADGES = "b_client_allow_invalid_badges",
B_CLIENT_ISSUE_CLIENT_QUERY_COMMAND = "b_client_issue_client_query_command",
B_CLIENT_USE_RESERVED_SLOT = "b_client_use_reserved_slot",
B_CLIENT_USE_CHANNEL_COMMANDER = "b_client_use_channel_commander",
B_CLIENT_REQUEST_TALKER = "b_client_request_talker",
B_CLIENT_AVATAR_DELETE_OTHER = "b_client_avatar_delete_other",
B_CLIENT_IS_STICKY = "b_client_is_sticky",
B_CLIENT_IGNORE_STICKY = "b_client_ignore_sticky",
B_CLIENT_MUSIC_CREATE_PERMANENT = "b_client_music_create_permanent",
B_CLIENT_MUSIC_CREATE_SEMI_PERMANENT = "b_client_music_create_semi_permanent",
B_CLIENT_MUSIC_CREATE_TEMPORARY = "b_client_music_create_temporary",
B_CLIENT_MUSIC_MODIFY_PERMANENT = "b_client_music_modify_permanent",
B_CLIENT_MUSIC_MODIFY_SEMI_PERMANENT = "b_client_music_modify_semi_permanent",
B_CLIENT_MUSIC_MODIFY_TEMPORARY = "b_client_music_modify_temporary",
I_CLIENT_MUSIC_CREATE_MODIFY_MAX_VOLUME = "i_client_music_create_modify_max_volume",
I_CLIENT_MUSIC_LIMIT = "i_client_music_limit",
I_CLIENT_MUSIC_NEEDED_DELETE_POWER = "i_client_music_needed_delete_power",
I_CLIENT_MUSIC_DELETE_POWER = "i_client_music_delete_power",
I_CLIENT_MUSIC_PLAY_POWER = "i_client_music_play_power",
I_CLIENT_MUSIC_NEEDED_PLAY_POWER = "i_client_music_needed_play_power",
I_CLIENT_MUSIC_MODIFY_POWER = "i_client_music_modify_power",
I_CLIENT_MUSIC_NEEDED_MODIFY_POWER = "i_client_music_needed_modify_power",
I_CLIENT_MUSIC_RENAME_POWER = "i_client_music_rename_power",
I_CLIENT_MUSIC_NEEDED_RENAME_POWER = "i_client_music_needed_rename_power",
B_PLAYLIST_CREATE = "b_playlist_create",
I_PLAYLIST_VIEW_POWER = "i_playlist_view_power",
I_PLAYLIST_NEEDED_VIEW_POWER = "i_playlist_needed_view_power",
I_PLAYLIST_MODIFY_POWER = "i_playlist_modify_power",
I_PLAYLIST_NEEDED_MODIFY_POWER = "i_playlist_needed_modify_power",
I_PLAYLIST_PERMISSION_MODIFY_POWER = "i_playlist_permission_modify_power",
I_PLAYLIST_NEEDED_PERMISSION_MODIFY_POWER = "i_playlist_needed_permission_modify_power",
I_PLAYLIST_DELETE_POWER = "i_playlist_delete_power",
I_PLAYLIST_NEEDED_DELETE_POWER = "i_playlist_needed_delete_power",
I_PLAYLIST_SONG_ADD_POWER = "i_playlist_song_add_power",
I_PLAYLIST_SONG_NEEDED_ADD_POWER = "i_playlist_song_needed_add_power",
I_PLAYLIST_SONG_REMOVE_POWER = "i_playlist_song_remove_power",
I_PLAYLIST_SONG_NEEDED_REMOVE_POWER = "i_playlist_song_needed_remove_power",
B_CLIENT_INFO_VIEW = "b_client_info_view",
B_CLIENT_PERMISSIONOVERVIEW_VIEW = "b_client_permissionoverview_view",
B_CLIENT_PERMISSIONOVERVIEW_OWN = "b_client_permissionoverview_own",
B_CLIENT_REMOTEADDRESS_VIEW = "b_client_remoteaddress_view",
I_CLIENT_SERVERQUERY_VIEW_POWER = "i_client_serverquery_view_power",
I_CLIENT_NEEDED_SERVERQUERY_VIEW_POWER = "i_client_needed_serverquery_view_power",
B_CLIENT_CUSTOM_INFO_VIEW = "b_client_custom_info_view",
B_CLIENT_MUSIC_CHANNEL_LIST = "b_client_music_channel_list",
B_CLIENT_MUSIC_SERVER_LIST = "b_client_music_server_list",
I_CLIENT_MUSIC_INFO = "i_client_music_info",
I_CLIENT_MUSIC_NEEDED_INFO = "i_client_music_needed_info",
I_CLIENT_KICK_FROM_SERVER_POWER = "i_client_kick_from_server_power",
I_CLIENT_NEEDED_KICK_FROM_SERVER_POWER = "i_client_needed_kick_from_server_power",
I_CLIENT_KICK_FROM_CHANNEL_POWER = "i_client_kick_from_channel_power",
I_CLIENT_NEEDED_KICK_FROM_CHANNEL_POWER = "i_client_needed_kick_from_channel_power",
I_CLIENT_BAN_POWER = "i_client_ban_power",
I_CLIENT_NEEDED_BAN_POWER = "i_client_needed_ban_power",
I_CLIENT_MOVE_POWER = "i_client_move_power",
I_CLIENT_NEEDED_MOVE_POWER = "i_client_needed_move_power",
I_CLIENT_COMPLAIN_POWER = "i_client_complain_power",
I_CLIENT_NEEDED_COMPLAIN_POWER = "i_client_needed_complain_power",
B_CLIENT_COMPLAIN_LIST = "b_client_complain_list",
B_CLIENT_COMPLAIN_DELETE_OWN = "b_client_complain_delete_own",
B_CLIENT_COMPLAIN_DELETE = "b_client_complain_delete",
B_CLIENT_BAN_LIST = "b_client_ban_list",
B_CLIENT_BAN_LIST_GLOBAL = "b_client_ban_list_global",
B_CLIENT_BAN_TRIGGER_LIST = "b_client_ban_trigger_list",
B_CLIENT_BAN_CREATE = "b_client_ban_create",
B_CLIENT_BAN_CREATE_GLOBAL = "b_client_ban_create_global",
B_CLIENT_BAN_NAME = "b_client_ban_name",
B_CLIENT_BAN_IP = "b_client_ban_ip",
B_CLIENT_BAN_HWID = "b_client_ban_hwid",
B_CLIENT_BAN_EDIT = "b_client_ban_edit",
B_CLIENT_BAN_EDIT_GLOBAL = "b_client_ban_edit_global",
B_CLIENT_BAN_DELETE_OWN = "b_client_ban_delete_own",
B_CLIENT_BAN_DELETE = "b_client_ban_delete",
B_CLIENT_BAN_DELETE_OWN_GLOBAL = "b_client_ban_delete_own_global",
B_CLIENT_BAN_DELETE_GLOBAL = "b_client_ban_delete_global",
I_CLIENT_BAN_MAX_BANTIME = "i_client_ban_max_bantime",
I_CLIENT_PRIVATE_TEXTMESSAGE_POWER = "i_client_private_textmessage_power",
I_CLIENT_NEEDED_PRIVATE_TEXTMESSAGE_POWER = "i_client_needed_private_textmessage_power",
B_CLIENT_EVEN_TEXTMESSAGE_SEND = "b_client_even_textmessage_send",
B_CLIENT_SERVER_TEXTMESSAGE_SEND = "b_client_server_textmessage_send",
B_CLIENT_CHANNEL_TEXTMESSAGE_SEND = "b_client_channel_textmessage_send",
B_CLIENT_OFFLINE_TEXTMESSAGE_SEND = "b_client_offline_textmessage_send",
I_CLIENT_TALK_POWER = "i_client_talk_power",
I_CLIENT_NEEDED_TALK_POWER = "i_client_needed_talk_power",
I_CLIENT_POKE_POWER = "i_client_poke_power",
I_CLIENT_NEEDED_POKE_POWER = "i_client_needed_poke_power",
B_CLIENT_SET_FLAG_TALKER = "b_client_set_flag_talker",
I_CLIENT_WHISPER_POWER = "i_client_whisper_power",
I_CLIENT_NEEDED_WHISPER_POWER = "i_client_needed_whisper_power",
B_CLIENT_MODIFY_DESCRIPTION = "b_client_modify_description",
B_CLIENT_MODIFY_OWN_DESCRIPTION = "b_client_modify_own_description",
B_CLIENT_USE_BBCODE_ANY = "b_client_use_bbcode_any",
B_CLIENT_USE_BBCODE_URL = "b_client_use_bbcode_url",
B_CLIENT_USE_BBCODE_IMAGE = "b_client_use_bbcode_image",
B_CLIENT_MODIFY_DBPROPERTIES = "b_client_modify_dbproperties",
B_CLIENT_DELETE_DBPROPERTIES = "b_client_delete_dbproperties",
B_CLIENT_CREATE_MODIFY_SERVERQUERY_LOGIN = "b_client_create_modify_serverquery_login",
B_CLIENT_QUERY_CREATE = "b_client_query_create",
B_CLIENT_QUERY_LIST = "b_client_query_list",
B_CLIENT_QUERY_LIST_OWN = "b_client_query_list_own",
B_CLIENT_QUERY_RENAME = "b_client_query_rename",
B_CLIENT_QUERY_RENAME_OWN = "b_client_query_rename_own",
B_CLIENT_QUERY_CHANGE_PASSWORD = "b_client_query_change_password",
B_CLIENT_QUERY_CHANGE_OWN_PASSWORD = "b_client_query_change_own_password",
B_CLIENT_QUERY_CHANGE_PASSWORD_GLOBAL = "b_client_query_change_password_global",
B_CLIENT_QUERY_DELETE = "b_client_query_delete",
B_CLIENT_QUERY_DELETE_OWN = "b_client_query_delete_own",
B_FT_IGNORE_PASSWORD = "b_ft_ignore_password",
B_FT_TRANSFER_LIST = "b_ft_transfer_list",
I_FT_FILE_UPLOAD_POWER = "i_ft_file_upload_power",
I_FT_NEEDED_FILE_UPLOAD_POWER = "i_ft_needed_file_upload_power",
I_FT_FILE_DOWNLOAD_POWER = "i_ft_file_download_power",
I_FT_NEEDED_FILE_DOWNLOAD_POWER = "i_ft_needed_file_download_power",
I_FT_FILE_DELETE_POWER = "i_ft_file_delete_power",
I_FT_NEEDED_FILE_DELETE_POWER = "i_ft_needed_file_delete_power",
I_FT_FILE_RENAME_POWER = "i_ft_file_rename_power",
I_FT_NEEDED_FILE_RENAME_POWER = "i_ft_needed_file_rename_power",
I_FT_FILE_BROWSE_POWER = "i_ft_file_browse_power",
I_FT_NEEDED_FILE_BROWSE_POWER = "i_ft_needed_file_browse_power",
I_FT_DIRECTORY_CREATE_POWER = "i_ft_directory_create_power",
I_FT_NEEDED_DIRECTORY_CREATE_POWER = "i_ft_needed_directory_create_power",
I_FT_QUOTA_MB_DOWNLOAD_PER_CLIENT = "i_ft_quota_mb_download_per_client",
I_FT_QUOTA_MB_UPLOAD_PER_CLIENT = "i_ft_quota_mb_upload_per_client"
}
class PermissionInfo {
export class PermissionInfo {
name: string;
id: number;
description: string;
@ -363,21 +18,21 @@ class PermissionInfo {
}
}
class PermissionGroup {
export class PermissionGroup {
begin: number;
end: number;
deep: number;
name: string;
}
class GroupedPermissions {
export class GroupedPermissions {
group: PermissionGroup;
permissions: PermissionInfo[];
children: GroupedPermissions[];
parent: GroupedPermissions;
}
class PermissionValue {
export class PermissionValue {
readonly type: PermissionInfo;
value: number;
flag_skip: boolean;
@ -411,13 +66,12 @@ class PermissionValue {
}
}
class NeededPermissionValue extends PermissionValue {
export class NeededPermissionValue extends PermissionValue {
constructor(type, value) {
super(type, value);
}
}
namespace permissions {
export type PermissionRequestKeys = {
client_id?: number;
channel_id?: number;
@ -471,15 +125,15 @@ namespace permissions {
group_id: number;
}
}
}
type RequestLists =
export type RequestLists =
"requests_channel_permissions" |
"requests_client_permissions" |
"requests_client_channel_permissions" |
"requests_playlist_permissions" |
"requests_playlist_client_permissions";
class PermissionManager extends connection.AbstractCommandHandler {
export class PermissionManager extends AbstractCommandHandler {
readonly handle: ConnectionHandler;
permissionList: PermissionInfo[] = [];
@ -488,11 +142,11 @@ class PermissionManager extends connection.AbstractCommandHandler {
needed_permission_change_listener: {[permission: string]:(() => any)[]} = {};
requests_channel_permissions: permissions.PermissionRequest[] = [];
requests_client_permissions: permissions.PermissionRequest[] = [];
requests_client_channel_permissions: permissions.PermissionRequest[] = [];
requests_playlist_permissions: permissions.PermissionRequest[] = [];
requests_playlist_client_permissions: permissions.PermissionRequest[] = [];
requests_channel_permissions: PermissionRequest[] = [];
requests_client_permissions: PermissionRequest[] = [];
requests_client_channel_permissions: PermissionRequest[] = [];
requests_playlist_permissions: PermissionRequest[] = [];
requests_playlist_client_permissions: PermissionRequest[] = [];
requests_permfind: {
timeout_id: number,
@ -603,7 +257,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
this._cacheNeededPermissions = undefined;
}
handle_command(command: connection.ServerCommand): boolean {
handle_command(command: ServerCommand): boolean {
switch (command.command) {
case "notifyclientneededpermissions":
this.onNeededPermissions(command.arguments);
@ -775,7 +429,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
}
private execute_channel_permission_request(request: permissions.PermissionRequestKeys) {
private execute_channel_permission_request(request: PermissionRequestKeys) {
this.handle.serverConnection.send_command("channelpermlist", {"cid": request.channel_id}).catch(error => {
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
this.fullfill_permission_request("requests_channel_permissions", request, "success", []);
@ -785,7 +439,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
}
requestChannelPermissions(channelId: number) : Promise<PermissionValue[]> {
const keys: permissions.PermissionRequestKeys = {
const keys: PermissionRequestKeys = {
channel_id: channelId
};
return this.execute_permission_request("requests_channel_permissions", keys, this.execute_channel_permission_request.bind(this));
@ -799,7 +453,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
}
private execute_client_permission_request(request: permissions.PermissionRequestKeys) {
private execute_client_permission_request(request: PermissionRequestKeys) {
this.handle.serverConnection.send_command("clientpermlist", {cldbid: request.client_id}).catch(error => {
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
this.fullfill_permission_request("requests_client_permissions", request, "success", []);
@ -809,7 +463,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
}
requestClientPermissions(client_id: number) : Promise<PermissionValue[]> {
const keys: permissions.PermissionRequestKeys = {
const keys: PermissionRequestKeys = {
client_id: client_id
};
return this.execute_permission_request("requests_client_permissions", keys, this.execute_client_permission_request.bind(this));
@ -826,7 +480,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
}
private execute_client_channel_permission_request(request: permissions.PermissionRequestKeys) {
private execute_client_channel_permission_request(request: PermissionRequestKeys) {
this.handle.serverConnection.send_command("channelclientpermlist", {cldbid: request.client_id, cid: request.channel_id})
.catch(error => {
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
@ -837,7 +491,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
}
requestClientChannelPermissions(client_id: number, channel_id: number) : Promise<PermissionValue[]> {
const keys: permissions.PermissionRequestKeys = {
const keys: PermissionRequestKeys = {
client_id: client_id
};
return this.execute_permission_request("requests_client_channel_permissions", keys, this.execute_client_channel_permission_request.bind(this));
@ -852,7 +506,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
}
private execute_playlist_permission_request(request: permissions.PermissionRequestKeys) {
private execute_playlist_permission_request(request: PermissionRequestKeys) {
this.handle.serverConnection.send_command("playlistpermlist", {playlist_id: request.playlist_id})
.catch(error => {
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
@ -863,7 +517,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
}
requestPlaylistPermissions(playlist_id: number) : Promise<PermissionValue[]> {
const keys: permissions.PermissionRequestKeys = {
const keys: PermissionRequestKeys = {
playlist_id: playlist_id
};
return this.execute_permission_request("requests_playlist_permissions", keys, this.execute_playlist_permission_request.bind(this));
@ -880,7 +534,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
}
private execute_playlist_client_permission_request(request: permissions.PermissionRequestKeys) {
private execute_playlist_client_permission_request(request: PermissionRequestKeys) {
this.handle.serverConnection.send_command("playlistclientpermlist", {playlist_id: request.playlist_id, cldbid: request.client_id})
.catch(error => {
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
@ -891,7 +545,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
}
requestPlaylistClientPermissions(playlist_id: number, client_database_id: number) : Promise<PermissionValue[]> {
const keys: permissions.PermissionRequestKeys = {
const keys: PermissionRequestKeys = {
playlist_id: playlist_id,
client_id: client_database_id
};
@ -907,8 +561,8 @@ class PermissionManager extends connection.AbstractCommandHandler {
};
private execute_permission_request(list: RequestLists,
criteria: permissions.PermissionRequestKeys,
execute: (criteria: permissions.PermissionRequestKeys) => any) : Promise<PermissionValue[]> {
criteria: PermissionRequestKeys,
execute: (criteria: PermissionRequestKeys) => any) : Promise<PermissionValue[]> {
for(const request of this[list])
if(this.criteria_equal(request, criteria) && request.promise.time() + 1000 < Date.now())
return request.promise;
@ -922,7 +576,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
return result.promise;
};
private fullfill_permission_request(list: RequestLists, criteria: permissions.PermissionRequestKeys, status: "success" | "error", result: any) {
private fullfill_permission_request(list: RequestLists, criteria: PermissionRequestKeys, status: "success" | "error", result: any) {
for(const request of this[list]) {
if(this.criteria_equal(request, criteria)) {
this[list].remove(request);
@ -932,7 +586,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
}
}
find_permission(...permissions: string[]) : Promise<permissions.find.Entry[]> {
find_permission(...permissions: string[]) : Promise<find.Entry[]> {
const permission_ids = [];
for(const permission of permissions) {
const info = this.resolveInfo(permission);
@ -942,11 +596,11 @@ class PermissionManager extends connection.AbstractCommandHandler {
}
if(!permission_ids.length) return Promise.resolve([]);
return new Promise<permissions.find.Entry[]>((resolve, reject) => {
return new Promise<find.Entry[]>((resolve, reject) => {
const single_handler = {
command: "notifypermfind",
function: command => {
const result: permissions.find.Entry[] = [];
const result: find.Entry[] = [];
for(const entry of command.arguments) {
const perm_id = parseInt(entry["p"]);
if(permission_ids.indexOf(perm_id) === -1) return; /* not our permfind result */
@ -960,32 +614,32 @@ class PermissionManager extends connection.AbstractCommandHandler {
data = {
type: "server_group",
group_id: parseInt(entry["id1"]),
} as permissions.find.ServerGroup;
} as find.ServerGroup;
break;
case 1:
data = {
type: "client",
client_id: parseInt(entry["id2"]),
} as permissions.find.Client;
} as find.Client;
break;
case 2:
data = {
type: "channel",
channel_id: parseInt(entry["id2"]),
} as permissions.find.Channel;
} as find.Channel;
break;
case 3:
data = {
type: "channel_group",
group_id: parseInt(entry["id1"]),
} as permissions.find.ChannelGroup;
} as find.ChannelGroup;
break;
case 4:
data = {
type: "client_channel",
client_id: parseInt(entry["id1"]),
channel_id: parseInt(entry["id1"]),
} as permissions.find.ClientChannel;
} as find.ClientChannel;
break;
default:
continue;

View file

@ -0,0 +1,350 @@
export enum PermissionType {
B_SERVERINSTANCE_HELP_VIEW = "b_serverinstance_help_view",
B_SERVERINSTANCE_VERSION_VIEW = "b_serverinstance_version_view",
B_SERVERINSTANCE_INFO_VIEW = "b_serverinstance_info_view",
B_SERVERINSTANCE_VIRTUALSERVER_LIST = "b_serverinstance_virtualserver_list",
B_SERVERINSTANCE_BINDING_LIST = "b_serverinstance_binding_list",
B_SERVERINSTANCE_PERMISSION_LIST = "b_serverinstance_permission_list",
B_SERVERINSTANCE_PERMISSION_FIND = "b_serverinstance_permission_find",
B_VIRTUALSERVER_CREATE = "b_virtualserver_create",
B_VIRTUALSERVER_DELETE = "b_virtualserver_delete",
B_VIRTUALSERVER_START_ANY = "b_virtualserver_start_any",
B_VIRTUALSERVER_STOP_ANY = "b_virtualserver_stop_any",
B_VIRTUALSERVER_CHANGE_MACHINE_ID = "b_virtualserver_change_machine_id",
B_VIRTUALSERVER_CHANGE_TEMPLATE = "b_virtualserver_change_template",
B_SERVERQUERY_LOGIN = "b_serverquery_login",
B_SERVERINSTANCE_TEXTMESSAGE_SEND = "b_serverinstance_textmessage_send",
B_SERVERINSTANCE_LOG_VIEW = "b_serverinstance_log_view",
B_SERVERINSTANCE_LOG_ADD = "b_serverinstance_log_add",
B_SERVERINSTANCE_STOP = "b_serverinstance_stop",
B_SERVERINSTANCE_MODIFY_SETTINGS = "b_serverinstance_modify_settings",
B_SERVERINSTANCE_MODIFY_QUERYGROUP = "b_serverinstance_modify_querygroup",
B_SERVERINSTANCE_MODIFY_TEMPLATES = "b_serverinstance_modify_templates",
B_VIRTUALSERVER_SELECT = "b_virtualserver_select",
B_VIRTUALSERVER_SELECT_GODMODE = "b_virtualserver_select_godmode",
B_VIRTUALSERVER_INFO_VIEW = "b_virtualserver_info_view",
B_VIRTUALSERVER_CONNECTIONINFO_VIEW = "b_virtualserver_connectioninfo_view",
B_VIRTUALSERVER_CHANNEL_LIST = "b_virtualserver_channel_list",
B_VIRTUALSERVER_CHANNEL_SEARCH = "b_virtualserver_channel_search",
B_VIRTUALSERVER_CLIENT_LIST = "b_virtualserver_client_list",
B_VIRTUALSERVER_CLIENT_SEARCH = "b_virtualserver_client_search",
B_VIRTUALSERVER_CLIENT_DBLIST = "b_virtualserver_client_dblist",
B_VIRTUALSERVER_CLIENT_DBSEARCH = "b_virtualserver_client_dbsearch",
B_VIRTUALSERVER_CLIENT_DBINFO = "b_virtualserver_client_dbinfo",
B_VIRTUALSERVER_PERMISSION_FIND = "b_virtualserver_permission_find",
B_VIRTUALSERVER_CUSTOM_SEARCH = "b_virtualserver_custom_search",
B_VIRTUALSERVER_START = "b_virtualserver_start",
B_VIRTUALSERVER_STOP = "b_virtualserver_stop",
B_VIRTUALSERVER_TOKEN_LIST = "b_virtualserver_token_list",
B_VIRTUALSERVER_TOKEN_ADD = "b_virtualserver_token_add",
B_VIRTUALSERVER_TOKEN_USE = "b_virtualserver_token_use",
B_VIRTUALSERVER_TOKEN_DELETE = "b_virtualserver_token_delete",
B_VIRTUALSERVER_LOG_VIEW = "b_virtualserver_log_view",
B_VIRTUALSERVER_LOG_ADD = "b_virtualserver_log_add",
B_VIRTUALSERVER_JOIN_IGNORE_PASSWORD = "b_virtualserver_join_ignore_password",
B_VIRTUALSERVER_NOTIFY_REGISTER = "b_virtualserver_notify_register",
B_VIRTUALSERVER_NOTIFY_UNREGISTER = "b_virtualserver_notify_unregister",
B_VIRTUALSERVER_SNAPSHOT_CREATE = "b_virtualserver_snapshot_create",
B_VIRTUALSERVER_SNAPSHOT_DEPLOY = "b_virtualserver_snapshot_deploy",
B_VIRTUALSERVER_PERMISSION_RESET = "b_virtualserver_permission_reset",
B_VIRTUALSERVER_MODIFY_NAME = "b_virtualserver_modify_name",
B_VIRTUALSERVER_MODIFY_WELCOMEMESSAGE = "b_virtualserver_modify_welcomemessage",
B_VIRTUALSERVER_MODIFY_MAXCLIENTS = "b_virtualserver_modify_maxclients",
B_VIRTUALSERVER_MODIFY_RESERVED_SLOTS = "b_virtualserver_modify_reserved_slots",
B_VIRTUALSERVER_MODIFY_PASSWORD = "b_virtualserver_modify_password",
B_VIRTUALSERVER_MODIFY_DEFAULT_SERVERGROUP = "b_virtualserver_modify_default_servergroup",
B_VIRTUALSERVER_MODIFY_DEFAULT_MUSICGROUP = "b_virtualserver_modify_default_musicgroup",
B_VIRTUALSERVER_MODIFY_DEFAULT_CHANNELGROUP = "b_virtualserver_modify_default_channelgroup",
B_VIRTUALSERVER_MODIFY_DEFAULT_CHANNELADMINGROUP = "b_virtualserver_modify_default_channeladmingroup",
B_VIRTUALSERVER_MODIFY_CHANNEL_FORCED_SILENCE = "b_virtualserver_modify_channel_forced_silence",
B_VIRTUALSERVER_MODIFY_COMPLAIN = "b_virtualserver_modify_complain",
B_VIRTUALSERVER_MODIFY_ANTIFLOOD = "b_virtualserver_modify_antiflood",
B_VIRTUALSERVER_MODIFY_FT_SETTINGS = "b_virtualserver_modify_ft_settings",
B_VIRTUALSERVER_MODIFY_FT_QUOTAS = "b_virtualserver_modify_ft_quotas",
B_VIRTUALSERVER_MODIFY_HOSTMESSAGE = "b_virtualserver_modify_hostmessage",
B_VIRTUALSERVER_MODIFY_HOSTBANNER = "b_virtualserver_modify_hostbanner",
B_VIRTUALSERVER_MODIFY_HOSTBUTTON = "b_virtualserver_modify_hostbutton",
B_VIRTUALSERVER_MODIFY_PORT = "b_virtualserver_modify_port",
B_VIRTUALSERVER_MODIFY_HOST = "b_virtualserver_modify_host",
B_VIRTUALSERVER_MODIFY_DEFAULT_MESSAGES = "b_virtualserver_modify_default_messages",
B_VIRTUALSERVER_MODIFY_AUTOSTART = "b_virtualserver_modify_autostart",
B_VIRTUALSERVER_MODIFY_NEEDED_IDENTITY_SECURITY_LEVEL = "b_virtualserver_modify_needed_identity_security_level",
B_VIRTUALSERVER_MODIFY_PRIORITY_SPEAKER_DIMM_MODIFICATOR = "b_virtualserver_modify_priority_speaker_dimm_modificator",
B_VIRTUALSERVER_MODIFY_LOG_SETTINGS = "b_virtualserver_modify_log_settings",
B_VIRTUALSERVER_MODIFY_MIN_CLIENT_VERSION = "b_virtualserver_modify_min_client_version",
B_VIRTUALSERVER_MODIFY_ICON_ID = "b_virtualserver_modify_icon_id",
B_VIRTUALSERVER_MODIFY_WEBLIST = "b_virtualserver_modify_weblist",
B_VIRTUALSERVER_MODIFY_CODEC_ENCRYPTION_MODE = "b_virtualserver_modify_codec_encryption_mode",
B_VIRTUALSERVER_MODIFY_TEMPORARY_PASSWORDS = "b_virtualserver_modify_temporary_passwords",
B_VIRTUALSERVER_MODIFY_TEMPORARY_PASSWORDS_OWN = "b_virtualserver_modify_temporary_passwords_own",
B_VIRTUALSERVER_MODIFY_CHANNEL_TEMP_DELETE_DELAY_DEFAULT = "b_virtualserver_modify_channel_temp_delete_delay_default",
B_VIRTUALSERVER_MODIFY_MUSIC_BOT_LIMIT = "b_virtualserver_modify_music_bot_limit",
B_VIRTUALSERVER_MODIFY_COUNTRY_CODE = "b_virtualserver_modify_country_code",
I_CHANNEL_MIN_DEPTH = "i_channel_min_depth",
I_CHANNEL_MAX_DEPTH = "i_channel_max_depth",
B_CHANNEL_GROUP_INHERITANCE_END = "b_channel_group_inheritance_end",
I_CHANNEL_PERMISSION_MODIFY_POWER = "i_channel_permission_modify_power",
I_CHANNEL_NEEDED_PERMISSION_MODIFY_POWER = "i_channel_needed_permission_modify_power",
B_CHANNEL_INFO_VIEW = "b_channel_info_view",
B_CHANNEL_CREATE_CHILD = "b_channel_create_child",
B_CHANNEL_CREATE_PERMANENT = "b_channel_create_permanent",
B_CHANNEL_CREATE_SEMI_PERMANENT = "b_channel_create_semi_permanent",
B_CHANNEL_CREATE_TEMPORARY = "b_channel_create_temporary",
B_CHANNEL_CREATE_PRIVATE = "b_channel_create_private",
B_CHANNEL_CREATE_WITH_TOPIC = "b_channel_create_with_topic",
B_CHANNEL_CREATE_WITH_DESCRIPTION = "b_channel_create_with_description",
B_CHANNEL_CREATE_WITH_PASSWORD = "b_channel_create_with_password",
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX8 = "b_channel_create_modify_with_codec_speex8",
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX16 = "b_channel_create_modify_with_codec_speex16",
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX32 = "b_channel_create_modify_with_codec_speex32",
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_CELTMONO48 = "b_channel_create_modify_with_codec_celtmono48",
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSVOICE = "b_channel_create_modify_with_codec_opusvoice",
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSMUSIC = "b_channel_create_modify_with_codec_opusmusic",
I_CHANNEL_CREATE_MODIFY_WITH_CODEC_MAXQUALITY = "i_channel_create_modify_with_codec_maxquality",
I_CHANNEL_CREATE_MODIFY_WITH_CODEC_LATENCY_FACTOR_MIN = "i_channel_create_modify_with_codec_latency_factor_min",
I_CHANNEL_CREATE_MODIFY_CONVERSATION_HISTORY_LENGTH = "i_channel_create_modify_conversation_history_length",
B_CHANNEL_CREATE_MODIFY_CONVERSATION_HISTORY_UNLIMITED = "b_channel_create_modify_conversation_history_unlimited",
B_CHANNEL_CREATE_MODIFY_CONVERSATION_PRIVATE = "b_channel_create_modify_conversation_private",
B_CHANNEL_CREATE_WITH_MAXCLIENTS = "b_channel_create_with_maxclients",
B_CHANNEL_CREATE_WITH_MAXFAMILYCLIENTS = "b_channel_create_with_maxfamilyclients",
B_CHANNEL_CREATE_WITH_SORTORDER = "b_channel_create_with_sortorder",
B_CHANNEL_CREATE_WITH_DEFAULT = "b_channel_create_with_default",
B_CHANNEL_CREATE_WITH_NEEDED_TALK_POWER = "b_channel_create_with_needed_talk_power",
B_CHANNEL_CREATE_MODIFY_WITH_FORCE_PASSWORD = "b_channel_create_modify_with_force_password",
I_CHANNEL_CREATE_MODIFY_WITH_TEMP_DELETE_DELAY = "i_channel_create_modify_with_temp_delete_delay",
B_CHANNEL_MODIFY_PARENT = "b_channel_modify_parent",
B_CHANNEL_MODIFY_MAKE_DEFAULT = "b_channel_modify_make_default",
B_CHANNEL_MODIFY_MAKE_PERMANENT = "b_channel_modify_make_permanent",
B_CHANNEL_MODIFY_MAKE_SEMI_PERMANENT = "b_channel_modify_make_semi_permanent",
B_CHANNEL_MODIFY_MAKE_TEMPORARY = "b_channel_modify_make_temporary",
B_CHANNEL_MODIFY_NAME = "b_channel_modify_name",
B_CHANNEL_MODIFY_TOPIC = "b_channel_modify_topic",
B_CHANNEL_MODIFY_DESCRIPTION = "b_channel_modify_description",
B_CHANNEL_MODIFY_PASSWORD = "b_channel_modify_password",
B_CHANNEL_MODIFY_CODEC = "b_channel_modify_codec",
B_CHANNEL_MODIFY_CODEC_QUALITY = "b_channel_modify_codec_quality",
B_CHANNEL_MODIFY_CODEC_LATENCY_FACTOR = "b_channel_modify_codec_latency_factor",
B_CHANNEL_MODIFY_MAXCLIENTS = "b_channel_modify_maxclients",
B_CHANNEL_MODIFY_MAXFAMILYCLIENTS = "b_channel_modify_maxfamilyclients",
B_CHANNEL_MODIFY_SORTORDER = "b_channel_modify_sortorder",
B_CHANNEL_MODIFY_NEEDED_TALK_POWER = "b_channel_modify_needed_talk_power",
I_CHANNEL_MODIFY_POWER = "i_channel_modify_power",
I_CHANNEL_NEEDED_MODIFY_POWER = "i_channel_needed_modify_power",
B_CHANNEL_MODIFY_MAKE_CODEC_ENCRYPTED = "b_channel_modify_make_codec_encrypted",
B_CHANNEL_MODIFY_TEMP_DELETE_DELAY = "b_channel_modify_temp_delete_delay",
B_CHANNEL_DELETE_PERMANENT = "b_channel_delete_permanent",
B_CHANNEL_DELETE_SEMI_PERMANENT = "b_channel_delete_semi_permanent",
B_CHANNEL_DELETE_TEMPORARY = "b_channel_delete_temporary",
B_CHANNEL_DELETE_FLAG_FORCE = "b_channel_delete_flag_force",
I_CHANNEL_DELETE_POWER = "i_channel_delete_power",
B_CHANNEL_CONVERSATION_MESSAGE_DELETE = "b_channel_conversation_message_delete",
I_CHANNEL_NEEDED_DELETE_POWER = "i_channel_needed_delete_power",
B_CHANNEL_JOIN_PERMANENT = "b_channel_join_permanent",
B_CHANNEL_JOIN_SEMI_PERMANENT = "b_channel_join_semi_permanent",
B_CHANNEL_JOIN_TEMPORARY = "b_channel_join_temporary",
B_CHANNEL_JOIN_IGNORE_PASSWORD = "b_channel_join_ignore_password",
B_CHANNEL_JOIN_IGNORE_MAXCLIENTS = "b_channel_join_ignore_maxclients",
B_CHANNEL_IGNORE_VIEW_POWER = "b_channel_ignore_view_power",
I_CHANNEL_JOIN_POWER = "i_channel_join_power",
I_CHANNEL_NEEDED_JOIN_POWER = "i_channel_needed_join_power",
B_CHANNEL_IGNORE_JOIN_POWER = "b_channel_ignore_join_power",
B_CHANNEL_IGNORE_DESCRIPTION_VIEW_POWER = "b_channel_ignore_description_view_power",
I_CHANNEL_VIEW_POWER = "i_channel_view_power",
I_CHANNEL_NEEDED_VIEW_POWER = "i_channel_needed_view_power",
I_CHANNEL_SUBSCRIBE_POWER = "i_channel_subscribe_power",
I_CHANNEL_NEEDED_SUBSCRIBE_POWER = "i_channel_needed_subscribe_power",
I_CHANNEL_DESCRIPTION_VIEW_POWER = "i_channel_description_view_power",
I_CHANNEL_NEEDED_DESCRIPTION_VIEW_POWER = "i_channel_needed_description_view_power",
I_ICON_ID = "i_icon_id",
I_MAX_ICON_FILESIZE = "i_max_icon_filesize",
I_MAX_PLAYLIST_SIZE = "i_max_playlist_size",
I_MAX_PLAYLISTS = "i_max_playlists",
B_ICON_MANAGE = "b_icon_manage",
B_GROUP_IS_PERMANENT = "b_group_is_permanent",
I_GROUP_AUTO_UPDATE_TYPE = "i_group_auto_update_type",
I_GROUP_AUTO_UPDATE_MAX_VALUE = "i_group_auto_update_max_value",
I_GROUP_SORT_ID = "i_group_sort_id",
I_GROUP_SHOW_NAME_IN_TREE = "i_group_show_name_in_tree",
B_VIRTUALSERVER_SERVERGROUP_CREATE = "b_virtualserver_servergroup_create",
B_VIRTUALSERVER_SERVERGROUP_LIST = "b_virtualserver_servergroup_list",
B_VIRTUALSERVER_SERVERGROUP_PERMISSION_LIST = "b_virtualserver_servergroup_permission_list",
B_VIRTUALSERVER_SERVERGROUP_CLIENT_LIST = "b_virtualserver_servergroup_client_list",
B_VIRTUALSERVER_CHANNELGROUP_CREATE = "b_virtualserver_channelgroup_create",
B_VIRTUALSERVER_CHANNELGROUP_LIST = "b_virtualserver_channelgroup_list",
B_VIRTUALSERVER_CHANNELGROUP_PERMISSION_LIST = "b_virtualserver_channelgroup_permission_list",
B_VIRTUALSERVER_CHANNELGROUP_CLIENT_LIST = "b_virtualserver_channelgroup_client_list",
B_VIRTUALSERVER_CLIENT_PERMISSION_LIST = "b_virtualserver_client_permission_list",
B_VIRTUALSERVER_CHANNEL_PERMISSION_LIST = "b_virtualserver_channel_permission_list",
B_VIRTUALSERVER_CHANNELCLIENT_PERMISSION_LIST = "b_virtualserver_channelclient_permission_list",
B_VIRTUALSERVER_PLAYLIST_PERMISSION_LIST = "b_virtualserver_playlist_permission_list",
I_SERVER_GROUP_MODIFY_POWER = "i_server_group_modify_power",
I_SERVER_GROUP_NEEDED_MODIFY_POWER = "i_server_group_needed_modify_power",
I_SERVER_GROUP_MEMBER_ADD_POWER = "i_server_group_member_add_power",
I_SERVER_GROUP_SELF_ADD_POWER = "i_server_group_self_add_power",
I_SERVER_GROUP_NEEDED_MEMBER_ADD_POWER = "i_server_group_needed_member_add_power",
I_SERVER_GROUP_MEMBER_REMOVE_POWER = "i_server_group_member_remove_power",
I_SERVER_GROUP_SELF_REMOVE_POWER = "i_server_group_self_remove_power",
I_SERVER_GROUP_NEEDED_MEMBER_REMOVE_POWER = "i_server_group_needed_member_remove_power",
I_CHANNEL_GROUP_MODIFY_POWER = "i_channel_group_modify_power",
I_CHANNEL_GROUP_NEEDED_MODIFY_POWER = "i_channel_group_needed_modify_power",
I_CHANNEL_GROUP_MEMBER_ADD_POWER = "i_channel_group_member_add_power",
I_CHANNEL_GROUP_SELF_ADD_POWER = "i_channel_group_self_add_power",
I_CHANNEL_GROUP_NEEDED_MEMBER_ADD_POWER = "i_channel_group_needed_member_add_power",
I_CHANNEL_GROUP_MEMBER_REMOVE_POWER = "i_channel_group_member_remove_power",
I_CHANNEL_GROUP_SELF_REMOVE_POWER = "i_channel_group_self_remove_power",
I_CHANNEL_GROUP_NEEDED_MEMBER_REMOVE_POWER = "i_channel_group_needed_member_remove_power",
I_GROUP_MEMBER_ADD_POWER = "i_group_member_add_power",
I_GROUP_NEEDED_MEMBER_ADD_POWER = "i_group_needed_member_add_power",
I_GROUP_MEMBER_REMOVE_POWER = "i_group_member_remove_power",
I_GROUP_NEEDED_MEMBER_REMOVE_POWER = "i_group_needed_member_remove_power",
I_GROUP_MODIFY_POWER = "i_group_modify_power",
I_GROUP_NEEDED_MODIFY_POWER = "i_group_needed_modify_power",
I_PERMISSION_MODIFY_POWER = "i_permission_modify_power",
B_PERMISSION_MODIFY_POWER_IGNORE = "b_permission_modify_power_ignore",
B_VIRTUALSERVER_SERVERGROUP_DELETE = "b_virtualserver_servergroup_delete",
B_VIRTUALSERVER_CHANNELGROUP_DELETE = "b_virtualserver_channelgroup_delete",
I_CLIENT_PERMISSION_MODIFY_POWER = "i_client_permission_modify_power",
I_CLIENT_NEEDED_PERMISSION_MODIFY_POWER = "i_client_needed_permission_modify_power",
I_CLIENT_MAX_CLONES_UID = "i_client_max_clones_uid",
I_CLIENT_MAX_CLONES_IP = "i_client_max_clones_ip",
I_CLIENT_MAX_CLONES_HWID = "i_client_max_clones_hwid",
I_CLIENT_MAX_IDLETIME = "i_client_max_idletime",
I_CLIENT_MAX_AVATAR_FILESIZE = "i_client_max_avatar_filesize",
I_CLIENT_MAX_CHANNEL_SUBSCRIPTIONS = "i_client_max_channel_subscriptions",
I_CLIENT_MAX_CHANNELS = "i_client_max_channels",
I_CLIENT_MAX_TEMPORARY_CHANNELS = "i_client_max_temporary_channels",
I_CLIENT_MAX_SEMI_CHANNELS = "i_client_max_semi_channels",
I_CLIENT_MAX_PERMANENT_CHANNELS = "i_client_max_permanent_channels",
B_CLIENT_USE_PRIORITY_SPEAKER = "b_client_use_priority_speaker",
B_CLIENT_SKIP_CHANNELGROUP_PERMISSIONS = "b_client_skip_channelgroup_permissions",
B_CLIENT_FORCE_PUSH_TO_TALK = "b_client_force_push_to_talk",
B_CLIENT_IGNORE_BANS = "b_client_ignore_bans",
B_CLIENT_IGNORE_VPN = "b_client_ignore_vpn",
B_CLIENT_IGNORE_ANTIFLOOD = "b_client_ignore_antiflood",
B_CLIENT_ENFORCE_VALID_HWID = "b_client_enforce_valid_hwid",
B_CLIENT_ALLOW_INVALID_PACKET = "b_client_allow_invalid_packet",
B_CLIENT_ALLOW_INVALID_BADGES = "b_client_allow_invalid_badges",
B_CLIENT_ISSUE_CLIENT_QUERY_COMMAND = "b_client_issue_client_query_command",
B_CLIENT_USE_RESERVED_SLOT = "b_client_use_reserved_slot",
B_CLIENT_USE_CHANNEL_COMMANDER = "b_client_use_channel_commander",
B_CLIENT_REQUEST_TALKER = "b_client_request_talker",
B_CLIENT_AVATAR_DELETE_OTHER = "b_client_avatar_delete_other",
B_CLIENT_IS_STICKY = "b_client_is_sticky",
B_CLIENT_IGNORE_STICKY = "b_client_ignore_sticky",
B_CLIENT_MUSIC_CREATE_PERMANENT = "b_client_music_create_permanent",
B_CLIENT_MUSIC_CREATE_SEMI_PERMANENT = "b_client_music_create_semi_permanent",
B_CLIENT_MUSIC_CREATE_TEMPORARY = "b_client_music_create_temporary",
B_CLIENT_MUSIC_MODIFY_PERMANENT = "b_client_music_modify_permanent",
B_CLIENT_MUSIC_MODIFY_SEMI_PERMANENT = "b_client_music_modify_semi_permanent",
B_CLIENT_MUSIC_MODIFY_TEMPORARY = "b_client_music_modify_temporary",
I_CLIENT_MUSIC_CREATE_MODIFY_MAX_VOLUME = "i_client_music_create_modify_max_volume",
I_CLIENT_MUSIC_LIMIT = "i_client_music_limit",
I_CLIENT_MUSIC_NEEDED_DELETE_POWER = "i_client_music_needed_delete_power",
I_CLIENT_MUSIC_DELETE_POWER = "i_client_music_delete_power",
I_CLIENT_MUSIC_PLAY_POWER = "i_client_music_play_power",
I_CLIENT_MUSIC_NEEDED_PLAY_POWER = "i_client_music_needed_play_power",
I_CLIENT_MUSIC_MODIFY_POWER = "i_client_music_modify_power",
I_CLIENT_MUSIC_NEEDED_MODIFY_POWER = "i_client_music_needed_modify_power",
I_CLIENT_MUSIC_RENAME_POWER = "i_client_music_rename_power",
I_CLIENT_MUSIC_NEEDED_RENAME_POWER = "i_client_music_needed_rename_power",
B_PLAYLIST_CREATE = "b_playlist_create",
I_PLAYLIST_VIEW_POWER = "i_playlist_view_power",
I_PLAYLIST_NEEDED_VIEW_POWER = "i_playlist_needed_view_power",
I_PLAYLIST_MODIFY_POWER = "i_playlist_modify_power",
I_PLAYLIST_NEEDED_MODIFY_POWER = "i_playlist_needed_modify_power",
I_PLAYLIST_PERMISSION_MODIFY_POWER = "i_playlist_permission_modify_power",
I_PLAYLIST_NEEDED_PERMISSION_MODIFY_POWER = "i_playlist_needed_permission_modify_power",
I_PLAYLIST_DELETE_POWER = "i_playlist_delete_power",
I_PLAYLIST_NEEDED_DELETE_POWER = "i_playlist_needed_delete_power",
I_PLAYLIST_SONG_ADD_POWER = "i_playlist_song_add_power",
I_PLAYLIST_SONG_NEEDED_ADD_POWER = "i_playlist_song_needed_add_power",
I_PLAYLIST_SONG_REMOVE_POWER = "i_playlist_song_remove_power",
I_PLAYLIST_SONG_NEEDED_REMOVE_POWER = "i_playlist_song_needed_remove_power",
B_CLIENT_INFO_VIEW = "b_client_info_view",
B_CLIENT_PERMISSIONOVERVIEW_VIEW = "b_client_permissionoverview_view",
B_CLIENT_PERMISSIONOVERVIEW_OWN = "b_client_permissionoverview_own",
B_CLIENT_REMOTEADDRESS_VIEW = "b_client_remoteaddress_view",
I_CLIENT_SERVERQUERY_VIEW_POWER = "i_client_serverquery_view_power",
I_CLIENT_NEEDED_SERVERQUERY_VIEW_POWER = "i_client_needed_serverquery_view_power",
B_CLIENT_CUSTOM_INFO_VIEW = "b_client_custom_info_view",
B_CLIENT_MUSIC_CHANNEL_LIST = "b_client_music_channel_list",
B_CLIENT_MUSIC_SERVER_LIST = "b_client_music_server_list",
I_CLIENT_MUSIC_INFO = "i_client_music_info",
I_CLIENT_MUSIC_NEEDED_INFO = "i_client_music_needed_info",
I_CLIENT_KICK_FROM_SERVER_POWER = "i_client_kick_from_server_power",
I_CLIENT_NEEDED_KICK_FROM_SERVER_POWER = "i_client_needed_kick_from_server_power",
I_CLIENT_KICK_FROM_CHANNEL_POWER = "i_client_kick_from_channel_power",
I_CLIENT_NEEDED_KICK_FROM_CHANNEL_POWER = "i_client_needed_kick_from_channel_power",
I_CLIENT_BAN_POWER = "i_client_ban_power",
I_CLIENT_NEEDED_BAN_POWER = "i_client_needed_ban_power",
I_CLIENT_MOVE_POWER = "i_client_move_power",
I_CLIENT_NEEDED_MOVE_POWER = "i_client_needed_move_power",
I_CLIENT_COMPLAIN_POWER = "i_client_complain_power",
I_CLIENT_NEEDED_COMPLAIN_POWER = "i_client_needed_complain_power",
B_CLIENT_COMPLAIN_LIST = "b_client_complain_list",
B_CLIENT_COMPLAIN_DELETE_OWN = "b_client_complain_delete_own",
B_CLIENT_COMPLAIN_DELETE = "b_client_complain_delete",
B_CLIENT_BAN_LIST = "b_client_ban_list",
B_CLIENT_BAN_LIST_GLOBAL = "b_client_ban_list_global",
B_CLIENT_BAN_TRIGGER_LIST = "b_client_ban_trigger_list",
B_CLIENT_BAN_CREATE = "b_client_ban_create",
B_CLIENT_BAN_CREATE_GLOBAL = "b_client_ban_create_global",
B_CLIENT_BAN_NAME = "b_client_ban_name",
B_CLIENT_BAN_IP = "b_client_ban_ip",
B_CLIENT_BAN_HWID = "b_client_ban_hwid",
B_CLIENT_BAN_EDIT = "b_client_ban_edit",
B_CLIENT_BAN_EDIT_GLOBAL = "b_client_ban_edit_global",
B_CLIENT_BAN_DELETE_OWN = "b_client_ban_delete_own",
B_CLIENT_BAN_DELETE = "b_client_ban_delete",
B_CLIENT_BAN_DELETE_OWN_GLOBAL = "b_client_ban_delete_own_global",
B_CLIENT_BAN_DELETE_GLOBAL = "b_client_ban_delete_global",
I_CLIENT_BAN_MAX_BANTIME = "i_client_ban_max_bantime",
I_CLIENT_PRIVATE_TEXTMESSAGE_POWER = "i_client_private_textmessage_power",
I_CLIENT_NEEDED_PRIVATE_TEXTMESSAGE_POWER = "i_client_needed_private_textmessage_power",
B_CLIENT_EVEN_TEXTMESSAGE_SEND = "b_client_even_textmessage_send",
B_CLIENT_SERVER_TEXTMESSAGE_SEND = "b_client_server_textmessage_send",
B_CLIENT_CHANNEL_TEXTMESSAGE_SEND = "b_client_channel_textmessage_send",
B_CLIENT_OFFLINE_TEXTMESSAGE_SEND = "b_client_offline_textmessage_send",
I_CLIENT_TALK_POWER = "i_client_talk_power",
I_CLIENT_NEEDED_TALK_POWER = "i_client_needed_talk_power",
I_CLIENT_POKE_POWER = "i_client_poke_power",
I_CLIENT_NEEDED_POKE_POWER = "i_client_needed_poke_power",
B_CLIENT_SET_FLAG_TALKER = "b_client_set_flag_talker",
I_CLIENT_WHISPER_POWER = "i_client_whisper_power",
I_CLIENT_NEEDED_WHISPER_POWER = "i_client_needed_whisper_power",
B_CLIENT_MODIFY_DESCRIPTION = "b_client_modify_description",
B_CLIENT_MODIFY_OWN_DESCRIPTION = "b_client_modify_own_description",
B_CLIENT_USE_BBCODE_ANY = "b_client_use_bbcode_any",
B_CLIENT_USE_BBCODE_URL = "b_client_use_bbcode_url",
B_CLIENT_USE_BBCODE_IMAGE = "b_client_use_bbcode_image",
B_CLIENT_MODIFY_DBPROPERTIES = "b_client_modify_dbproperties",
B_CLIENT_DELETE_DBPROPERTIES = "b_client_delete_dbproperties",
B_CLIENT_CREATE_MODIFY_SERVERQUERY_LOGIN = "b_client_create_modify_serverquery_login",
B_CLIENT_QUERY_CREATE = "b_client_query_create",
B_CLIENT_QUERY_LIST = "b_client_query_list",
B_CLIENT_QUERY_LIST_OWN = "b_client_query_list_own",
B_CLIENT_QUERY_RENAME = "b_client_query_rename",
B_CLIENT_QUERY_RENAME_OWN = "b_client_query_rename_own",
B_CLIENT_QUERY_CHANGE_PASSWORD = "b_client_query_change_password",
B_CLIENT_QUERY_CHANGE_OWN_PASSWORD = "b_client_query_change_own_password",
B_CLIENT_QUERY_CHANGE_PASSWORD_GLOBAL = "b_client_query_change_password_global",
B_CLIENT_QUERY_DELETE = "b_client_query_delete",
B_CLIENT_QUERY_DELETE_OWN = "b_client_query_delete_own",
B_FT_IGNORE_PASSWORD = "b_ft_ignore_password",
B_FT_TRANSFER_LIST = "b_ft_transfer_list",
I_FT_FILE_UPLOAD_POWER = "i_ft_file_upload_power",
I_FT_NEEDED_FILE_UPLOAD_POWER = "i_ft_needed_file_upload_power",
I_FT_FILE_DOWNLOAD_POWER = "i_ft_file_download_power",
I_FT_NEEDED_FILE_DOWNLOAD_POWER = "i_ft_needed_file_download_power",
I_FT_FILE_DELETE_POWER = "i_ft_file_delete_power",
I_FT_NEEDED_FILE_DELETE_POWER = "i_ft_needed_file_delete_power",
I_FT_FILE_RENAME_POWER = "i_ft_file_rename_power",
I_FT_NEEDED_FILE_RENAME_POWER = "i_ft_needed_file_rename_power",
I_FT_FILE_BROWSE_POWER = "i_ft_file_browse_power",
I_FT_NEEDED_FILE_BROWSE_POWER = "i_ft_needed_file_browse_power",
I_FT_DIRECTORY_CREATE_POWER = "i_ft_directory_create_power",
I_FT_NEEDED_DIRECTORY_CREATE_POWER = "i_ft_needed_directory_create_power",
I_FT_QUOTA_MB_DOWNLOAD_PER_CLIENT = "i_ft_quota_mb_download_per_client",
I_FT_QUOTA_MB_UPLOAD_PER_CLIENT = "i_ft_quota_mb_upload_per_client"
}
export default PermissionType;

View file

@ -1,4 +1,12 @@
namespace profiles {
import {decode_identity, IdentitifyType, Identity} from "tc-shared/profiles/Identity";
import {guid} from "tc-shared/crypto/uid";
import {TeaForumIdentity} from "tc-shared/profiles/identities/TeaForumIdentity";
import {TeaSpeakIdentity} from "tc-shared/profiles/identities/TeamSpeakIdentity";
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
import {HandshakeIdentityHandler} from "tc-shared/connection/HandshakeHandler";
import {createErrorModal} from "tc-shared/ui/elements/Modal";
import {formatMessage} from "tc-shared/ui/frames/chat";
export class ConnectionProfile {
id: string;
@ -7,7 +15,7 @@ namespace profiles {
default_password: string;
selected_identity_type: string = "unset";
identities: { [key: string]: identities.Identity } = {};
identities: { [key: string]: Identity } = {};
constructor(id: string) {
this.id = id;
@ -22,31 +30,31 @@ namespace profiles {
return name || "Another TeaSpeak user";
}
selected_identity(current_type?: identities.IdentitifyType): identities.Identity {
selected_identity(current_type?: IdentitifyType): Identity {
if (!current_type)
current_type = this.selected_type();
if (current_type === undefined)
return undefined;
if (current_type == identities.IdentitifyType.TEAFORO) {
return identities.static_forum_identity();
} else if (current_type == identities.IdentitifyType.TEAMSPEAK || current_type == identities.IdentitifyType.NICKNAME) {
return this.identities[identities.IdentitifyType[current_type].toLowerCase()];
if (current_type == IdentitifyType.TEAFORO) {
return TeaForumIdentity.identity();
} else if (current_type == IdentitifyType.TEAMSPEAK || current_type == IdentitifyType.NICKNAME) {
return this.identities[IdentitifyType[current_type].toLowerCase()];
}
return undefined;
}
selected_type?(): identities.IdentitifyType {
return this.selected_identity_type ? identities.IdentitifyType[this.selected_identity_type.toUpperCase()] : undefined;
selected_type?(): IdentitifyType {
return this.selected_identity_type ? IdentitifyType[this.selected_identity_type.toUpperCase()] : undefined;
}
set_identity(type: identities.IdentitifyType, identity: identities.Identity) {
this.identities[identities.IdentitifyType[type].toLowerCase()] = identity;
set_identity(type: IdentitifyType, identity: Identity) {
this.identities[IdentitifyType[type].toLowerCase()] = identity;
}
spawn_identity_handshake_handler?(connection: connection.AbstractServerConnection): connection.HandshakeIdentityHandler {
spawn_identity_handshake_handler?(connection: AbstractServerConnection): HandshakeIdentityHandler {
const identity = this.selected_identity();
if (!identity)
return undefined;
@ -72,9 +80,8 @@ namespace profiles {
valid(): boolean {
const identity = this.selected_identity();
if (!identity || !identity.valid()) return false;
return true;
return !!identity && identity.valid();
}
}
@ -90,12 +97,12 @@ namespace profiles {
result.selected_identity_type = (data.identity_type || "").toLowerCase();
if (data.identity_data) {
for (const key in data.identity_data) {
const type = identities.IdentitifyType[key.toUpperCase() as string];
for (const key of Object.keys(data.identity_data)) {
const type = IdentitifyType[key.toUpperCase() as string];
const _data = data.identity_data[key];
if (type == undefined) continue;
const identity = await identities.decode_identity(type, _data);
const identity = await decode_identity(type, _data);
if (identity == undefined) continue;
result.identities[key.toLowerCase()] = identity;
@ -122,7 +129,7 @@ namespace profiles {
} catch (error) {
debugger;
console.error(tr("Invalid profile json! Resetting profiles :( (%o)"), profiles_json);
createErrorModal(tr("Profile data invalid"), MessageHelper.formatMessage(tr("The profile data is invalid.{:br:}This might cause data loss."))).open();
createErrorModal(tr("Profile data invalid"), formatMessage(tr("The profile data is invalid.{:br:}This might cause data loss."))).open();
return {version: 0};
}
})();
@ -153,14 +160,14 @@ namespace profiles {
/* generate default identity */
try {
const identity = await identities.TeaSpeakIdentity.generate_new();
const identity = await TeaSpeakIdentity.generate_new();
let active = true;
setTimeout(() => {
active = false;
}, 1000);
await identity.improve_level(8, 1, () => active);
profile.set_identity(identities.IdentitifyType.TEAMSPEAK, identity);
profile.selected_identity_type = identities.IdentitifyType[identities.IdentitifyType.TEAMSPEAK];
profile.set_identity(IdentitifyType.TEAMSPEAK, identity);
profile.selected_identity_type = IdentitifyType[IdentitifyType.TEAMSPEAK];
} catch (error) {
createErrorModal(tr("Failed to generate default identity"), tr("Failed to generate default identity!<br>Please manually generate the identity within your settings => profiles")).open();
}
@ -172,8 +179,8 @@ namespace profiles {
profile.default_username = "";
profile.profile_name = "TeaSpeak Forum profile";
profile.set_identity(identities.IdentitifyType.TEAFORO, identities.static_forum_identity());
profile.selected_identity_type = identities.IdentitifyType[identities.IdentitifyType.TEAFORO];
profile.set_identity(IdentitifyType.TEAFORO, TeaForumIdentity.identity());
profile.selected_identity_type = IdentitifyType[IdentitifyType.TEAFORO];
}
save();
@ -248,4 +255,3 @@ namespace profiles {
export function delete_profile(profile: ConnectionProfile) {
available_profiles.remove(profile);
}
}

View file

@ -1,4 +1,7 @@
namespace profiles.identities {
import {AbstractServerConnection, ServerCommand} from "tc-shared/connection/ConnectionBase";
import {HandshakeIdentityHandler} from "tc-shared/connection/HandshakeHandler";
import {AbstractCommandHandler} from "tc-shared/connection/AbstractCommandHandler";
export enum IdentitifyType {
TEAFORO,
TEAMSPEAK,
@ -15,20 +18,24 @@ namespace profiles.identities {
encode?() : string;
decode(data: string) : Promise<void>;
spawn_identity_handshake_handler(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler;
spawn_identity_handshake_handler(connection: AbstractServerConnection) : HandshakeIdentityHandler;
}
/* avoid circular dependencies here */
export async function decode_identity(type: IdentitifyType, data: string) : Promise<Identity> {
let identity: Identity;
switch (type) {
case IdentitifyType.NICKNAME:
identity = new NameIdentity();
const nidentity = require("tc-shared/profiles/identities/NameIdentity");
identity = new nidentity.NameIdentity();
break;
case IdentitifyType.TEAFORO:
identity = new TeaForumIdentity(undefined);
const fidentity = require("tc-shared/profiles/identities/TeaForumIdentity");
identity = new fidentity.TeaForumIdentity(undefined);
break;
case IdentitifyType.TEAMSPEAK:
identity = new TeaSpeakIdentity(undefined, undefined);
const tidentity = require("tc-shared/profiles/identities/TeamSpeakIdentity");
identity = new tidentity.TeaSpeakIdentity(undefined, undefined);
break;
}
if(!identity)
@ -49,28 +56,31 @@ namespace profiles.identities {
let identity: Identity;
switch (type) {
case IdentitifyType.NICKNAME:
identity = new NameIdentity();
const nidentity = require("tc-shared/profiles/identities/NameIdentity");
identity = new nidentity.NameIdentity();
break;
case IdentitifyType.TEAFORO:
identity = new TeaForumIdentity(undefined);
const fidentity = require("tc-shared/profiles/identities/TeaForumIdentity");
identity = new fidentity.TeaForumIdentity(undefined);
break;
case IdentitifyType.TEAMSPEAK:
identity = new TeaSpeakIdentity(undefined, undefined);
const tidentity = require("tc-shared/profiles/identities/TeamSpeakIdentity");
identity = new tidentity.TeaSpeakIdentity(undefined, undefined);
break;
}
return identity;
}
export class HandshakeCommandHandler<T extends AbstractHandshakeIdentityHandler> extends connection.AbstractCommandHandler {
export class HandshakeCommandHandler<T extends AbstractHandshakeIdentityHandler> extends AbstractCommandHandler {
readonly handle: T;
constructor(connection: connection.AbstractServerConnection, handle: T) {
constructor(connection: AbstractServerConnection, handle: T) {
super(connection);
this.handle = handle;
}
handle_command(command: connection.ServerCommand): boolean {
handle_command(command: ServerCommand): boolean {
if($.isFunction(this[command.command]))
this[command.command](command.arguments);
else if(command.command == "error") {
@ -82,12 +92,12 @@ namespace profiles.identities {
}
}
export abstract class AbstractHandshakeIdentityHandler implements connection.HandshakeIdentityHandler {
connection: connection.AbstractServerConnection;
export abstract class AbstractHandshakeIdentityHandler implements HandshakeIdentityHandler {
connection: AbstractServerConnection;
protected callbacks: ((success: boolean, message?: string) => any)[] = [];
protected constructor(connection: connection.AbstractServerConnection) {
protected constructor(connection: AbstractServerConnection) {
this.connection = connection;
}
@ -107,4 +117,3 @@ namespace profiles.identities {
callback(false, message);
}
}
}

View file

@ -1,11 +1,21 @@
/// <reference path="../Identity.ts" />
import {
AbstractHandshakeIdentityHandler,
HandshakeCommandHandler,
IdentitifyType,
Identity
} from "tc-shared/profiles/Identity";
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
import {HandshakeIdentityHandler} from "tc-shared/connection/HandshakeHandler";
namespace profiles.identities {
console.error(AbstractHandshakeIdentityHandler);
class NameHandshakeHandler extends AbstractHandshakeIdentityHandler {
readonly identity: NameIdentity;
handler: HandshakeCommandHandler<NameHandshakeHandler>;
constructor(connection: connection.AbstractServerConnection, identity: profiles.identities.NameIdentity) {
constructor(connection: AbstractServerConnection, identity: NameIdentity) {
super(connection);
this.identity = identity;
@ -81,8 +91,7 @@ namespace profiles.identities {
});
}
spawn_identity_handshake_handler(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler {
spawn_identity_handshake_handler(connection: AbstractServerConnection) : HandshakeIdentityHandler {
return new NameHandshakeHandler(connection, this);
}
}
}

View file

@ -1,11 +1,21 @@
/// <reference path="../Identity.ts" />
import {
AbstractHandshakeIdentityHandler,
HandshakeCommandHandler,
IdentitifyType,
Identity
} from "tc-shared/profiles/Identity";
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
import {HandshakeIdentityHandler} from "tc-shared/connection/HandshakeHandler";
import * as forum from "./teaspeak-forum";
namespace profiles.identities {
class TeaForumHandshakeHandler extends AbstractHandshakeIdentityHandler {
readonly identity: TeaForumIdentity;
handler: HandshakeCommandHandler<TeaForumHandshakeHandler>;
constructor(connection: connection.AbstractServerConnection, identity: profiles.identities.TeaForumIdentity) {
constructor(connection: AbstractServerConnection, identity: TeaForumIdentity) {
super(connection);
this.identity = identity;
this.handler = new HandshakeCommandHandler(connection, this);
@ -62,7 +72,7 @@ namespace profiles.identities {
this.identity_data = data;
}
data() : forum.Data {
data() {
return this.identity_data;
}
@ -80,7 +90,7 @@ namespace profiles.identities {
});
}
spawn_identity_handshake_handler(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler {
spawn_identity_handshake_handler(connection: AbstractServerConnection) : HandshakeIdentityHandler {
return new TeaForumHandshakeHandler(connection, this);
}
@ -88,7 +98,7 @@ namespace profiles.identities {
return this.identity_data ? this.identity_data.name() : undefined;
}
type(): profiles.identities.IdentitifyType {
type(): IdentitifyType {
return IdentitifyType.TEAFORO;
}
@ -96,6 +106,10 @@ namespace profiles.identities {
//FIXME: Real UID!
return "TeaForo#" + ((this.identity_data ? this.identity_data.name() : "Another TeaSpeak user"));
}
public static identity() {
return static_identity;
}
}
let static_identity: TeaForumIdentity;
@ -119,4 +133,3 @@ namespace profiles.identities {
export function static_forum_identity() : TeaForumIdentity | undefined {
return static_identity;
}
}

View file

@ -1,9 +1,23 @@
/// <reference path="../Identity.ts" />
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import * as asn1 from "tc-shared/crypto/asn1";
import * as sha from "tc-shared/crypto/sha";
import {
AbstractHandshakeIdentityHandler,
HandshakeCommandHandler,
IdentitifyType,
Identity
} from "tc-shared/profiles/Identity";
import {settings} from "tc-shared/settings";
import {arrayBufferBase64, base64_encode_ab, str2ab8} from "tc-shared/utils/buffers";
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import {HandshakeIdentityHandler} from "tc-shared/connection/HandshakeHandler";
namespace profiles.identities {
export namespace CryptoHelper {
export function base64_url_encode(str){
return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
export function base64_url_decode(str: string, pad?: boolean){
@ -192,11 +206,6 @@ namespace profiles.identities {
k = k.substr(1);
}
/*
console.log("Key x: %s (%d)", btoa(x), x.length);
console.log("Key y: %s (%d)", btoa(y), y.length);
console.log("Key k: %s (%d)", btoa(k), k.length);
*/
return {
crv: "P-256",
d: base64_url_encode(btoa(k)),
@ -214,7 +223,7 @@ namespace profiles.identities {
identity: TeaSpeakIdentity;
handler: HandshakeCommandHandler<TeaSpeakHandshakeHandler>;
constructor(connection: connection.AbstractServerConnection, identity: TeaSpeakIdentity) {
constructor(connection: AbstractServerConnection, identity: TeaSpeakIdentity) {
super(connection);
this.identity = identity;
this.handler = new HandshakeCommandHandler(connection, this);
@ -802,7 +811,6 @@ namespace profiles.identities {
}
this._initialized = true;
//const public_key = await profiles.identities.CryptoHelper.export_ecc_key(key, true);
}
async export_ts(ini?: boolean) : Promise<string> {
@ -864,8 +872,7 @@ namespace profiles.identities {
return base64_encode_ab(buffer.subarray(0, index));
}
spawn_identity_handshake_handler(connection: connection.AbstractServerConnection): connection.HandshakeIdentityHandler {
spawn_identity_handshake_handler(connection: AbstractServerConnection): HandshakeIdentityHandler {
return new TeaSpeakHandshakeHandler(connection, this);
}
}
}

View file

@ -1,6 +1,12 @@
import {settings, Settings} from "tc-shared/settings";
import * as loader from "tc-loader";
import * as fidentity from "./TeaForumIdentity";
declare global {
interface Window {
grecaptcha: GReCaptcha;
}
}
interface GReCaptcha {
render(container: string | HTMLElement, parameters: {
@ -18,7 +24,6 @@ interface GReCaptcha {
reset(widget_id?: string);
}
namespace forum {
export namespace gcaptcha {
export async function initialize() {
if(typeof(window.grecaptcha) === "undefined") {
@ -46,7 +51,7 @@ namespace forum {
if(script)
script.onerror = undefined;
delete window[callback_name];
clearTimeout(timeout);
timeout && clearTimeout(timeout);
}
}
@ -206,7 +211,7 @@ namespace forum {
localStorage.setItem("teaspeak-forum-data", response["data"]);
localStorage.setItem("teaspeak-forum-sign", response["sign"]);
localStorage.setItem("teaspeak-forum-auth", response["auth-key"]);
profiles.identities.update_forum();
fidentity.update_forum();
} catch(error) {
console.error(tr("Failed to parse forum given data: %o"), error);
return {
@ -266,7 +271,7 @@ namespace forum {
_data = new Data(_data.auth_key, response["data"], response["sign"]);
localStorage.setItem("teaspeak-forum-data", response["data"]);
localStorage.setItem("teaspeak-forum-sign", response["sign"]);
profiles.identities.update_forum();
fidentity.update_forum();
} catch(error) {
console.error(tr("Failed to parse forum given data: %o"), error);
throw tr("failed to parse data");
@ -320,7 +325,7 @@ namespace forum {
localStorage.removeItem("teaspeak-forum-data");
localStorage.removeItem("teaspeak-forum-sign");
localStorage.removeItem("teaspeak-forum-auth");
profiles.identities.update_forum();
fidentity.update_forum();
}
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
@ -363,4 +368,3 @@ namespace forum {
}
}
})
}

View file

@ -1,5 +1,6 @@
//Used by CertAccept popup
declare global {
interface Array<T> {
remove(elem?: T): boolean;
last?(): T;
@ -41,6 +42,69 @@ interface String {
format(arguments: string[]): string;
}
interface Twemoji {
parse(message: string) : string;
}
let twemoji: Twemoji;
interface HighlightJS {
listLanguages() : string[];
getLanguage(name: string) : any | undefined;
highlight(language: string, text: string, ignore_illegals?: boolean) : HighlightJSResult;
highlightAuto(text: string) : HighlightJSResult;
}
interface HighlightJSResult {
language: string;
relevance: number;
value: string;
second_best?: any;
}
interface DOMPurify {
sanitize(html: string, config?: {
ADD_ATTR?: string[]
ADD_TAGS?: string[];
}) : string;
}
let DOMPurify: DOMPurify;
let remarkable: typeof window.remarkable;
class webkitAudioContext extends AudioContext {}
class webkitOfflineAudioContext extends OfflineAudioContext {}
interface Window {
readonly webkitAudioContext: typeof webkitAudioContext;
readonly AudioContext: typeof webkitAudioContext;
readonly OfflineAudioContext: typeof OfflineAudioContext;
readonly webkitOfflineAudioContext: typeof webkitOfflineAudioContext;
readonly RTCPeerConnection: typeof RTCPeerConnection;
readonly Pointer_stringify: any;
readonly jsrender: any;
twemoji: Twemoji;
hljs: HighlightJS;
remarkable: any;
require(id: string): any;
}
interface Navigator {
browserSpecs: {
name: string,
version: string
};
mozGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
webkitGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
}
}
export function initialize() { }
if(!JSON.map_to) {
JSON.map_to = function <T>(object: T, json: any, variables?: string | string[], validator?: (map_field: string, map_value: string) => boolean, variable_direction?: number): number {
if (!validator) validator = (a, b) => true;
@ -131,6 +195,7 @@ if(typeof ($) !== "undefined") {
return $(document.createElement(tagName) as any);
}
}
if(!$.fn.renderTag) {
$.fn.renderTag = function (this: JQuery, values?: any) : JQuery {
let result;
@ -184,7 +249,8 @@ if(typeof ($) !== "undefined") {
const result = this.height();
this.attr("style", original_style || "");
return result;
}
};
if(!$.fn.visible_width)
$.fn.visible_width = function (this: JQuery<HTMLElement>) {
const original_style = this.attr("style");
@ -197,7 +263,8 @@ if(typeof ($) !== "undefined") {
const result = this.width();
this.attr("style", original_style || "");
return result;
}
};
if(!$.fn.firstParent)
$.fn.firstParent = function (this: JQuery<HTMLElement>, selector: string) {
if(this.is(selector))
@ -232,30 +299,6 @@ function concatenate(resultConstructor, ...arrays) {
return result;
}
function formatDate(secs: number) : string {
let years = Math.floor(secs / (60 * 60 * 24 * 365));
let days = Math.floor(secs / (60 * 60 * 24)) % 365;
let hours = Math.floor(secs / (60 * 60)) % 24;
let minutes = Math.floor(secs / 60) % 60;
let seconds = Math.floor(secs % 60);
let result = "";
if(years > 0)
result += years + " " + tr("years") + " ";
if(years > 0 || days > 0)
result += days + " " + tr("days") + " ";
if(years > 0 || days > 0 || hours > 0)
result += hours + " " + tr("hours") + " ";
if(years > 0 || days > 0 || hours > 0 || minutes > 0)
result += minutes + " " + tr("minutes") + " ";
if(years > 0 || days > 0 || hours > 0 || minutes > 0 || seconds > 0)
result += seconds + " " + tr("seconds") + " ";
else
result = tr("now") + " ";
return result.substr(0, result.length - 1);
}
function calculate_width(text: string) : number {
let element = $.spawn("div");
element.text(text)
@ -266,63 +309,3 @@ function calculate_width(text: string) : number {
element.detach();
return size;
}
interface Twemoji {
parse(message: string) : string;
}
declare let twemoji: Twemoji;
interface HighlightJS {
listLanguages() : string[];
getLanguage(name: string) : any | undefined;
highlight(language: string, text: string, ignore_illegals?: boolean) : HighlightJSResult;
highlightAuto(text: string) : HighlightJSResult;
}
interface HighlightJSResult {
language: string;
relevance: number;
value: string;
second_best?: any;
}
interface DOMPurify {
sanitize(html: string, config?: {
ADD_ATTR?: string[]
ADD_TAGS?: string[];
}) : string;
}
declare let DOMPurify: DOMPurify;
declare let remarkable: typeof window.remarkable;
declare class webkitAudioContext extends AudioContext {}
declare class webkitOfflineAudioContext extends OfflineAudioContext {}
interface Window {
readonly webkitAudioContext: typeof webkitAudioContext;
readonly AudioContext: typeof webkitAudioContext;
readonly OfflineAudioContext: typeof OfflineAudioContext;
readonly webkitOfflineAudioContext: typeof webkitOfflineAudioContext;
readonly RTCPeerConnection: typeof RTCPeerConnection;
readonly Pointer_stringify: any;
readonly jsrender: any;
twemoji: Twemoji;
hljs: HighlightJS;
remarkable: any;
require(id: string): any;
}
interface Navigator {
browserSpecs: {
name: string,
version: string
};
mozGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
webkitGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
}

View file

@ -1,6 +1,10 @@
/// <reference path="ui/elements/modal.ts" />
//Used by CertAccept popup
import {createErrorModal} from "tc-shared/ui/elements/Modal";
import {LogCategory} from "tc-shared/log";
import * as loader from "tc-loader";
import * as log from "tc-shared/log";
if(typeof(customElements) !== "undefined") {
try {
class X_Properties extends HTMLElement {}
@ -9,12 +13,12 @@ if(typeof(customElements) !== "undefined") {
customElements.define('x-properties', X_Properties, { extends: 'div' });
customElements.define('x-property', X_Property, { extends: 'div' });
} catch(error) {
console.warn("failed to define costum elements");
console.warn("failed to define costume elements");
}
}
/* T = value type */
interface SettingsKey<T> {
export interface SettingsKey<T> {
key: string;
fallback_keys?: string | string[];
@ -25,7 +29,7 @@ interface SettingsKey<T> {
require_restart?: boolean;
}
class SettingsBase {
export class SettingsBase {
protected static readonly UPDATE_DIRECT: boolean = true;
protected static transformStO?<T>(input?: string, _default?: T, default_type?: string) : T {
@ -77,7 +81,7 @@ class SettingsBase {
}
}
class StaticSettings extends SettingsBase {
export class StaticSettings extends SettingsBase {
private static _instance: StaticSettings;
static get instance() : StaticSettings {
if(!this._instance)
@ -139,7 +143,7 @@ class StaticSettings extends SettingsBase {
}
}
class Settings extends StaticSettings {
export class Settings extends StaticSettings {
static readonly KEY_USER_IS_NEW: SettingsKey<boolean> = {
key: 'user_is_new_user',
default_value: true
@ -433,7 +437,7 @@ class Settings extends StaticSettings {
}
}
class ServerSettings extends SettingsBase {
export class ServerSettings extends SettingsBase {
private cacheServer = {};
private _server_unique_id: string;
private _server_save_worker: NodeJS.Timer;
@ -511,4 +515,4 @@ class ServerSettings extends SettingsBase {
}
}
let settings: Settings;
export let settings: Settings;

View file

@ -1,4 +1,10 @@
enum Sound {
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import {settings} from "tc-shared/settings";
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import * as sbackend from "tc-backend/audio/sounds";
export enum Sound {
SOUND_TEST = "sound.test",
SOUND_EGG = "sound.egg",
@ -61,7 +67,6 @@ enum Sound {
GROUP_CHANNEL_CHANGED_SELF = "group.channel.changed.self"
}
namespace sound {
export interface SoundHandle {
key: string;
filename: string;
@ -136,21 +141,6 @@ namespace sound {
ignore_muted = flag;
}
export function reinitialisize_audio() {
const context = audio.player.context();
const destination = audio.player.destination();
if(master_mixed)
master_mixed.disconnect();
master_mixed = context.createGain();
if(master_mixed.gain.setValueAtTime)
master_mixed.gain.setValueAtTime(master_volume, 0);
else
master_mixed.gain.value = master_volume;
master_mixed.connect(destination);
}
export function save() {
if(volume_require_save) {
volume_require_save = false;
@ -158,7 +148,7 @@ namespace sound {
const data: any = {};
data.version = 1;
for(const key in Sound) {
for(const key of Object.keys(Sound)) {
if(typeof(speech_volume[Sound[key]]) !== "undefined")
data[Sound[key]] = speech_volume[Sound[key]];
}
@ -183,7 +173,7 @@ namespace sound {
/* volumes */
{
const data = JSON.parse(settings.static_global("sound_volume", "{}"));
for(const sound_key in Sound) {
for(const sound_key of Object.keys(Sound)) {
if(typeof(data[Sound[sound_key]]) !== "undefined")
speech_volume[Sound[sound_key]] = data[Sound[sound_key]];
}
@ -197,7 +187,6 @@ namespace sound {
register_sound("message.send", "effects/message_send.wav");
manager = new SoundManager(undefined);
audio.player.on_ready(reinitialisize_audio);
return new Promise<void>((resolve, reject) => {
$.ajax({
url: "audio/speech/mapping.json",
@ -254,25 +243,19 @@ namespace sound {
if(volume == 0 || master_volume == 0)
return;
if(this._handle && !options.ignore_muted && !sound.ignore_output_muted() && this._handle.client_status.output_muted)
if(this._handle && !options.ignore_muted && !ignore_output_muted() && this._handle.client_status.output_muted)
return;
const context = audio.player.context();
if(!context) {
log.warn(LogCategory.AUDIO, tr("Tried to replay a sound without an audio context (Sound: %o). Dropping playback"), _sound);
return;
}
sound.resolve_sound(_sound).then(handle => {
resolve_sound(_sound).then(handle => {
if(!handle) return;
if(!options.ignore_overlap && (this._playing_sounds[handle.filename] > 0) && !sound.overlap_activated()) {
if(!options.ignore_overlap && (this._playing_sounds[handle.filename] > 0) && !overlap_activated()) {
log.info(LogCategory.AUDIO, tr("Dropping requested playback for sound %s because it would overlap."), _sound);
return;
}
this._playing_sounds[handle.filename] = (this._playing_sounds[handle.filename] || 0) + 1;
audio.sounds.play_sound({
sbackend.play_sound({
path: "audio/" + handle.filename,
volume: volume * master_volume
}).then(() => {
@ -286,10 +269,9 @@ namespace sound {
this._playing_sounds[handle.filename]--;
});
}).catch(error => {
log.warn(LogCategory.AUDIO, tr("Failed to replay sound %o because it could not be resolved: %o"), sound, error);
log.warn(LogCategory.AUDIO, tr("Failed to replay sound %o because it could not be resolved: %o"), _sound, error);
if(options.callback)
options.callback(false);
});
}
}
}

View file

@ -1,7 +1,9 @@
namespace stats {
import {LogCategory} from "tc-shared/log";
import * as log from "tc-shared/log";
const LOG_PREFIX = "[Statistics] ";
export enum CloseCodes {
enum CloseCodes {
UNSET = 3000,
RECONNECT = 3001,
INTERNAL_ERROR = 3002,
@ -240,4 +242,3 @@ namespace stats {
handler["notifyusercount"] = handle_notify_user_count;
}
}
}

View file

@ -1,12 +1,26 @@
/// <reference path="view.ts" />
/// <reference path="../utils/helpers.ts" />
import {ChannelTree} from "tc-shared/ui/view";
import {ClientEntry} from "tc-shared/ui/client";
import * as log from "tc-shared/log";
import {LogCategory, LogType} from "tc-shared/log";
import PermissionType from "tc-shared/permission/PermissionType";
import {settings, Settings} from "tc-shared/settings";
import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
import {Sound} from "tc-shared/sound/Sounds";
import {createErrorModal, createInfoModal, createInputModal} from "tc-shared/ui/elements/Modal";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import * as htmltags from "./htmltags";
import {hashPassword} from "tc-shared/utils/helpers";
import * as server_log from "tc-shared/ui/frames/server_log";
import {openChannelInfo} from "tc-shared/ui/modal/ModalChannelInfo";
import {createChannelModal} from "tc-shared/ui/modal/ModalCreateChannel";
import {formatMessage} from "tc-shared/ui/frames/chat";
enum ChannelType {
export enum ChannelType {
PERMANENT,
SEMI_PERMANENT,
TEMPORARY
}
namespace ChannelType {
export namespace ChannelType {
export function normalize(mode: ChannelType) {
let value: string = ChannelType[mode];
value = value.toLowerCase();
@ -14,13 +28,13 @@ namespace ChannelType {
}
}
enum ChannelSubscribeMode {
export enum ChannelSubscribeMode {
SUBSCRIBED,
UNSUBSCRIBED,
INHERITED
}
class ChannelProperties {
export class ChannelProperties {
channel_order: number = 0;
channel_name: string = "";
channel_name_phonetic: string = "";
@ -55,7 +69,7 @@ class ChannelProperties {
channel_conversation_history_length: number = -1;
}
class ChannelEntry {
export class ChannelEntry {
channelTree: ChannelTree;
channelId: number;
parent?: ChannelEntry;
@ -525,7 +539,7 @@ class ChannelEntry {
name: tr("Show channel info"),
callback: () => {
trigger_close = false;
Modals.openChannelInfo(this);
openChannelInfo(this);
},
icon_class: "client-about"
},
@ -565,7 +579,7 @@ class ChannelEntry {
name: tr("Edit channel"),
invalidPermission: !channelModify,
callback: () => {
Modals.createChannelModal(this.channelTree.client, this, undefined, this.channelTree.client.permissions, (changes?, permissions?) => {
createChannelModal(this.channelTree.client, this, undefined, this.channelTree.client.permissions, (changes?, permissions?) => {
if(changes) {
changes["cid"] = this.channelId;
this.channelTree.client.serverConnection.send_command("channeledit", changes);
@ -617,7 +631,7 @@ class ChannelEntry {
error = error.extra_message || error.message;
}
createErrorModal(tr("Failed to create bot"), MessageHelper.formatMessage(tr("Failed to create the music bot:<br>{0}"), error)).open();
createErrorModal(tr("Failed to create bot"), formatMessage(tr("Failed to create the music bot:<br>{0}"), error)).open();
});
}
},
@ -834,7 +848,7 @@ class ChannelEntry {
!this.channelTree.client.permissions.neededPermission(PermissionType.B_CHANNEL_JOIN_IGNORE_PASSWORD).granted(1)) {
createInputModal(tr("Channel password"), tr("Channel password:"), () => true, text => {
if(typeof(text) == typeof(true)) return;
helpers.hashPassword(text as string).then(result => {
hashPassword(text as string).then(result => {
this._cachedPassword = result;
this.joinChannel();
this.updateChannelTypeIcon();
@ -923,7 +937,7 @@ class ChannelEntry {
this._tag_channel.find(".marker-text-unread").toggleClass("hidden", !flag);
}
log_data() : log.server.base.Channel {
log_data() : server_log.base.Channel {
return {
channel_name: this.channelName(),
channel_id: this.channelId

View file

@ -1,8 +1,34 @@
/// <reference path="channel.ts" />
/// <reference path="modal/ModalChangeVolume.ts" />
/// <reference path="client_move.ts" />
import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
import {channel_tree, Registry} from "tc-shared/events";
import {ChannelTree} from "tc-shared/ui/view";
import * as log from "tc-shared/log";
import {LogCategory, LogType} from "tc-shared/log";
import {Settings, settings} from "tc-shared/settings";
import {KeyCode, SpecialKey} from "tc-shared/PPTListener";
import {Sound} from "tc-shared/sound/Sounds";
import {Group, GroupManager, GroupTarget, GroupType} from "tc-shared/permission/GroupManager";
import PermissionType from "tc-shared/permission/PermissionType";
import {createErrorModal, createInputModal} from "tc-shared/ui/elements/Modal";
import * as htmltags from "tc-shared/ui/htmltags";
import * as server_log from "tc-shared/ui/frames/server_log";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import {ChannelEntry} from "tc-shared/ui/channel";
import {ConnectionHandler, ViewReasonId} from "tc-shared/ConnectionHandler";
import {voice} from "tc-shared/connection/ConnectionBase";
import VoiceClient = voice.VoiceClient;
import {spawnPermissionEdit} from "tc-shared/ui/modal/permission/ModalPermissionEdit";
import {createServerGroupAssignmentModal} from "tc-shared/ui/modal/ModalGroupAssignment";
import {openClientInfo} from "tc-shared/ui/modal/ModalClientInfo";
import {spawnBanClient} from "tc-shared/ui/modal/ModalBanClient";
import {spawnChangeVolume} from "tc-shared/ui/modal/ModalChangeVolume";
import {spawnChangeLatency} from "tc-shared/ui/modal/ModalChangeLatency";
import {spawnPlaylistEdit} from "tc-shared/ui/modal/ModalPlaylistEdit";
import {formatMessage} from "tc-shared/ui/frames/chat";
import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
import * as ppt from "tc-backend/ppt";
import * as hex from "tc-shared/crypto/hex";
enum ClientType {
export enum ClientType {
CLIENT_VOICE,
CLIENT_QUERY,
CLIENT_INTERNAL,
@ -11,7 +37,7 @@ enum ClientType {
CLIENT_UNDEFINED
}
class ClientProperties {
export class ClientProperties {
client_type: ClientType = ClientType.CLIENT_VOICE; //TeamSpeaks type
client_type_exact: ClientType = ClientType.CLIENT_VOICE;
@ -57,7 +83,7 @@ class ClientProperties {
client_is_priority_speaker: boolean = false;
}
class ClientConnectionInfo {
export class ClientConnectionInfo {
connection_bandwidth_received_last_minute_control: number = -1;
connection_bandwidth_received_last_minute_keepalive: number = -1;
connection_bandwidth_received_last_minute_speech: number = -1;
@ -109,8 +135,8 @@ class ClientConnectionInfo {
connection_client_port: number = -1;
}
class ClientEntry {
readonly events: events.Registry<events.channel_tree.client>;
export class ClientEntry {
readonly events: Registry<channel_tree.client>;
protected _clientId: number;
protected _channel: ChannelEntry;
@ -121,7 +147,7 @@ class ClientEntry {
protected _speaking: boolean;
protected _listener_initialized: boolean;
protected _audio_handle: connection.voice.VoiceClient;
protected _audio_handle: VoiceClient;
protected _audio_volume: number;
protected _audio_muted: boolean;
@ -136,7 +162,7 @@ class ClientEntry {
channelTree: ChannelTree;
constructor(clientId: number, clientName, properties: ClientProperties = new ClientProperties()) {
this.events = new events.Registry<events.channel_tree.client>();
this.events = new Registry<channel_tree.client>();
this._properties = properties;
this._properties.client_nickname = clientName;
@ -181,7 +207,7 @@ class ClientEntry {
this._channel = undefined;
}
set_audio_handle(handle: connection.voice.VoiceClient) {
set_audio_handle(handle: VoiceClient) {
if(this._audio_handle === handle)
return;
@ -200,7 +226,7 @@ class ClientEntry {
handle.callback_stopped = () => this.speaking = false;
}
get_audio_handle() : connection.voice.VoiceClient {
get_audio_handle() : VoiceClient {
return this._audio_handle;
}
@ -285,7 +311,7 @@ class ClientEntry {
let clients = this.channelTree.currently_selected as (ClientEntry | ClientEntry[]);
if(ppt.key_pressed(ppt.SpecialKey.SHIFT)) {
if(ppt.key_pressed(SpecialKey.SHIFT)) {
if(clients != this && !($.isArray(clients) && clients.indexOf(this) != -1))
clients = $.isArray(clients) ? [...clients, this] : [clients, this];
} else {
@ -418,20 +444,20 @@ class ClientEntry {
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-permission_client",
name: tr("Client permissions"),
callback: () => Modals.spawnPermissionEdit(this.channelTree.client, "clp", {unique_id: this.clientUid()}).open()
callback: () => spawnPermissionEdit(this.channelTree.client, "clp", {unique_id: this.clientUid()}).open()
},
{
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-permission_client",
name: tr("Client channel permissions"),
callback: () => Modals.spawnPermissionEdit(this.channelTree.client, "clchp", {unique_id: this.clientUid(), channel_id: this._channel ? this._channel.channelId : undefined }).open()
callback: () => spawnPermissionEdit(this.channelTree.client, "clchp", {unique_id: this.clientUid(), channel_id: this._channel ? this._channel.channelId : undefined }).open()
}
]
}];
}
open_assignment_modal() {
Modals.createServerGroupAssignmentModal(this, (groups, flag) => {
createServerGroupAssignmentModal(this, (groups, flag) => {
if(groups.length == 0) return Promise.resolve(true);
if(groups.length == 1) {
@ -490,7 +516,7 @@ class ClientEntry {
type: contextmenu.MenuEntryType.ENTRY,
icon_class: "client-about",
name: tr("Show client info"),
callback: () => Modals.openClientInfo(this)
callback: () => openClientInfo(this)
},
contextmenu.Entry.HR(),
{
@ -582,7 +608,7 @@ class ClientEntry {
name: tr("Ban client"),
invalidPermission: !this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
callback: () => {
Modals.spawnBanClient(this.channelTree.client, [{
spawnBanClient(this.channelTree.client, [{
name: this.properties.client_nickname,
unique_id: this.properties.client_unique_identifier
}], (data) => {
@ -622,7 +648,7 @@ class ClientEntry {
icon_class: "client-volume",
name: tr("Change Volume"),
callback: () => {
Modals.spawnChangeVolume(this, true, this._audio_volume, undefined, volume => {
spawnChangeVolume(this, true, this._audio_volume, undefined, volume => {
this._audio_volume = volume;
this.channelTree.client.settings.changeServer("volume_client_" + this.clientUid(), volume);
if(this._audio_handle)
@ -635,7 +661,7 @@ class ClientEntry {
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Change playback latency"),
callback: () => {
Modals.spawnChangeLatency(this, this._audio_handle.latency_settings(), () => {
spawnChangeLatency(this, this._audio_handle.latency_settings(), () => {
this._audio_handle.reset_latency_settings();
return this._audio_handle.latency_settings();
}, settings => this._audio_handle.latency_settings(settings), this._audio_handle.support_flush ? () => {
@ -843,7 +869,7 @@ class ClientEntry {
if(variable.key == "client_nickname") {
if(variable.value !== old_value && typeof(old_value) === "string") {
if(!(this instanceof LocalClientEntry)) { /* own changes will be logged somewhere else */
this.channelTree.client.log.log(log.server.Type.CLIENT_NICKNAME_CHANGED, {
this.channelTree.client.log.log(server_log.Type.CLIENT_NICKNAME_CHANGED, {
own_client: false,
client: this.log_data(),
new_name: variable.value,
@ -1082,7 +1108,7 @@ class ClientEntry {
this.tag.css('padding-left', (5 + (index + 2) * 16) + "px");
}
log_data() : log.server.base.Client {
log_data() : server_log.base.Client {
return {
client_unique_id: this.properties.client_unique_identifier,
client_name: this.clientNickName(),
@ -1123,7 +1149,7 @@ class ClientEntry {
}
}
class LocalClientEntry extends ClientEntry {
export class LocalClientEntry extends ClientEntry {
handle: ConnectionHandler;
private renaming: boolean;
@ -1216,14 +1242,14 @@ class LocalClientEntry extends ClientEntry {
const old_name = this.clientNickName();
this.handle.serverConnection.command_helper.updateClient("client_nickname", text).then((e) => {
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, text);
this.channelTree.client.log.log(log.server.Type.CLIENT_NICKNAME_CHANGED, {
this.channelTree.client.log.log(server_log.Type.CLIENT_NICKNAME_CHANGED, {
client: this.log_data(),
old_name: old_name,
new_name: text,
own_client: true
});
}).catch((e: CommandResult) => {
this.channelTree.client.log.log(log.server.Type.CLIENT_NICKNAME_CHANGE_FAILED, {
this.channelTree.client.log.log(server_log.Type.CLIENT_NICKNAME_CHANGE_FAILED, {
reason: e.extra_message
});
this.openRename();
@ -1232,7 +1258,7 @@ class LocalClientEntry extends ClientEntry {
}
}
class MusicClientProperties extends ClientProperties {
export class MusicClientProperties extends ClientProperties {
player_state: number = 0;
player_volume: number = 0;
@ -1264,7 +1290,7 @@ class MusicClientProperties extends ClientProperties {
}
*/
class SongInfo {
export class SongInfo {
song_id: number = 0;
song_url: string = "";
song_invoker: number = 0;
@ -1277,7 +1303,7 @@ class SongInfo {
song_length: number = 0;
}
class MusicClientPlayerInfo extends SongInfo {
export class MusicClientPlayerInfo extends SongInfo {
bot_id: number = 0;
player_state: number = 0;
@ -1290,7 +1316,7 @@ class MusicClientPlayerInfo extends SongInfo {
player_description: string = "";
}
class MusicClientEntry extends ClientEntry {
export class MusicClientEntry extends ClientEntry {
private _info_promise: Promise<MusicClientPlayerInfo>;
private _info_promise_age: number = 0;
private _info_promise_resolve: any;
@ -1366,7 +1392,7 @@ class MusicClientEntry extends ClientEntry {
this.channelTree.client.serverConnection.command_helper.request_playlist_list().then(lists => {
for(const entry of lists) {
if(entry.playlist_id == this.properties.client_playlist_id) {
Modals.spawnPlaylistEdit(this.channelTree.client, entry);
spawnPlaylistEdit(this.channelTree.client, entry);
return;
}
}
@ -1435,7 +1461,7 @@ class MusicClientEntry extends ClientEntry {
icon_class: "client-volume",
name: tr("Change local volume"),
callback: () => {
Modals.spawnChangeVolume(this, true, this._audio_handle.get_volume(), undefined, volume => {
spawnChangeVolume(this, true, this._audio_handle.get_volume(), undefined, volume => {
this.channelTree.client.settings.changeServer("volume_client_" + this.clientUid(), volume);
this._audio_handle.set_volume(volume);
});
@ -1450,7 +1476,7 @@ class MusicClientEntry extends ClientEntry {
if(max_volume < 0)
max_volume = 100;
Modals.spawnChangeVolume(this, false, this.properties.player_volume, max_volume / 100, value => {
spawnChangeVolume(this, false, this.properties.player_volume, max_volume / 100, value => {
if(typeof(value) !== "number")
return;
@ -1467,7 +1493,7 @@ class MusicClientEntry extends ClientEntry {
type: contextmenu.MenuEntryType.ENTRY,
name: tr("Change playback latency"),
callback: () => {
Modals.spawnChangeLatency(this, this._audio_handle.latency_settings(), () => {
spawnChangeLatency(this, this._audio_handle.latency_settings(), () => {
this._audio_handle.reset_latency_settings();
return this._audio_handle.latency_settings();
}, settings => this._audio_handle.latency_settings(settings), this._audio_handle.support_flush ? () => {
@ -1482,8 +1508,8 @@ class MusicClientEntry extends ClientEntry {
icon_class: "client-delete",
disabled: false,
callback: () => {
const tag = $.spawn("div").append(MessageHelper.formatMessage(tr("Do you really want to delete {0}"), this.createChatTag(false)));
Modals.spawnYesNo(tr("Are you sure?"), $.spawn("div").append(tag), result => {
const tag = $.spawn("div").append(formatMessage(tr("Do you really want to delete {0}"), this.createChatTag(false)));
spawnYesNo(tr("Are you sure?"), $.spawn("div").append(tag), result => {
if(result) {
this.channelTree.client.serverConnection.send_command("musicbotdelete", {
bot_id: this.properties.client_database_id

View file

@ -1,6 +1,10 @@
/// <reference path="client.ts" />
import {ChannelTree} from "tc-shared/ui/view";
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import {ClientEntry} from "tc-shared/ui/client";
import {ChannelEntry} from "tc-shared/ui/channel";
class ClientMover {
export class ClientMover {
static readonly listener_root = $(document);
static readonly move_element = $("#mouse-move");
readonly channel_tree: ChannelTree;

View file

@ -1,6 +1,16 @@
import {settings} from "tc-shared/settings";
import {LogCategory} from "tc-shared/log";
import * as log from "tc-shared/log";
declare global {
interface JQuery<TElement = HTMLElement> {
dividerfy() : this;
}
}
export function initialize() {
}
if(!$.fn.dividerfy) {
$.fn.dividerfy = function<T extends HTMLElement>(this: JQuery<T>) : JQuery<T> {
@ -58,8 +68,8 @@ if(!$.fn.dividerfy) {
Math.max(previous_offset.top + previous_element.height(), next_offset.top + next_element.height());
*/
let previous = 0;
let next = 0;
let previous;
let next;
if(current < min) {
previous = 0;
next = 1;
@ -89,7 +99,7 @@ if(!$.fn.dividerfy) {
}));
};
const listener_up = (event: MouseEvent) => {
const listener_up = () => {
document.removeEventListener('mousemove', listener_move);
document.removeEventListener('touchmove', listener_move);

View file

@ -1,4 +1,3 @@
namespace contextmenu {
export interface MenuEntry {
callback?: () => void;
type: MenuEntryType;
@ -74,9 +73,8 @@ namespace contextmenu {
provider = _provider;
provider.initialize();
}
}
class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
class HTMLContextMenuProvider implements ContextMenuProvider {
private _global_click_listener: (event) => any;
private _context_menu: JQuery;
private _close_callbacks: (() => any)[] = [];
@ -118,10 +116,10 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
}
}
private generate_tag(entry: contextmenu.MenuEntry) : JQuery {
if(entry.type == contextmenu.MenuEntryType.HR) {
private generate_tag(entry: MenuEntry) : JQuery {
if(entry.type == MenuEntryType.HR) {
return $.spawn("hr");
} else if(entry.type == contextmenu.MenuEntryType.ENTRY) {
} else if(entry.type == MenuEntryType.ENTRY) {
let icon = entry.icon_class;
if(!icon || icon.length == 0) icon = "icon_empty";
else icon = "icon " + icon;
@ -141,7 +139,7 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
});
}
return tag;
} else if(entry.type == contextmenu.MenuEntryType.CHECKBOX) {
} else if(entry.type == MenuEntryType.CHECKBOX) {
let checkbox = $.spawn("label").addClass("ccheckbox");
$.spawn("input").attr("type", "checkbox").prop("checked", !!entry.checkbox_checked).appendTo(checkbox);
$.spawn("span").addClass("checkmark").appendTo(checkbox);
@ -161,7 +159,7 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
});
}
return tag;
} else if(entry.type == contextmenu.MenuEntryType.SUB_MENU) {
} else if(entry.type == MenuEntryType.SUB_MENU) {
let icon = entry.icon_class;
if(!icon || icon.length == 0) icon = "icon_empty";
else icon = "icon " + icon;
@ -187,7 +185,7 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
return $.spawn("div").text("undefined");
}
spawn_context_menu(x: number, y: number, ...entries: contextmenu.MenuEntry[]) {
spawn_context_menu(x: number, y: number, ...entries: MenuEntry[]) {
this._visible = true;
let menu_tag = this._context_menu || (this._context_menu = $(".context-menu"));
@ -200,7 +198,7 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
if(typeof(entry.visible) === 'boolean' && !entry.visible)
continue;
if(entry.type == contextmenu.MenuEntryType.CLOSE) {
if(entry.type == MenuEntryType.CLOSE) {
if(entry.callback)
this._close_callbacks.push(entry.callback);
} else
@ -225,5 +223,4 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
return true;
}
}
contextmenu.set_provider(new HTMLContextMenuProvider());
set_provider(new HTMLContextMenuProvider());

View file

@ -1,13 +1,13 @@
/// <reference path="../../PPTListener.ts" />
import {KeyCode} from "tc-shared/PPTListener";
enum ElementType {
export enum ElementType {
HEADER,
BODY,
FOOTER
}
type BodyCreator = (() => JQuery | JQuery[] | string) | string | JQuery | JQuery[];
const ModalFunctions = {
export type BodyCreator = (() => JQuery | JQuery[] | string) | string | JQuery | JQuery[];
export const ModalFunctions = {
divify: function (val: JQuery) {
if(val.length > 1)
return $.spawn("div").append(val);
@ -54,7 +54,7 @@ const ModalFunctions = {
}
};
class ModalProperties {
export class ModalProperties {
template?: string;
header: BodyCreator = () => "HEADER";
body: BodyCreator = () => "BODY";
@ -184,7 +184,7 @@ let _global_modal_count = 0;
let _global_modal_last: HTMLElement;
let _global_modal_last_time: number;
class Modal {
export class Modal {
private _htmlTag: JQuery;
properties: ModalProperties;
shown: boolean;
@ -296,11 +296,11 @@ class Modal {
}
}
function createModal(data: ModalProperties | any) : Modal {
export function createModal(data: ModalProperties | any) : Modal {
return new Modal(ModalFunctions.warpProperties(data));
}
class InputModalProperties extends ModalProperties {
export class InputModalProperties extends ModalProperties {
maxLength?: number;
field_title?: string;
@ -310,7 +310,7 @@ class InputModalProperties extends ModalProperties {
error_message?: string;
}
function createInputModal(headMessage: BodyCreator, question: BodyCreator, validator: (input: string) => boolean, callback: (flag: boolean | string) => void, props: InputModalProperties | any = {}) : Modal {
export function createInputModal(headMessage: BodyCreator, question: BodyCreator, validator: (input: string) => boolean, callback: (flag: boolean | string) => void, props: InputModalProperties | any = {}) : Modal {
props = ModalFunctions.warpProperties(props);
props.template_properties || (props.template_properties = {});
props.template_properties.field_title = props.field_title;
@ -370,7 +370,7 @@ function createInputModal(headMessage: BodyCreator, question: BodyCreator, valid
return modal;
}
function createErrorModal(header: BodyCreator, message: BodyCreator, props: ModalProperties | any = { footer: undefined }) {
export function createErrorModal(header: BodyCreator, message: BodyCreator, props: ModalProperties | any = { footer: undefined }) {
props = ModalFunctions.warpProperties(props);
(props.template_properties || (props.template_properties = {})).header_class = "modal-header-error";
@ -382,7 +382,7 @@ function createErrorModal(header: BodyCreator, message: BodyCreator, props: Moda
return modal;
}
function createInfoModal(header: BodyCreator, message: BodyCreator, props: ModalProperties | any = { footer: undefined }) {
export function createInfoModal(header: BodyCreator, message: BodyCreator, props: ModalProperties | any = { footer: undefined }) {
props = ModalFunctions.warpProperties(props);
(props.template_properties || (props.template_properties = {})).header_class = "modal-header-info";
@ -394,52 +394,6 @@ function createInfoModal(header: BodyCreator, message: BodyCreator, props: Modal
return modal;
}
/* extend jquery */
interface ModalElements {
header?: BodyCreator;
body?: BodyCreator;
footer?: BodyCreator;
}
interface JQuery<TElement = HTMLElement> {
modalize(entry_callback?: (header: JQuery, body: JQuery, footer: JQuery) => ModalElements | void, properties?: ModalProperties | any) : Modal;
}
$.fn.modalize = function (this: JQuery, entry_callback?: (header: JQuery, body: JQuery, footer: JQuery) => ModalElements | void, properties?: ModalProperties | any) : Modal {
properties = properties || {} as ModalProperties;
entry_callback = entry_callback || ((a,b,c) => undefined);
let tag_modal = this[0].tagName.toLowerCase() == "modal" ? this : undefined; /* TODO may throw exception? */
let tag_head = tag_modal ? tag_modal.find("modal-header") : ModalFunctions.jqueriefy(properties.header);
let tag_body = tag_modal ? tag_modal.find("modal-body") : this;
let tag_footer = tag_modal ? tag_modal.find("modal-footer") : ModalFunctions.jqueriefy(properties.footer);
const result = entry_callback(tag_head as any, tag_body, tag_footer as any) || {};
properties.header = result.header || tag_head;
properties.body = result.body || tag_body;
properties.footer = result.footer || tag_footer;
return createModal(properties);
};

View file

@ -0,0 +1,433 @@
export type Entry = {
timestamp: number;
upload?: number;
download?: number;
highlight?: boolean;
}
export type Style = {
background_color: string;
separator_color: string;
separator_count: number;
separator_width: number;
upload: {
fill: string;
stroke: string;
strike_width: number;
},
download: {
fill: string;
stroke: string;
strike_width: number;
}
}
export type TimeSpan = {
origin: {
begin: number;
end: number;
time: number;
},
target: {
begin: number;
end: number;
time: number;
}
}
/* Great explanation of Bezier curves: http://en.wikipedia.org/wiki/Bezier_curve#Quadratic_curves
*
* Assuming A was the last point in the line plotted and B is the new point,
* we draw a curve with control points P and Q as below.
*
* A---P
* |
* |
* |
* Q---B
*
* Importantly, A and P are at the same y coordinate, as are B and Q. This is
* so adjacent curves appear to flow as one.
*/
export class Graph {
private static _loops: (() => any)[] = [];
readonly canvas: HTMLCanvasElement;
public style: Style = {
background_color: "#28292b",
//background_color: "red",
separator_color: "#283036",
//separator_color: 'blue',
separator_count: 10,
separator_width: 1,
upload: {
fill: "#2d3f4d",
stroke: "#336e9f",
strike_width: 2,
},
download: {
fill: "#532c26",
stroke: "#a9321c",
strike_width: 2,
}
};
private _canvas_context: CanvasRenderingContext2D;
private _entries: Entry[] = [];
private _entry_max = {
upload: 1,
download: 1,
};
private _max_space = 1.12;
private _max_gap = 5;
private _listener_mouse_move;
private _listener_mouse_out;
private _animate_loop;
_time_span: TimeSpan = {
origin: {
begin: 0,
end: 1,
time: 0
},
target: {
begin: 0,
end: 1,
time: 1
}
};
private _detailed_shown = false;
callback_detailed_info: (upload: number, download: number, timestamp: number, event: MouseEvent) => any;
callback_detailed_hide: () => any;
constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas;
this._animate_loop = () => this.draw();
this.recalculate_cache(); /* initialize cache */
}
initialize() {
this._canvas_context = this.canvas.getContext("2d");
Graph._loops.push(this._animate_loop);
if(Graph._loops.length == 1) {
const static_loop = () => {
Graph._loops.forEach(l => l());
if(Graph._loops.length > 0)
requestAnimationFrame(static_loop);
else
console.log("STATIC terminate!");
};
static_loop();
}
this.canvas.onmousemove = this.on_mouse_move.bind(this);
this.canvas.onmouseleave = this.on_mouse_leave.bind(this);
}
terminate() {
Graph._loops.remove(this._animate_loop);
}
max_gap_size(value?: number) : number { return typeof(value) === "number" ? (this._max_gap = value) : this._max_gap; }
private recalculate_cache(time_span?: boolean) {
this._entries = this._entries.sort((a, b) => a.timestamp - b.timestamp);
this._entry_max = {
download: 1,
upload: 1
};
if(time_span) {
this._time_span = {
origin: {
begin: 0,
end: 0,
time: 0
},
target: {
begin: this._entries.length > 0 ? this._entries[0].timestamp : 0,
end: this._entries.length > 0 ? this._entries.last().timestamp : 0,
time: 0
}
};
}
for(const entry of this._entries) {
if(typeof(entry.upload) === "number")
this._entry_max.upload = Math.max(this._entry_max.upload, entry.upload);
if(typeof(entry.download) === "number")
this._entry_max.download = Math.max(this._entry_max.download, entry.download);
}
this._entry_max.upload *= this._max_space;
this._entry_max.download *= this._max_space;
}
insert_entry(entry: Entry) {
if(this._entries.length > 0 && entry.timestamp < this._entries.last().timestamp)
throw "invalid timestamp";
this._entries.push(entry);
if(typeof(entry.upload) === "number")
this._entry_max.upload = Math.max(this._entry_max.upload, entry.upload * this._max_space);
if(typeof(entry.download) === "number")
this._entry_max.download = Math.max(this._entry_max.download, entry.download * this._max_space);
}
insert_entries(entries: Entry[]) {
this._entries.push(...entries);
this.recalculate_cache();
this.cleanup();
}
resize() {
this.canvas.style.height = "100%";
this.canvas.style.width = "100%";
const cstyle = getComputedStyle(this.canvas);
this.canvas.width = parseInt(cstyle.width);
this.canvas.height = parseInt(cstyle.height);
}
cleanup() {
const time = this.calculate_time_span();
let index = 0;
for(;index < this._entries.length; index++) {
if(this._entries[index].timestamp < time.begin)
continue;
if(index == 0)
return;
break;
}
/* keep the last entry as a reference point to the left */
if(index > 1) {
this._entries.splice(0, index - 1);
this.recalculate_cache();
}
}
calculate_time_span() : { begin: number; end: number } {
const time = Date.now();
if(time >= this._time_span.target.time)
return this._time_span.target;
if(time <= this._time_span.origin.time)
return this._time_span.origin;
const ob = this._time_span.origin.begin;
const oe = this._time_span.origin.end;
const ot = this._time_span.origin.time;
const tb = this._time_span.target.begin;
const te = this._time_span.target.end;
const tt = this._time_span.target.time;
const offset = (time - ot) / (tt - ot);
return {
begin: ob + (tb - ob) * offset,
end: oe + (te - oe) * offset,
};
}
draw() {
let ctx = this._canvas_context;
const height = this.canvas.height;
const width = this.canvas.width;
//console.log("Painting on %ox%o", height, width);
ctx.shadowBlur = 0;
ctx.filter = "";
ctx.lineCap = "square";
ctx.fillStyle = this.style.background_color;
ctx.fillRect(0, 0, width, height);
/* first of all print the separators */
{
const sw = this.style.separator_width;
const swh = this.style.separator_width / 2;
ctx.lineWidth = sw;
ctx.strokeStyle = this.style.separator_color;
ctx.beginPath();
/* horizontal */
{
const dw = width / this.style.separator_count;
let dx = dw / 2;
while(dx < width) {
ctx.moveTo(Math.floor(dx - swh) + .5, .5);
ctx.lineTo(Math.floor(dx - swh) + .5, Math.floor(height) + .5);
dx += dw;
}
}
/* vertical */
{
const dh = height / 3; //tree lines (top, center, bottom)
let dy = dh / 2;
while(dy < height) {
ctx.moveTo(.5, Math.floor(dy - swh) + .5);
ctx.lineTo(Math.floor(width) + .5, Math.floor(dy - swh) + .5);
dy += dh;
}
}
ctx.stroke();
ctx.closePath();
}
/* draw the lines */
{
const t = this.calculate_time_span();
const tb = t.begin; /* time begin */
const dt = t.end - t.begin; /* delta time */
const dtw = width / dt; /* delta time width */
const draw_graph = (type: "upload" | "download", direction: number, max: number) => {
const hy = Math.floor(height / 2); /* half y */
const by = hy - direction * this.style[type].strike_width; /* the "base" line */
const marked_points: ({x: number, y: number})[] = [];
ctx.beginPath();
ctx.moveTo(0, by);
let x, y, lx = 0, ly = by; /* last x, last y */
const floor = a => a; //Math.floor;
for(const entry of this._entries) {
x = floor((entry.timestamp - tb) * dtw);
if(typeof entry[type] === "number")
y = floor(hy - direction * Math.max(hy * (entry[type] / max), this.style[type].strike_width));
else
y = hy - direction * this.style[type].strike_width;
if(entry.timestamp < tb) {
lx = x;
ly = y;
continue;
}
if(x - lx > this._max_gap && this._max_gap > 0) {
ctx.lineTo(lx, by);
ctx.lineTo(x, by);
ctx.lineTo(x, y);
lx = x;
ly = y;
continue;
}
ctx.bezierCurveTo((x + lx) / 2, ly, (x + lx) / 2, y, x, y);
if(entry.highlight)
marked_points.push({x: x, y: y});
lx = x;
ly = y;
}
ctx.strokeStyle = this.style[type].stroke;
ctx.lineWidth = this.style[type].strike_width;
ctx.lineJoin = "miter";
ctx.stroke();
//Close the path and fill
ctx.lineTo(width, hy);
ctx.lineTo(0, hy);
ctx.fillStyle = this.style[type].fill;
ctx.fill();
ctx.closePath();
{
ctx.beginPath();
const radius = 3;
for(const point of marked_points) {
ctx.moveTo(point.x, point.y);
ctx.ellipse(point.x, point.y, radius, radius, 0, 0, 2 * Math.PI, false);
}
ctx.stroke();
ctx.fill();
ctx.closePath();
}
};
const shared_max = Math.max(this._entry_max.upload, this._entry_max.download);
draw_graph("upload", 1, shared_max);
draw_graph("download", -1, shared_max);
}
}
private on_mouse_move(event: MouseEvent) {
const offset = event.offsetX;
const max_offset = this.canvas.width;
if(offset < 0) return;
if(offset > max_offset) return;
const time_span = this.calculate_time_span();
const time = time_span.begin + (time_span.end - time_span.begin) * (offset / max_offset);
let index = 0;
for(;index < this._entries.length; index++) {
if(this._entries[index].timestamp > time)
break;
}
const entry_before = this._entries[index - 1]; /* In JS negative array access is allowed and returns undefined */
const entry_next = this._entries[index]; /* In JS negative array access is allowed and returns undefined */
let entry: Entry;
if(!entry_before || !entry_next) {
entry = entry_before || entry_next;
} else {
const dn = entry_next.timestamp - time;
const db = time - entry_before.timestamp;
if(dn > db)
entry = entry_before;
else
entry = entry_next;
}
if(!entry) {
this.on_mouse_leave(event);
} else {
this._entries.forEach(e => e.highlight = false);
this._detailed_shown = true;
entry.highlight = true;
if(this.callback_detailed_info)
this.callback_detailed_info(entry.upload, entry.download, entry.timestamp, event);
}
}
private on_mouse_leave(event: MouseEvent) {
if(!this._detailed_shown) return;
this._detailed_shown = false;
this._entries.forEach(e => e.highlight = false);
if(this.callback_detailed_hide)
this.callback_detailed_hide();
}
}

View file

@ -1,4 +1,6 @@
interface SliderOptions {
import * as tooltip from "tc-shared/ui/elements/Tooltip";
export interface SliderOptions {
min_value?: number;
max_value?: number;
initial_value?: number;
@ -8,11 +10,11 @@ interface SliderOptions {
value_field?: JQuery | JQuery[];
}
interface Slider {
export interface Slider {
value(value?: number) : number;
}
function sliderfy(slider: JQuery, options?: SliderOptions) : Slider {
export function sliderfy(slider: JQuery, options?: SliderOptions) : Slider {
options = Object.assign( {
initial_value: 0,
min_value: 0,
@ -30,7 +32,7 @@ function sliderfy(slider: JQuery, options?: SliderOptions) : Slider {
throw "invalid step size";
const tool = tooltip(slider); /* add the tooltip functionality */
const tool = tooltip.initialize(slider); /* add the tooltip functionality */
const filler = slider.find(".filler");
const thumb = slider.find(".thumb");
const tooltip_text = slider.find(".tooltip a");

View file

@ -1,12 +1,11 @@
/// <reference path="../../i18n/localize.ts" />
declare global {
interface JQuery<TElement = HTMLElement> {
asTabWidget(copy?: boolean) : JQuery<TElement>;
tabify(copy?: boolean) : this;
changeElementType(type: string) : JQuery<TElement>;
}
}
if(typeof (customElements) !== "undefined") {
try {
@ -26,7 +25,7 @@ if(typeof (customElements) !== "undefined") {
console.warn(tr("Could not defied tab customElements!"));
}
var TabFunctions = {
export const TabFunctions = {
tabify(template: JQuery, copy: boolean = true) : JQuery {
console.log("Tabify: copy=" + copy);
console.log(template);

View file

@ -0,0 +1,79 @@
let _global_tooltip: JQuery;
export type Handle = {
show();
is_shown();
hide();
update();
}
export function initialize(entry: JQuery, callbacks?: {
on_show?(tag: JQuery),
on_hide?(tag: JQuery)
}) : Handle {
if(!callbacks) callbacks = {};
let _show;
let _hide;
let _shown;
let _update;
entry.find(".container-tooltip").each((index, _node) => {
const node = $(_node) as JQuery;
const node_content = node.find(".tooltip");
let _force_show = false, _flag_shown = false;
const mouseenter = (event?) => {
const bounds = node[0].getBoundingClientRect();
if(!_global_tooltip) {
_global_tooltip = $("#global-tooltip");
}
_global_tooltip[0].style.left = (bounds.left + bounds.width / 2) + "px";
_global_tooltip[0].style.top = bounds.top + "px";
_global_tooltip[0].classList.add("shown");
_global_tooltip[0].innerHTML = node_content[0].innerHTML;
callbacks.on_show && callbacks.on_show(_global_tooltip);
_flag_shown = _flag_shown || !!event; /* if event is undefined then it has been triggered by hand */
};
const mouseexit = () => {
if(_global_tooltip) {
if(!_force_show) {
callbacks.on_hide && callbacks.on_hide(_global_tooltip);
_global_tooltip[0].classList.remove("shown");
}
_flag_shown = false;
}
};
_node.addEventListener("mouseenter", mouseenter);
_node.addEventListener("mouseleave", mouseexit);
_show = () => {
_force_show = true;
mouseenter();
};
_hide = () => {
_force_show = false;
if(!_flag_shown)
mouseexit();
};
_update = () => {
if(_flag_shown || _force_show)
mouseenter();
};
_shown = () => _flag_shown || _force_show;
});
return {
hide: _hide || (() => {}),
show: _show || (() => {}),
is_shown: _shown || (() => false),
update: _update || (() => {})
};
}

View file

@ -1,435 +0,0 @@
namespace net.graph {
export type Entry = {
timestamp: number;
upload?: number;
download?: number;
highlight?: boolean;
}
export type Style = {
background_color: string;
separator_color: string;
separator_count: number;
separator_width: number;
upload: {
fill: string;
stroke: string;
strike_width: number;
},
download: {
fill: string;
stroke: string;
strike_width: number;
}
}
export type TimeSpan = {
origin: {
begin: number;
end: number;
time: number;
},
target: {
begin: number;
end: number;
time: number;
}
}
/* Great explanation of Bezier curves: http://en.wikipedia.org/wiki/Bezier_curve#Quadratic_curves
*
* Assuming A was the last point in the line plotted and B is the new point,
* we draw a curve with control points P and Q as below.
*
* A---P
* |
* |
* |
* Q---B
*
* Importantly, A and P are at the same y coordinate, as are B and Q. This is
* so adjacent curves appear to flow as one.
*/
export class Graph {
private static _loops: (() => any)[] = [];
readonly canvas: HTMLCanvasElement;
public style: Style = {
background_color: "#28292b",
//background_color: "red",
separator_color: "#283036",
//separator_color: 'blue',
separator_count: 10,
separator_width: 1,
upload: {
fill: "#2d3f4d",
stroke: "#336e9f",
strike_width: 2,
},
download: {
fill: "#532c26",
stroke: "#a9321c",
strike_width: 2,
}
};
private _canvas_context: CanvasRenderingContext2D;
private _entries: Entry[] = [];
private _entry_max = {
upload: 1,
download: 1,
};
private _max_space = 1.12;
private _max_gap = 5;
private _listener_mouse_move;
private _listener_mouse_out;
private _animate_loop;
_time_span: TimeSpan = {
origin: {
begin: 0,
end: 1,
time: 0
},
target: {
begin: 0,
end: 1,
time: 1
}
};
private _detailed_shown = false;
callback_detailed_info: (upload: number, download: number, timestamp: number, event: MouseEvent) => any;
callback_detailed_hide: () => any;
constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas;
this._animate_loop = () => this.draw();
this.recalculate_cache(); /* initialize cache */
}
initialize() {
this._canvas_context = this.canvas.getContext("2d");
Graph._loops.push(this._animate_loop);
if(Graph._loops.length == 1) {
const static_loop = () => {
Graph._loops.forEach(l => l());
if(Graph._loops.length > 0)
requestAnimationFrame(static_loop);
else
console.log("STATIC terminate!");
};
static_loop();
}
this.canvas.onmousemove = this.on_mouse_move.bind(this);
this.canvas.onmouseleave = this.on_mouse_leave.bind(this);
}
terminate() {
Graph._loops.remove(this._animate_loop);
}
max_gap_size(value?: number) : number { return typeof(value) === "number" ? (this._max_gap = value) : this._max_gap; }
private recalculate_cache(time_span?: boolean) {
this._entries = this._entries.sort((a, b) => a.timestamp - b.timestamp);
this._entry_max = {
download: 1,
upload: 1
};
if(time_span) {
this._time_span = {
origin: {
begin: 0,
end: 0,
time: 0
},
target: {
begin: this._entries.length > 0 ? this._entries[0].timestamp : 0,
end: this._entries.length > 0 ? this._entries.last().timestamp : 0,
time: 0
}
};
}
for(const entry of this._entries) {
if(typeof(entry.upload) === "number")
this._entry_max.upload = Math.max(this._entry_max.upload, entry.upload);
if(typeof(entry.download) === "number")
this._entry_max.download = Math.max(this._entry_max.download, entry.download);
}
this._entry_max.upload *= this._max_space;
this._entry_max.download *= this._max_space;
}
insert_entry(entry: Entry) {
if(this._entries.length > 0 && entry.timestamp < this._entries.last().timestamp)
throw "invalid timestamp";
this._entries.push(entry);
if(typeof(entry.upload) === "number")
this._entry_max.upload = Math.max(this._entry_max.upload, entry.upload * this._max_space);
if(typeof(entry.download) === "number")
this._entry_max.download = Math.max(this._entry_max.download, entry.download * this._max_space);
}
insert_entries(entries: Entry[]) {
this._entries.push(...entries);
this.recalculate_cache();
this.cleanup();
}
resize() {
this.canvas.style.height = "100%";
this.canvas.style.width = "100%";
const cstyle = getComputedStyle(this.canvas);
this.canvas.width = parseInt(cstyle.width);
this.canvas.height = parseInt(cstyle.height);
}
cleanup() {
const time = this.calculate_time_span();
let index = 0;
for(;index < this._entries.length; index++) {
if(this._entries[index].timestamp < time.begin)
continue;
if(index == 0)
return;
break;
}
/* keep the last entry as a reference point to the left */
if(index > 1) {
this._entries.splice(0, index - 1);
this.recalculate_cache();
}
}
calculate_time_span() : { begin: number; end: number } {
const time = Date.now();
if(time >= this._time_span.target.time)
return this._time_span.target;
if(time <= this._time_span.origin.time)
return this._time_span.origin;
const ob = this._time_span.origin.begin;
const oe = this._time_span.origin.end;
const ot = this._time_span.origin.time;
const tb = this._time_span.target.begin;
const te = this._time_span.target.end;
const tt = this._time_span.target.time;
const offset = (time - ot) / (tt - ot);
return {
begin: ob + (tb - ob) * offset,
end: oe + (te - oe) * offset,
};
}
draw() {
let ctx = this._canvas_context;
const height = this.canvas.height;
const width = this.canvas.width;
//console.log("Painting on %ox%o", height, width);
ctx.shadowBlur = 0;
ctx.filter = "";
ctx.lineCap = "square";
ctx.fillStyle = this.style.background_color;
ctx.fillRect(0, 0, width, height);
/* first of all print the separators */
{
const sw = this.style.separator_width;
const swh = this.style.separator_width / 2;
ctx.lineWidth = sw;
ctx.strokeStyle = this.style.separator_color;
ctx.beginPath();
/* horizontal */
{
const dw = width / this.style.separator_count;
let dx = dw / 2;
while(dx < width) {
ctx.moveTo(Math.floor(dx - swh) + .5, .5);
ctx.lineTo(Math.floor(dx - swh) + .5, Math.floor(height) + .5);
dx += dw;
}
}
/* vertical */
{
const dh = height / 3; //tree lines (top, center, bottom)
let dy = dh / 2;
while(dy < height) {
ctx.moveTo(.5, Math.floor(dy - swh) + .5);
ctx.lineTo(Math.floor(width) + .5, Math.floor(dy - swh) + .5);
dy += dh;
}
}
ctx.stroke();
ctx.closePath();
}
/* draw the lines */
{
const t = this.calculate_time_span();
const tb = t.begin; /* time begin */
const dt = t.end - t.begin; /* delta time */
const dtw = width / dt; /* delta time width */
const draw_graph = (type: "upload" | "download", direction: number, max: number) => {
const hy = Math.floor(height / 2); /* half y */
const by = hy - direction * this.style[type].strike_width; /* the "base" line */
const marked_points: ({x: number, y: number})[] = [];
ctx.beginPath();
ctx.moveTo(0, by);
let x, y, lx = 0, ly = by; /* last x, last y */
const floor = a => a; //Math.floor;
for(const entry of this._entries) {
x = floor((entry.timestamp - tb) * dtw);
if(typeof entry[type] === "number")
y = floor(hy - direction * Math.max(hy * (entry[type] / max), this.style[type].strike_width));
else
y = hy - direction * this.style[type].strike_width;
if(entry.timestamp < tb) {
lx = x;
ly = y;
continue;
}
if(x - lx > this._max_gap && this._max_gap > 0) {
ctx.lineTo(lx, by);
ctx.lineTo(x, by);
ctx.lineTo(x, y);
lx = x;
ly = y;
continue;
}
ctx.bezierCurveTo((x + lx) / 2, ly, (x + lx) / 2, y, x, y);
if(entry.highlight)
marked_points.push({x: x, y: y});
lx = x;
ly = y;
}
ctx.strokeStyle = this.style[type].stroke;
ctx.lineWidth = this.style[type].strike_width;
ctx.lineJoin = "miter";
ctx.stroke();
//Close the path and fill
ctx.lineTo(width, hy);
ctx.lineTo(0, hy);
ctx.fillStyle = this.style[type].fill;
ctx.fill();
ctx.closePath();
{
ctx.beginPath();
const radius = 3;
for(const point of marked_points) {
ctx.moveTo(point.x, point.y);
ctx.ellipse(point.x, point.y, radius, radius, 0, 0, 2 * Math.PI, false);
}
ctx.stroke();
ctx.fill();
ctx.closePath();
}
};
const shared_max = Math.max(this._entry_max.upload, this._entry_max.download);
draw_graph("upload", 1, shared_max);
draw_graph("download", -1, shared_max);
}
}
private on_mouse_move(event: MouseEvent) {
const offset = event.offsetX;
const max_offset = this.canvas.width;
if(offset < 0) return;
if(offset > max_offset) return;
const time_span = this.calculate_time_span();
const time = time_span.begin + (time_span.end - time_span.begin) * (offset / max_offset);
let index = 0;
for(;index < this._entries.length; index++) {
if(this._entries[index].timestamp > time)
break;
}
const entry_before = this._entries[index - 1]; /* In JS negative array access is allowed and returns undefined */
const entry_next = this._entries[index]; /* In JS negative array access is allowed and returns undefined */
let entry: Entry;
if(!entry_before || !entry_next) {
entry = entry_before || entry_next;
} else {
const dn = entry_next.timestamp - time;
const db = time - entry_before.timestamp;
if(dn > db)
entry = entry_before;
else
entry = entry_next;
}
if(!entry) {
this.on_mouse_leave(event);
} else {
this._entries.forEach(e => e.highlight = false);
this._detailed_shown = true;
entry.highlight = true;
if(this.callback_detailed_info)
this.callback_detailed_info(entry.upload, entry.download, entry.timestamp, event);
}
}
private on_mouse_leave(event: MouseEvent) {
if(!this._detailed_shown) return;
this._detailed_shown = false;
this._entries.forEach(e => e.highlight = false);
if(this.callback_detailed_hide)
this.callback_detailed_hide();
}
}
}

View file

@ -1,85 +0,0 @@
function tooltip(entry: JQuery) {
return tooltip.initialize(entry);
}
namespace tooltip {
let _global_tooltip: JQuery;
export type Handle = {
show();
is_shown();
hide();
update();
}
export function initialize(entry: JQuery, callbacks?: {
on_show?(tag: JQuery),
on_hide?(tag: JQuery)
}) : Handle {
if(!callbacks) callbacks = {};
let _show;
let _hide;
let _shown;
let _update;
entry.find(".container-tooltip").each((index, _node) => {
const node = $(_node) as JQuery;
const node_content = node.find(".tooltip");
let _force_show = false, _flag_shown = false;
const mouseenter = (event?) => {
const bounds = node[0].getBoundingClientRect();
if(!_global_tooltip) {
_global_tooltip = $("#global-tooltip");
}
_global_tooltip[0].style.left = (bounds.left + bounds.width / 2) + "px";
_global_tooltip[0].style.top = bounds.top + "px";
_global_tooltip[0].classList.add("shown");
_global_tooltip[0].innerHTML = node_content[0].innerHTML;
callbacks.on_show && callbacks.on_show(_global_tooltip);
_flag_shown = _flag_shown || !!event; /* if event is undefined then it has been triggered by hand */
};
const mouseexit = () => {
if(_global_tooltip) {
if(!_force_show) {
callbacks.on_hide && callbacks.on_hide(_global_tooltip);
_global_tooltip[0].classList.remove("shown");
}
_flag_shown = false;
}
};
_node.addEventListener("mouseenter", mouseenter);
_node.addEventListener("mouseleave", mouseexit);
_show = () => {
_force_show = true;
mouseenter();
};
_hide = () => {
_force_show = false;
if(!_flag_shown)
mouseexit();
};
_update = () => {
if(_flag_shown || _force_show)
mouseenter();
};
_shown = () => _flag_shown || _force_show;
});
return {
hide: _hide || (() => {}),
show: _show || (() => {}),
is_shown: _shown || (() => false),
update: _update || (() => {})
};
}
}

View file

@ -1,24 +1,40 @@
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../modal/ModalSettings.ts" />
/// <reference path="../modal/ModalBanList.ts" />
/*
client_output_hardware Value: '1'
client_output_muted Value: '0'
client_outputonly_muted Value: '0'
import {ConnectionHandler, DisconnectReason} from "tc-shared/ConnectionHandler";
import {createErrorModal, createInfoModal, createInputModal} from "tc-shared/ui/elements/Modal";
import {manager, Sound} from "tc-shared/sound/Sounds";
import {default_recorder} from "tc-shared/voice/RecorderProfile";
import {Settings, settings} from "tc-shared/settings";
import {spawnSettingsModal} from "tc-shared/ui/modal/ModalSettings";
import {spawnConnectModal} from "tc-shared/ui/modal/ModalConnect";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import PermissionType from "tc-shared/permission/PermissionType";
import {spawnPermissionEdit} from "tc-shared/ui/modal/permission/ModalPermissionEdit";
import {openBanList} from "tc-shared/ui/modal/ModalBanList";
import {
add_current_server,
Bookmark,
bookmarks,
BookmarkType,
boorkmak_connect,
DirectoryBookmark
} from "tc-shared/bookmarks";
import {IconManager} from "tc-shared/FileManager";
import {spawnBookmarkModal} from "tc-shared/ui/modal/ModalBookmarks";
import {spawnQueryCreate} from "tc-shared/ui/modal/ModalQuery";
import {spawnQueryManage} from "tc-shared/ui/modal/ModalQueryManage";
import {spawnPlaylistManage} from "tc-shared/ui/modal/ModalPlaylistList";
import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
import {server_connections} from "tc-shared/ui/frames/connection_handlers";
import {formatMessage} from "tc-shared/ui/frames/chat";
import * as slog from "tc-shared/ui/frames/server_log";
import * as top_menu from "./MenuBar";
client_input_hardware Value: '1'
client_input_muted Value: '0'
export let control_bar: ControlBar; /* global variable to access the control bar */
export function set_control_bar(bar: ControlBar) { control_bar = bar; }
client_away Value: '0'
client_away_message Value: ''
*/
let control_bar: ControlBar; /* global variable to access the control bar */
type MicrophoneState = "disabled" | "muted" | "enabled";
type HeadphoneState = "muted" | "enabled";
type AwayState = "away-global" | "away" | "online";
class ControlBar {
export type MicrophoneState = "disabled" | "muted" | "enabled";
export type HeadphoneState = "muted" | "enabled";
export type AwayState = "away-global" | "away" | "online";
export class ControlBar {
private _button_away_active: AwayState;
private _button_microphone: MicrophoneState;
private _button_speakers: HeadphoneState;
@ -363,10 +379,10 @@ class ControlBar {
private on_toggle_microphone() {
if(this._button_microphone === "disabled" || this._button_microphone === "muted") {
this.button_microphone = "enabled";
sound.manager.play(Sound.MICROPHONE_ACTIVATED);
manager.play(Sound.MICROPHONE_ACTIVATED);
} else {
this.button_microphone = "muted";
sound.manager.play(Sound.MICROPHONE_MUTED);
manager.play(Sound.MICROPHONE_MUTED);
}
if(this.connection_handler) {
@ -384,10 +400,10 @@ class ControlBar {
private on_toggle_sound() {
if(this._button_speakers === "muted") {
this.button_speaker = "enabled";
sound.manager.play(Sound.SOUND_ACTIVATED);
manager.play(Sound.SOUND_ACTIVATED);
} else {
this.button_speaker = "muted";
sound.manager.play(Sound.SOUND_MUTED);
manager.play(Sound.SOUND_MUTED);
}
if(this.connection_handler) {
@ -421,20 +437,20 @@ class ControlBar {
}
private on_open_settings() {
Modals.spawnSettingsModal();
spawnSettingsModal();
}
private on_open_connect() {
if(this.connection_handler)
this.connection_handler.cancel_reconnect(true);
Modals.spawnConnectModal({}, {
spawnConnectModal({}, {
url: "ts.TeaSpeak.de",
enforce: false
});
}
private on_open_connect_new_tab() {
Modals.spawnConnectModal({
spawnConnectModal({
default_connect_new_tab: true
}, {
url: "ts.TeaSpeak.de",
@ -470,7 +486,7 @@ class ControlBar {
this.connection_handler.handleDisconnect(DisconnectReason.REQUESTED); //TODO message?
this.update_connection_state();
this.connection_handler.sound.play(Sound.CONNECTION_DISCONNECTED);
this.connection_handler.log.log(log.server.Type.DISCONNECTED, {});
this.connection_handler.log.log(slog.Type.DISCONNECTED, {});
}
private on_token_use() {
@ -483,7 +499,7 @@ class ControlBar {
createInfoModal(tr("Use token"), tr("Toke successfully used!")).open();
}).catch(error => {
//TODO tr
createErrorModal(tr("Use token"), MessageHelper.formatMessage(tr("Failed to use token: {}"), error instanceof CommandResult ? error.message : error)).open();
createErrorModal(tr("Use token"), formatMessage(tr("Failed to use token: {}"), error instanceof CommandResult ? error.message : error)).open();
});
}).open();
}
@ -497,7 +513,7 @@ class ControlBar {
button.addClass("activated");
setTimeout(() => {
if(this.connection_handler)
Modals.spawnPermissionEdit(this.connection_handler).open();
spawnPermissionEdit(this.connection_handler).open();
else
createErrorModal(tr("You have to be connected"), tr("You have to be connected!")).open();
button.removeClass("activated");
@ -508,7 +524,7 @@ class ControlBar {
if(!this.connection_handler.serverConnection) return;
if(this.connection_handler.permissions.neededPermission(PermissionType.B_CLIENT_BAN_LIST).granted(1)) {
Modals.openBanList(this.connection_handler);
openBanList(this.connection_handler);
} else {
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to view the ban list")).open();
this.connection_handler.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
@ -516,7 +532,7 @@ class ControlBar {
}
private on_bookmark_server_add() {
bookmarks.add_current_server();
add_current_server();
}
update_bookmark_status() {
@ -530,13 +546,13 @@ class ControlBar {
let tag_bookmark = this.htmlTag.find(".btn_bookmark > .dropdown");
tag_bookmark.find(".bookmark, .directory").remove();
const build_entry = (bookmark: bookmarks.DirectoryBookmark | bookmarks.Bookmark) => {
if(bookmark.type == bookmarks.BookmarkType.ENTRY) {
const mark = <bookmarks.Bookmark>bookmark;
const build_entry = (bookmark: DirectoryBookmark | Bookmark) => {
if(bookmark.type == BookmarkType.ENTRY) {
const mark = <Bookmark>bookmark;
const bookmark_connect = (new_tab: boolean) => {
this.htmlTag.find(".btn_bookmark").find(".dropdown").removeClass("displayed"); //FIXME Not working
bookmarks.boorkmak_connect(mark, new_tab);
boorkmak_connect(mark, new_tab);
};
return $.spawn("div")
@ -580,7 +596,7 @@ class ControlBar {
})
)
} else {
const mark = <bookmarks.DirectoryBookmark>bookmark;
const mark = <DirectoryBookmark>bookmark;
const container = $.spawn("div").addClass("sub-menu dropdown");
const result = $.spawn("div")
@ -609,19 +625,19 @@ class ControlBar {
}
};
for(const bookmark of bookmarks.bookmarks().content) {
for(const bookmark of bookmarks().content) {
const entry = build_entry(bookmark);
tag_bookmark.append(entry);
}
}
private on_bookmark_manage() {
Modals.spawnBookmarkModal();
spawnBookmarkModal();
}
private on_open_query_create() {
if(this.connection_handler.permissions.neededPermission(PermissionType.B_CLIENT_CREATE_MODIFY_SERVERQUERY_LOGIN).granted(1)) {
Modals.spawnQueryCreate(this.connection_handler);
spawnQueryCreate(this.connection_handler);
} else {
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to create a server query login")).open();
this.connection_handler.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
@ -630,7 +646,7 @@ class ControlBar {
private on_open_query_manage() {
if(this.connection_handler && this.connection_handler.connected) {
Modals.spawnQueryManage(this.connection_handler);
spawnQueryManage(this.connection_handler);
} else {
createErrorModal(tr("You have to be connected"), tr("You have to be connected!")).open();
}
@ -638,7 +654,7 @@ class ControlBar {
private on_open_playlist_manage() {
if(this.connection_handler && this.connection_handler.connected) {
Modals.spawnPlaylistManage(this.connection_handler);
spawnPlaylistManage(this.connection_handler);
} else {
createErrorModal(tr("You have to be connected"), tr("You have to be connected to use this function!")).open();
}

View file

@ -1,4 +1,31 @@
namespace top_menu {
import {Icon, IconManager} from "tc-shared/FileManager";
import {spawnBookmarkModal} from "tc-shared/ui/modal/ModalBookmarks";
import {
add_current_server,
Bookmark,
bookmarks,
BookmarkType,
boorkmak_connect,
DirectoryBookmark
} from "tc-shared/bookmarks";
import {ConnectionHandler, DisconnectReason} from "tc-shared/ConnectionHandler";
import {Sound} from "tc-shared/sound/Sounds";
import {spawnConnectModal} from "tc-shared/ui/modal/ModalConnect";
import {spawnPermissionEdit} from "tc-shared/ui/modal/permission/ModalPermissionEdit";
import {createErrorModal, createInfoModal, createInputModal} from "tc-shared/ui/elements/Modal";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import PermissionType from "tc-shared/permission/PermissionType";
import {openBanList} from "tc-shared/ui/modal/ModalBanList";
import {spawnQueryManage} from "tc-shared/ui/modal/ModalQueryManage";
import {spawnQueryCreate} from "tc-shared/ui/modal/ModalQuery";
import {spawnSettingsModal} from "tc-shared/ui/modal/ModalSettings";
import {spawnAbout} from "tc-shared/ui/modal/ModalAbout";
import {server_connections} from "tc-shared/ui/frames/connection_handlers";
import {control_bar} from "tc-shared/ui/frames/ControlBar";
import * as loader from "tc-loader";
import {formatMessage} from "tc-shared/ui/frames/chat";
import * as slog from "tc-shared/ui/frames/server_log";
export interface HRItem { }
export interface MenuItem {
@ -47,7 +74,7 @@ namespace top_menu {
export let native_actions: NativeActions;
namespace html {
class HTMLHrItem implements top_menu.HRItem {
class HTMLHrItem implements HRItem {
readonly html_tag: JQuery;
constructor() {
@ -55,7 +82,7 @@ namespace top_menu {
}
}
class HTMLMenuItem implements top_menu.MenuItem {
class HTMLMenuItem implements MenuItem {
readonly html_tag: JQuery;
readonly _label_tag: JQuery;
readonly _label_icon_tag: JQuery;
@ -99,7 +126,7 @@ namespace top_menu {
this.html_tag.append(this._submenu_tag);
}
append_item(label: string): top_menu.MenuItem {
append_item(label: string): MenuItem {
const item = new HTMLMenuItem(label, "side");
this._items.push(item);
this._submenu_tag.append(item.html_tag);
@ -114,7 +141,7 @@ namespace top_menu {
return item;
}
delete_item(item: top_menu.MenuItem | top_menu.HRItem) {
delete_item(item: MenuItem | HRItem) {
this._items.remove(item);
(item as any).html_tag.detach();
this.html_tag.toggleClass('sub-entries', this._items.length > 0);
@ -128,7 +155,7 @@ namespace top_menu {
return value;
}
items(): (top_menu.MenuItem | top_menu.HRItem)[] {
items(): (MenuItem | HRItem)[] {
return this._items;
}
@ -178,7 +205,7 @@ namespace top_menu {
this.html_tag = $.spawn("div").addClass("top-menu-bar");
}
append_item(label: string): top_menu.MenuItem {
append_item(label: string): MenuItem {
const item = new HTMLMenuItem(label, "down");
this._items.push(item);
@ -209,7 +236,7 @@ namespace top_menu {
return undefined;
}
items(): top_menu.MenuItem[] {
items(): MenuItem[] {
return this._items;
}
@ -237,11 +264,11 @@ namespace top_menu {
};
_items_bookmark.manage = _items_bookmark.root.append_item(tr("Manage bookmarks"));
_items_bookmark.manage.icon("client-bookmark_manager");
_items_bookmark.manage.click(() => Modals.spawnBookmarkModal());
_items_bookmark.manage.click(() => spawnBookmarkModal());
_items_bookmark.add_current = _items_bookmark.root.append_item(tr("Add current server to bookmarks"));
_items_bookmark.add_current.icon('client-bookmark_add');
_items_bookmark.add_current.click(() => bookmarks.add_current_server());
_items_bookmark.add_current.click(() => add_current_server());
_state_updater["bookmarks.ac"] = { item: _items_bookmark.add_current, conditions: [condition_connected]};
}
@ -250,9 +277,9 @@ namespace top_menu {
});
_items_bookmark.root.append_hr();
const build_bookmark = (root: MenuItem, entry: bookmarks.DirectoryBookmark | bookmarks.Bookmark) => {
if(entry.type == bookmarks.BookmarkType.DIRECTORY) {
const directory = entry as bookmarks.DirectoryBookmark;
const build_bookmark = (root: MenuItem, entry: DirectoryBookmark | Bookmark) => {
if(entry.type == BookmarkType.DIRECTORY) {
const directory = entry as DirectoryBookmark;
const item = root.append_item(directory.display_name);
item.icon('client-folder');
for(const entry of directory.content)
@ -260,14 +287,14 @@ namespace top_menu {
if(directory.content.length == 0)
item.disabled(true);
} else {
const bookmark = entry as bookmarks.Bookmark;
const bookmark = entry as Bookmark;
const item = root.append_item(bookmark.display_name);
item.icon(IconManager.load_cached_icon(bookmark.last_icon_id || 0));
item.click(() => bookmarks.boorkmak_connect(bookmark));
item.click(() => boorkmak_connect(bookmark));
}
};
for(const entry of bookmarks.bookmarks().content)
for(const entry of bookmarks().content)
build_bookmark(_items_bookmark.root, entry);
driver().flush_changes();
}
@ -302,16 +329,16 @@ namespace top_menu {
}
export function initialize() {
const driver = top_menu.driver();
driver.initialize();
const driver_ = driver();
driver_.initialize();
/* build connection */
let item: MenuItem;
{
const menu = driver.append_item(tr("Connection"));
const menu = driver_.append_item(tr("Connection"));
item = menu.append_item(tr("Connect to a server"));
item.icon('client-connect');
item.click(() => Modals.spawnConnectModal({}));
item.click(() => spawnConnectModal({}));
const do_disconnect = (handlers: ConnectionHandler[]) => {
for(const handler of handlers) {
@ -319,7 +346,7 @@ namespace top_menu {
handler.handleDisconnect(DisconnectReason.REQUESTED); //TODO message?
server_connections.active_connection_handler().serverConnection.disconnect();
handler.sound.play(Sound.CONNECTION_DISCONNECTED);
handler.log.log(log.server.Type.DISCONNECTED, {});
handler.log.log(slog.Type.DISCONNECTED, {});
}
control_bar.update_connection_state();
update_state();
@ -343,7 +370,7 @@ namespace top_menu {
return true;
}};
if(!app.is_web()) {
if(loader.version().type !== "web") {
menu.append_hr();
item = menu.append_item(tr("Quit"));
@ -356,45 +383,45 @@ namespace top_menu {
}
if(false) {
const menu = driver.append_item(tr("Self"));
const menu = driver_.append_item(tr("Self"));
/* Microphone | Sound | Away */
}
{
const menu = driver.append_item(tr("Permissions"));
const menu = driver_.append_item(tr("Permissions"));
item = menu.append_item(tr("Server Groups"));
item.icon("client-permission_server_groups");
item.click(() => {
Modals.spawnPermissionEdit(server_connections.active_connection_handler(), "sg").open();
spawnPermissionEdit(server_connections.active_connection_handler(), "sg").open();
});
_state_updater["permission.sg"] = { item: item, conditions: [condition_connected]};
item = menu.append_item(tr("Client Permissions"));
item.icon("client-permission_client");
item.click(() => {
Modals.spawnPermissionEdit(server_connections.active_connection_handler(), "clp").open();
spawnPermissionEdit(server_connections.active_connection_handler(), "clp").open();
});
_state_updater["permission.clp"] = { item: item, conditions: [condition_connected]};
item = menu.append_item(tr("Channel Client Permissions"));
item.icon("client-permission_client");
item.click(() => {
Modals.spawnPermissionEdit(server_connections.active_connection_handler(), "clchp").open();
spawnPermissionEdit(server_connections.active_connection_handler(), "clchp").open();
});
_state_updater["permission.chclp"] = { item: item, conditions: [condition_connected]};
item = menu.append_item(tr("Channel Groups"));
item.icon("client-permission_channel");
item.click(() => {
Modals.spawnPermissionEdit(server_connections.active_connection_handler(), "cg").open();
spawnPermissionEdit(server_connections.active_connection_handler(), "cg").open();
});
_state_updater["permission.cg"] = { item: item, conditions: [condition_connected]};
item = menu.append_item(tr("Channel Permissions"));
item.icon("client-permission_channel");
item.click(() => {
Modals.spawnPermissionEdit(server_connections.active_connection_handler(), "chp").open();
spawnPermissionEdit(server_connections.active_connection_handler(), "chp").open();
});
_state_updater["permission.cp"] = { item: item, conditions: [condition_connected]};
@ -421,7 +448,7 @@ namespace top_menu {
createInfoModal(tr("Use token"), tr("Toke successfully used!")).open();
}).catch(error => {
//TODO tr
createErrorModal(tr("Use token"), MessageHelper.formatMessage(tr("Failed to use token: {}"), error instanceof CommandResult ? error.message : error)).open();
createErrorModal(tr("Use token"), formatMessage(tr("Failed to use token: {}"), error instanceof CommandResult ? error.message : error)).open();
});
}).open();
});
@ -429,7 +456,7 @@ namespace top_menu {
}
{
const menu = driver.append_item(tr("Tools"));
const menu = driver_.append_item(tr("Tools"));
/*
item = menu.append_item(tr("Manage Playlists"));
@ -451,7 +478,7 @@ namespace top_menu {
const scon = server_connections.active_connection_handler();
if(scon && scon.connected) {
if(scon.permissions.neededPermission(PermissionType.B_CLIENT_BAN_LIST).granted(1)) {
Modals.openBanList(scon);
openBanList(scon);
} else {
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to view the ban list")).open();
scon.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
@ -468,7 +495,7 @@ namespace top_menu {
const scon = server_connections.active_connection_handler();
if(scon && scon.connected) {
if(scon.permissions.neededPermission(PermissionType.B_CLIENT_QUERY_LIST).granted(1) || scon.permissions.neededPermission(PermissionType.B_CLIENT_QUERY_LIST_OWN).granted(1)) {
Modals.spawnQueryManage(scon);
spawnQueryManage(scon);
} else {
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to view the server query list")).open();
scon.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
@ -485,7 +512,7 @@ namespace top_menu {
const scon = server_connections.active_connection_handler();
if(scon && scon.connected) {
if(scon.permissions.neededPermission(PermissionType.B_CLIENT_CREATE_MODIFY_SERVERQUERY_LOGIN).granted(1) || scon.permissions.neededPermission(PermissionType.B_CLIENT_QUERY_CREATE).granted(1)) {
Modals.spawnQueryCreate(scon);
spawnQueryCreate(scon);
} else {
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to create a server query login")).open();
scon.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
@ -499,13 +526,13 @@ namespace top_menu {
item = menu.append_item(tr("Settings"));
item.icon("client-settings");
item.click(() => Modals.spawnSettingsModal());
item.click(() => spawnSettingsModal());
}
{
const menu = driver.append_item(tr("Help"));
const menu = driver_.append_item(tr("Help"));
if(!app.is_web()) {
if(loader.version().type !== "web") {
item = menu.append_item(tr("Check for updates"));
item.click(() => native_actions.check_native_update());
@ -519,7 +546,7 @@ namespace top_menu {
item = menu.append_item(tr("Visit TeaSpeak forum"));
item.click(() => window.open('https://forum.teaspeak.de/', '_blank'));
if(!app.is_web() && typeof(native_actions.show_dev_tools) === "function" && native_actions.show_dev_tools()) {
if(loader.version().type !== "web" && typeof(native_actions.show_dev_tools) === "function" && native_actions.show_dev_tools()) {
menu.append_hr();
item = menu.append_item(tr("Open developer tools"));
item.click(() => native_actions.open_dev_tools());
@ -529,13 +556,19 @@ namespace top_menu {
}
menu.append_hr();
item = menu.append_item(app.is_web() ? tr("About TeaWeb") : tr("About TeaClient"));
item.click(() => Modals.spawnAbout())
item = menu.append_item(loader.version().type === "web" ? tr("About TeaWeb") : tr("About TeaClient"));
item.click(() => spawnAbout())
}
update_state();
}
/* default is HTML, the client will override this */
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
function: async () => {
if(!driver())
set_driver(html.HTMLMenuBarDriver.instance());
}
},
priority: 100,
name: "Menu bar init"
});

View file

@ -1,4 +1,10 @@
enum ChatType {
import {LogCategory} from "tc-shared/log";
import {settings, Settings} from "tc-shared/settings";
import * as log from "tc-shared/log";
import {bbcode} from "tc-shared/MessageFormatter";
import * as loader from "tc-loader";
export enum ChatType {
GENERAL,
SERVER,
CHANNEL,
@ -6,7 +12,6 @@ enum ChatType {
}
declare const xbbcode: any;
namespace MessageHelper {
export function htmlEscape(message: string) : string[] {
const div = document.createElement('div');
div.innerText = message;
@ -92,7 +97,7 @@ namespace MessageHelper {
//TODO: Remove this (only legacy)
export function bbcode_chat(message: string) : JQuery[] {
return messages.formatter.bbcode.format(message, {
return bbcode.format(message, {
is_chat_message: true
});
}
@ -243,8 +248,7 @@ namespace MessageHelper {
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "icon size init",
function: async () => {
MessageHelper.set_icon_size((settings.static_global(Settings.KEY_ICON_SIZE) / 100).toFixed(2) + "em");
set_icon_size((settings.static_global(Settings.KEY_ICON_SIZE) / 100).toFixed(2) + "em");
},
priority: 10
});
}

View file

@ -1,5 +1,15 @@
/* the bar on the right with the chats (Channel & Client) */
namespace chat {
import {ClientEntry, MusicClientEntry} from "tc-shared/ui/client";
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {ChannelEntry} from "tc-shared/ui/channel";
import {ServerEntry} from "tc-shared/ui/server";
import {openMusicManage} from "tc-shared/ui/modal/ModalMusicManage";
import {formatMessage} from "tc-shared/ui/frames/chat";
import {PrivateConverations} from "tc-shared/ui/frames/side/private_conversations";
import {ClientInfo} from "tc-shared/ui/frames/side/client_info";
import {MusicInfo} from "tc-shared/ui/frames/side/music_info";
import {ConversationManager} from "tc-shared/ui/frames/side/conversations";
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
@ -69,7 +79,7 @@ namespace chat {
const bot = this.handle.music_info().current_bot();
if(!bot) return;
Modals.openMusicManage(this.handle.handle, bot);
openMusicManage(this.handle.handle, bot);
});
this._button_song_add = this._html_tag.find(".bot-add-song").on('click', event => {
this.handle.music_info().events.fire("action_song_add");
@ -157,7 +167,7 @@ namespace chat {
this.update_channel_limit(channel, html_limit_tag);
} else if(channel_tree && current_channel_id > 0) {
html_tag.append(MessageHelper.formatMessage(tr("Unknown channel id {}"), current_channel_id));
html_tag.append(formatMessage(tr("Unknown channel id {}"), current_channel_id));
} else if(channel_tree && current_channel_id == 0) {
const server = this.handle.handle.channelTree.server;
if(server.properties.virtualserver_icon_id != 0)
@ -274,7 +284,7 @@ namespace chat {
private _conversations: PrivateConverations;
private _client_info: ClientInfo;
private _music_info: MusicInfo;
private _channel_conversations: channel.ConversationManager;
private _channel_conversations: ConversationManager;
constructor(handle: ConnectionHandler) {
this.handle = handle;
@ -282,7 +292,7 @@ namespace chat {
this._content_type = FrameContent.NONE;
this._info_frame = new InfoFrame(this);
this._conversations = new PrivateConverations(this);
this._channel_conversations = new channel.ConversationManager(this);
this._channel_conversations = new ConversationManager(this);
this._client_info = new ClientInfo(this);
this._music_info = new MusicInfo(this);
@ -335,7 +345,7 @@ namespace chat {
return this._conversations;
}
channel_conversations() : channel.ConversationManager {
channel_conversations() : ConversationManager {
return this._channel_conversations;
}
@ -414,4 +424,3 @@ namespace chat {
}
}
}
}

View file

@ -1,7 +1,13 @@
import {ConnectionHandler, DisconnectReason} from "tc-shared/ConnectionHandler";
import {Settings, settings} from "tc-shared/settings";
import {control_bar} from "tc-shared/ui/frames/ControlBar";
import * as top_menu from "./MenuBar";
let server_connections: ServerConnectionManager;
class ServerConnectionManager {
export let server_connections: ServerConnectionManager;
export function initialize(manager: ServerConnectionManager) {
server_connections = manager;
}
export class ServerConnectionManager {
private connection_handlers: ConnectionHandler[] = [];
private active_handler: ConnectionHandler | undefined;

View file

@ -1,4 +1,9 @@
class Hostbanner {
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {settings, Settings} from "tc-shared/settings";
import {LogCategory} from "tc-shared/log";
import * as log from "tc-shared/log";
export class Hostbanner {
readonly html_tag: JQuery<HTMLElement>;
readonly client: ConnectionHandler;

View file

@ -1,4 +1,5 @@
namespace image_preview {
import * as loader from "tc-loader";
let preview_overlay: JQuery<HTMLDivElement>;
let container_image: JQuery<HTMLDivElement>;
let button_open_in_browser: JQuery;
@ -78,4 +79,3 @@ namespace image_preview {
});
}
});
}

View file

@ -1,5 +1,10 @@
namespace log {
export namespace server {
import {tra} from "tc-shared/i18n/localize";
import {PermissionInfo} from "tc-shared/permission/PermissionManager";
import {ConnectionHandler, ViewReasonId} from "tc-shared/ConnectionHandler";
import * as htmltags from "tc-shared/ui/htmltags";
import {bbcode_chat, format_time, formatMessage} from "tc-shared/ui/frames/chat";
import {formatDate} from "tc-shared/MessageFormatter";
export enum Type {
CONNECTION_BEGIN = "connection_begin",
CONNECTION_HOSTNAME_RESOLVE = "connection_hostname_resolve",
@ -253,20 +258,19 @@ namespace log {
}
export type MessageBuilderOptions = {};
export type MessageBuilder<T extends keyof server.TypeInfo> = (data: TypeInfo[T], options: MessageBuilderOptions) => JQuery[] | undefined;
export type MessageBuilder<T extends keyof TypeInfo> = (data: TypeInfo[T], options: MessageBuilderOptions) => JQuery[] | undefined;
export const MessageBuilders: {[key: string]: MessageBuilder<any>} = {
"error_custom": (data: event.ErrorCustom, options) => {
return [$.spawn("div").addClass("log-error").text(data.message)]
}
};
}
export class ServerLog {
private readonly handle: ConnectionHandler;
private history_length: number = 100;
private _log: server.LogMessage[] = [];
private _log: LogMessage[] = [];
private _html_tag: JQuery;
private _log_container: JQuery;
private auto_follow: boolean; /* automatic scroll to bottom */
@ -290,7 +294,7 @@ namespace log {
});
}
log<T extends keyof server.TypeInfo>(type: T, data: server.TypeInfo[T]) {
log<T extends keyof TypeInfo>(type: T, data: TypeInfo[T]) {
const event = {
data: data,
timestamp: Date.now(),
@ -318,7 +322,7 @@ namespace log {
private _scroll_task: number;
private append_log(message: server.LogMessage) {
private append_log(message: LogMessage) {
let container = $.spawn("div").addClass("log-message");
/* build timestamp */
@ -333,9 +337,9 @@ namespace log {
/* build message data */
{
const builder = server.MessageBuilders[message.type];
const builder = MessageBuilders[message.type];
if(!builder) {
MessageHelper.formatMessage(tr("missing log message builder {0}!"), message.type).forEach(e => e.addClass("log-error").appendTo(container));
formatMessage(tr("missing log message builder {0}!"), message.type).forEach(e => e.addClass("log-error").appendTo(container));
} else {
const elements = builder(message.data, {});
if(!elements || elements.length == 0)
@ -365,12 +369,8 @@ namespace log {
}
}
}
}
/* impl of the parsers */
namespace log {
export namespace server {
namespace impl {
const client_tag = (client: base.Client, braces?: boolean) => htmltags.generate_client_object({
client_unique_id: client.client_unique_id,
client_id: client.client_id,
@ -385,23 +385,23 @@ namespace log {
});
MessageBuilders["connection_begin"] = (data: event.ConnectBegin, options) => {
return MessageHelper.formatMessage(tr("Connecting to {0}{1}"), data.address.server_hostname, data.address.server_port == 9987 ? "" : (":" + data.address.server_port));
return formatMessage(tr("Connecting to {0}{1}"), data.address.server_hostname, data.address.server_port == 9987 ? "" : (":" + data.address.server_port));
};
MessageBuilders["connection_hostname_resolve"] = (data: event.ConnectionHostnameResolve, options) => MessageHelper.formatMessage(tr("Resolving hostname"));
MessageBuilders["connection_hostname_resolved"] = (data: event.ConnectionHostnameResolved, options) => MessageHelper.formatMessage(tr("Hostname resolved successfully to {0}:{1}"), data.address.server_hostname, data.address.server_port);
MessageBuilders["connection_hostname_resolve_error"] = (data: event.ConnectionHostnameResolveError, options) => MessageHelper.formatMessage(tr("Failed to resolve hostname. Connecting to given hostname. Error: {0}"), data.message);
MessageBuilders["connection_hostname_resolve"] = (data: event.ConnectionHostnameResolve, options) => formatMessage(tr("Resolving hostname"));
MessageBuilders["connection_hostname_resolved"] = (data: event.ConnectionHostnameResolved, options) => formatMessage(tr("Hostname resolved successfully to {0}:{1}"), data.address.server_hostname, data.address.server_port);
MessageBuilders["connection_hostname_resolve_error"] = (data: event.ConnectionHostnameResolveError, options) => formatMessage(tr("Failed to resolve hostname. Connecting to given hostname. Error: {0}"), data.message);
MessageBuilders["connection_login"] = (data: event.ConnectionLogin, options) => MessageHelper.formatMessage(tr("Logging in..."));
MessageBuilders["connection_failed"] = (data: event.ConnectionFailed, options) => MessageHelper.formatMessage(tr("Connect failed."));
MessageBuilders["connection_connected"] = (data: event.ConnectionConnected, options) => MessageHelper.formatMessage(tr("Connected as {0}"), client_tag(data.own_client, true));
MessageBuilders["connection_login"] = (data: event.ConnectionLogin, options) => formatMessage(tr("Logging in..."));
MessageBuilders["connection_failed"] = (data: event.ConnectionFailed, options) => formatMessage(tr("Connect failed."));
MessageBuilders["connection_connected"] = (data: event.ConnectionConnected, options) => formatMessage(tr("Connected as {0}"), client_tag(data.own_client, true));
MessageBuilders["connection_voice_setup_failed"] = (data: event.ConnectionVoiceSetupFailed, options) => {
return MessageHelper.formatMessage(tr("Failed to setup voice bridge: {0}. Allow reconnect: {1}"), data.reason, data.reconnect_delay > 0 ? tr("yes") : tr("no"));
return formatMessage(tr("Failed to setup voice bridge: {0}. Allow reconnect: {1}"), data.reason, data.reconnect_delay > 0 ? tr("yes") : tr("no"));
};
MessageBuilders["error_permission"] = (data: event.ErrorPermission, options) => {
return MessageHelper.formatMessage(tr("Insufficient client permissions. Failed on permission {0}"), data.permission ? data.permission.name : "unknown").map(e => e.addClass("log-error"));
return formatMessage(tr("Insufficient client permissions. Failed on permission {0}"), data.permission ? data.permission.name : "unknown").map(e => e.addClass("log-error"));
};
MessageBuilders["client_view_enter"] = (data: event.ClientEnter, options) => {
@ -410,20 +410,20 @@ namespace log {
} if(data.reason == ViewReasonId.VREASON_USER_ACTION) {
/* client appeared */
if(data.channel_from) {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} appeared from {1} to your {2}") : tr("{0} appeared from {1} to {2}"), client_tag(data.client), channel_tag(data.channel_from), channel_tag(data.channel_to));
return formatMessage(data.own_channel ? tr("{0} appeared from {1} to your {2}") : tr("{0} appeared from {1} to {2}"), client_tag(data.client), channel_tag(data.channel_from), channel_tag(data.channel_to));
} else {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} connected to your channel {1}") : tr("{0} connected to channel {1}"), client_tag(data.client), channel_tag(data.channel_to));
return formatMessage(data.own_channel ? tr("{0} connected to your channel {1}") : tr("{0} connected to channel {1}"), client_tag(data.client), channel_tag(data.channel_to));
}
} else if(data.reason == ViewReasonId.VREASON_MOVED) {
if(data.channel_from) {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} appeared from {1} to your channel {2}, moved by {3}") : tr("{0} appeared from {1} to {2}, moved by {3}"),
return formatMessage(data.own_channel ? tr("{0} appeared from {1} to your channel {2}, moved by {3}") : tr("{0} appeared from {1} to {2}, moved by {3}"),
client_tag(data.client),
channel_tag(data.channel_from),
channel_tag(data.channel_to),
client_tag(data.invoker)
);
} else {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} appeared to your channel {1}, moved by {2}") : tr("{0} appeared to {1}, moved by {2}"),
return formatMessage(data.own_channel ? tr("{0} appeared to your channel {1}, moved by {2}") : tr("{0} appeared to {1}, moved by {2}"),
client_tag(data.client),
channel_tag(data.channel_to),
client_tag(data.invoker)
@ -431,7 +431,7 @@ namespace log {
}
} else if(data.reason == ViewReasonId.VREASON_CHANNEL_KICK) {
if(data.channel_from) {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} appeared from {1} to your channel {2}, kicked by {3}{4}") : tr("{0} appeared from {1} to {2}, kicked by {3}{4}"),
return formatMessage(data.own_channel ? tr("{0} appeared from {1} to your channel {2}, kicked by {3}{4}") : tr("{0} appeared from {1} to {2}, kicked by {3}{4}"),
client_tag(data.client),
channel_tag(data.channel_from),
channel_tag(data.channel_to),
@ -439,7 +439,7 @@ namespace log {
data.message ? (" (" + data.message + ")") : ""
);
} else {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} appeared to your channel {1}, kicked by {2}{3}") : tr("{0} appeared to {1}, kicked by {2}{3}"),
return formatMessage(data.own_channel ? tr("{0} appeared to your channel {1}, kicked by {2}{3}") : tr("{0} appeared to {1}, kicked by {2}{3}"),
client_tag(data.client),
channel_tag(data.channel_to),
client_tag(data.invoker),
@ -452,20 +452,20 @@ namespace log {
MessageBuilders["client_view_move"] = (data: event.ClientMove, options) => {
if(data.reason == ViewReasonId.VREASON_MOVED) {
return MessageHelper.formatMessage(data.client_own ? tr("You was moved by {3} from channel {1} to {2}") : tr("{0} was moved from channel {1} to {2} by {3}"),
return formatMessage(data.client_own ? tr("You was moved by {3} from channel {1} to {2}") : tr("{0} was moved from channel {1} to {2} by {3}"),
client_tag(data.client),
channel_tag(data.channel_from),
channel_tag(data.channel_to),
client_tag(data.invoker)
);
} else if(data.reason == ViewReasonId.VREASON_USER_ACTION) {
return MessageHelper.formatMessage(data.client_own ? tr("You switched from channel {1} to {2}") : tr("{0} switched from channel {1} to {2}"),
return formatMessage(data.client_own ? tr("You switched from channel {1} to {2}") : tr("{0} switched from channel {1} to {2}"),
client_tag(data.client),
channel_tag(data.channel_from),
channel_tag(data.channel_to)
);
} else if(data.reason == ViewReasonId.VREASON_CHANNEL_KICK) {
return MessageHelper.formatMessage(data.client_own ? tr("You got kicked out of the channel {1} to channel {2} by {3}{4}") : tr("{0} got kicked from channel {1} to {2} by {3}{4}"),
return formatMessage(data.client_own ? tr("You got kicked out of the channel {1} to channel {2} by {3}{4}") : tr("{0} got kicked from channel {1} to {2} by {3}{4}"),
client_tag(data.client),
channel_tag(data.channel_from),
channel_tag(data.channel_to),
@ -478,13 +478,13 @@ namespace log {
MessageBuilders["client_view_leave"] = (data: event.ClientLeave, options) => {
if(data.reason == ViewReasonId.VREASON_USER_ACTION) {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} disappeared from your channel {1} to {2}") : tr("{0} disappeared from {1} to {2}"), client_tag(data.client), channel_tag(data.channel_from), channel_tag(data.channel_to));
return formatMessage(data.own_channel ? tr("{0} disappeared from your channel {1} to {2}") : tr("{0} disappeared from {1} to {2}"), client_tag(data.client), channel_tag(data.channel_from), channel_tag(data.channel_to));
} else if(data.reason == ViewReasonId.VREASON_SERVER_LEFT) {
return MessageHelper.formatMessage(tr("{0} left the server{1}"), client_tag(data.client), data.message ? (" (" + data.message + ")") : "");
return formatMessage(tr("{0} left the server{1}"), client_tag(data.client), data.message ? (" (" + data.message + ")") : "");
} else if(data.reason == ViewReasonId.VREASON_SERVER_KICK) {
return MessageHelper.formatMessage(tr("{0} was kicked from the server by {1}.{2}"), client_tag(data.client), client_tag(data.invoker), data.message ? (" (" + data.message + ")") : "");
return formatMessage(tr("{0} was kicked from the server by {1}.{2}"), client_tag(data.client), client_tag(data.invoker), data.message ? (" (" + data.message + ")") : "");
} else if(data.reason == ViewReasonId.VREASON_CHANNEL_KICK) {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} was kicked from your channel by {2}.{3}") : tr("{0} was kicked from channel {1} by {2}.{3}"),
return formatMessage(data.own_channel ? tr("{0} was kicked from your channel by {2}.{3}") : tr("{0} was kicked from channel {1} by {2}.{3}"),
client_tag(data.client),
channel_tag(data.channel_from),
client_tag(data.invoker),
@ -494,34 +494,34 @@ namespace log {
let duration = "permanently";
if(data.ban_time)
duration = "for " + formatDate(data.ban_time);
return MessageHelper.formatMessage(tr("{0} was banned {1} by {2}.{3}"),
return formatMessage(tr("{0} was banned {1} by {2}.{3}"),
client_tag(data.client),
duration,
client_tag(data.invoker),
data.message ? (" (" + data.message + ")") : ""
);
} else if(data.reason == ViewReasonId.VREASON_TIMEOUT) {
return MessageHelper.formatMessage(tr("{0} timed out{1}"), client_tag(data.client), data.message ? (" (" + data.message + ")") : "");
return formatMessage(tr("{0} timed out{1}"), client_tag(data.client), data.message ? (" (" + data.message + ")") : "");
} else if(data.reason == ViewReasonId.VREASON_MOVED) {
return MessageHelper.formatMessage(data.own_channel ? tr("{0} disappeared from your channel {1} to {2}, moved by {3}") : tr("{0} disappeared from {1} to {2}, moved by {3}"), client_tag(data.client), channel_tag(data.channel_from), channel_tag(data.channel_to), client_tag(data.invoker));
return formatMessage(data.own_channel ? tr("{0} disappeared from your channel {1} to {2}, moved by {3}") : tr("{0} disappeared from {1} to {2}, moved by {3}"), client_tag(data.client), channel_tag(data.channel_from), channel_tag(data.channel_to), client_tag(data.invoker));
}
return [$.spawn("div").addClass("log-error").text("Invalid view leave reason id (" + data.reason + ")")];
};
MessageBuilders["server_welcome_message"] = (data: event.WelcomeMessage, options) => {
return MessageHelper.bbcode_chat("[color=green]" + data.message + "[/color]");
return bbcode_chat("[color=green]" + data.message + "[/color]");
};
MessageBuilders["server_host_message"] = (data: event.WelcomeMessage, options) => {
return MessageHelper.bbcode_chat("[color=green]" + data.message + "[/color]");
return bbcode_chat("[color=green]" + data.message + "[/color]");
};
MessageBuilders["client_nickname_changed"] = (data: event.ClientNicknameChanged, options) => {
if(data.own_client) {
return MessageHelper.formatMessage(tr("Nickname successfully changed."));
return formatMessage(tr("Nickname successfully changed."));
} else {
return MessageHelper.formatMessage(tr("{0} changed his nickname from \"{1}\" to \"{2}\""), client_tag(data.client), data.old_name, data.new_name);
return formatMessage(tr("{0} changed his nickname from \"{1}\" to \"{2}\""), client_tag(data.client), data.old_name, data.new_name);
}
};
@ -529,10 +529,10 @@ namespace log {
return []; /* we do not show global messages within log */
};
MessageBuilders["disconnected"] = () => MessageHelper.formatMessage(tr("Disconnected from server"));
MessageBuilders["disconnected"] = () => formatMessage(tr("Disconnected from server"));
MessageBuilders["reconnect_scheduled"] = (data: event.ReconnectScheduled, options) => {
return tra("Reconnecting in {0}.", MessageHelper.format_time(data.timeout, tr("now")))
return tra("Reconnecting in {0}.", format_time(data.timeout, tr("now")))
};
MessageBuilders["reconnect_canceled"] = (data: event.ReconnectCanceled, options) => {
@ -546,7 +546,7 @@ namespace log {
MessageBuilders["server_banned"] = (data: event.ServerBanned, options) => {
let result: JQuery[];
const time = data.time == 0 ? tr("ever") : MessageHelper.format_time(data.time * 1000, tr("one second"));
const time = data.time == 0 ? tr("ever") : format_time(data.time * 1000, tr("one second"));
if(data.invoker.client_id > 0) {
if(data.message)
result = tra("You've been banned from the server by {0} for {1}. Reason: {2}", client_tag(data.invoker), time, data.message);
@ -561,6 +561,3 @@ namespace log {
return result.map(e => e.addClass("log-error"));
};
}
}
}

View file

@ -1,4 +1,6 @@
namespace chat {
import {Settings, settings} from "tc-shared/settings";
import {helpers} from "tc-shared/ui/frames/side/chat_helper";
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
@ -264,4 +266,3 @@ namespace chat {
this._html_input.focus();
}
}
}

View file

@ -1,4 +1,8 @@
namespace chat {
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import {Settings, settings} from "tc-shared/settings";
declare const xbbcode;
export namespace helpers {
//https://regex101.com/r/YQbfcX/2
//static readonly URL_REGEX = /^(?<hostname>([a-zA-Z0-9-]+\.)+[a-zA-Z0-9-]{2,63})(?:\/(?<path>(?:[^\s?]+)?)(?:\?(?<query>\S+))?)?$/gm;
@ -420,4 +424,3 @@ test
}
}
}
}

View file

@ -1,4 +1,12 @@
namespace chat {
import {GroupManager} from "tc-shared/permission/GroupManager";
import {Frame, FrameContent} from "tc-shared/ui/frames/chat_frame";
import {ClientEntry, LocalClientEntry} from "tc-shared/ui/client";
import {openClientInfo} from "tc-shared/ui/modal/ModalClientInfo";
import * as htmltags from "tc-shared/ui/htmltags";
import * as image_preview from "../image_preview";
import {format} from "tc-shared/ui/frames/side/chat_helper";
import * as i18nc from "tc-shared/i18n/country";
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
@ -40,7 +48,7 @@ namespace chat {
if(!this._current_client)
return;
Modals.openClientInfo(this._current_client);
openClientInfo(this._current_client);
});
this._html_tag.find('.container-avatar-edit').on('click', () => this.handle.handle.update_avatar());
}
@ -116,7 +124,7 @@ namespace chat {
country.children().detach();
const country_code = (client ? client.properties.client_country : undefined) || "xx";
$.spawn("div").addClass("country flag-" + country_code.toLowerCase()).appendTo(country);
$.spawn("a").text(i18n.country_name(country_code.toUpperCase())).appendTo(country);
$.spawn("a").text(i18nc.country_name(country_code.toUpperCase())).appendTo(country);
const version = this._html_tag.find(".client-version");
@ -272,4 +280,3 @@ namespace chat {
}
}
}
}

View file

@ -1,8 +1,18 @@
namespace chat {
import {settings, Settings} from "tc-shared/settings";
import {format} from "tc-shared/ui/frames/side/chat_helper";
import {bbcode_chat, formatMessage} from "tc-shared/ui/frames/chat";
import {CommandResult, ErrorID} from "tc-shared/connection/ServerConnectionDeclaration";
import {LogCategory} from "tc-shared/log";
import PermissionType from "tc-shared/permission/PermissionType";
import {ChatBox} from "tc-shared/ui/frames/side/chat_box";
import {Frame, FrameContent} from "tc-shared/ui/frames/chat_frame";
import {createErrorModal} from "tc-shared/ui/elements/Modal";
import * as log from "tc-shared/log";
import * as htmltags from "tc-shared/ui/htmltags";
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
export namespace channel {
export type ViewEntry = {
html_element: JQuery;
update_timer?: number;
@ -151,7 +161,7 @@ namespace chat {
client_unique_id: data.sender_unique_id,
client_id: 0
}),
message: MessageHelper.bbcode_chat(data.message),
message: bbcode_chat(data.message),
avatar: this.handle.handle.handle.fileManager.avatars.generate_chat_tag({database_id: data.sender_database_id}, data.sender_unique_id)
});
@ -433,7 +443,7 @@ namespace chat {
log.error(LogCategory.CHAT, tr("Failed to delete conversation message for conversation %o: %o"), this.channel_id, error);
if(error instanceof CommandResult)
error = error.extra_message || error.message;
createErrorModal(tr("Failed to delete message"), MessageHelper.formatMessage(tr("Failed to delete conversation message{:br:}Error: {}"), error)).open();
createErrorModal(tr("Failed to delete message"), formatMessage(tr("Failed to delete conversation message{:br:}Error: {}"), error)).open();
});
log.debug(LogCategory.CLIENT, tr("Deleting text message %o"), message);
}
@ -611,5 +621,3 @@ namespace chat {
}
}
}
}
}

View file

@ -1,5 +1,13 @@
namespace chat {
import PlayerState = connection.voice.PlayerState;
import {Frame, FrameContent} from "tc-shared/ui/frames/chat_frame";
import * as events from "tc-shared/events";
import {MusicClientEntry, SongInfo} from "tc-shared/ui/client";
import {voice} from "tc-shared/connection/ConnectionBase";
import PlayerState = voice.PlayerState;
import {LogCategory} from "tc-shared/log";
import {CommandResult, ErrorID, PlaylistSong} from "tc-shared/connection/ServerConnectionDeclaration";
import {createErrorModal, createInputModal} from "tc-shared/ui/elements/Modal";
import * as log from "tc-shared/log";
import * as image_preview from "../image_preview";
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
@ -846,4 +854,3 @@ namespace chat {
return tag;
}
}
}

View file

@ -1,5 +1,14 @@
/* the bar on the right with the chats (Channel & Client) */
namespace chat {
import {settings, Settings} from "tc-shared/settings";
import {LogCategory} from "tc-shared/log";
import {format, helpers} from "tc-shared/ui/frames/side/chat_helper";
import {bbcode_chat} from "tc-shared/ui/frames/chat";
import {Frame} from "tc-shared/ui/frames/chat_frame";
import {ChatBox} from "tc-shared/ui/frames/side/chat_box";
import {CommandResult, ErrorID} from "tc-shared/connection/ServerConnectionDeclaration";
import * as log from "tc-shared/log";
import * as htmltags from "tc-shared/ui/htmltags";
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
@ -341,7 +350,7 @@ namespace chat {
client_unique_id: message.sender_unique_id,
client_id: message.sender_client_id
}),
message: MessageHelper.bbcode_chat(message.message),
message: bbcode_chat(message.message),
avatar: this.handle.handle.handle.fileManager.avatars.generate_chat_tag({id: message.sender_client_id}, message.sender_unique_id)
});
if(time.next_update > 0) {
@ -792,7 +801,7 @@ namespace chat {
}
} else {
conv.append_error(tr("Failed to send message. Lookup the console for more details"));
log.error(LogCategory.CHAT, tr("Failed to send conversation message: %o", error));
log.error(LogCategory.CHAT, tr("Failed to send conversation message: %o"), error);
}
});
});
@ -893,4 +902,3 @@ namespace chat {
}
}
}
}

View file

@ -1,4 +1,10 @@
namespace htmltags {
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import {ChannelEntry} from "tc-shared/ui/channel";
import {ClientEntry} from "tc-shared/ui/client";
import {htmlEscape} from "tc-shared/ui/frames/chat";
import {server_connections} from "tc-shared/ui/frames/connection_handlers";
let mouse_coordinates: {x: number, y: number} = {x: 0, y: 0};
function initialize() {
@ -64,7 +70,7 @@ namespace htmltags {
if(properties.add_braces)
result = result + "\"";
result = result + MessageHelper.htmlEscape(properties.client_name || "undefined").join(" ");
result = result + htmlEscape(properties.client_name || "undefined").join(" ");
if(properties.add_braces)
result = result + "\"";
}
@ -106,7 +112,7 @@ namespace htmltags {
{
if(properties.add_braces)
result = result + "\"";
result = result + MessageHelper.htmlEscape(properties.channel_display_name || properties.channel_name || "undefined").join(" ");
result = result + htmlEscape(properties.channel_display_name || properties.channel_name || "undefined").join(" ");
if(properties.add_braces)
result = result + "\"";
}
@ -181,6 +187,7 @@ namespace htmltags {
}
}
declare const xbbcode;
namespace bbcodes {
/* the = because we sometimes get that */
//const url_client_regex = /?client:\/\/(?<client_id>[0-9]+)\/(?<client_unique_id>[a-zA-Z0-9+=#]+)~(?<client_name>(?:[^%]|%[0-9A-Fa-f]{2})+)$/g;
@ -223,31 +230,7 @@ namespace htmltags {
}
return origin_url.build_html_tag_close(layer);
}
})
/*
"img": {
openTag: function(params,content) {
let myUrl;
if (!params) {
myUrl = content.replace(/<.*?>/g,"");
} else {
myUrl = params.substr(1);
}
urlPattern.lastIndex = 0;
if ( !urlPattern.test( myUrl ) ) {
myUrl = "#";
}
return '<a href="' + myUrl + '" target="_blank">';
},
closeTag: function(params,content) {
return '</a>';
}
},
*/
});
}
initialize();
}
}

View file

@ -1,8 +1,8 @@
/// <reference path="../../ui/elements/modal.ts" />
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
import {createModal} from "tc-shared/ui/elements/Modal";
import * as loader from "tc-loader";
import {LogCategory} from "tc-shared/log";
import * as log from "tc-shared/log";
namespace Modals {
function format_date(date: number) {
const d = new Date(date);
@ -27,9 +27,9 @@ namespace Modals {
header: tr("About"),
body: () => {
let tag = $("#tmpl_about").renderTag({
client: !app.is_web(),
client: loader.version().type !== "web",
version_client: app.is_web() ? app_version || "in-dev" : "loading...",
version_client: loader.version().type === "web" ? app_version || "in-dev" : "loading...",
version_ui: app_version || "in-dev",
version_timestamp: !!app_version ? format_date(Date.now()) : "--"
@ -43,7 +43,7 @@ namespace Modals {
connectModal.htmlTag.find(".modal-body").addClass("modal-about");
connectModal.open();
if(!app.is_web()) {
if(loader.version().type !== "web") {
(window as any).native.client_version().then(version => {
connectModal.htmlTag.find(".version-client").text(version);
}).catch(error => {
@ -52,4 +52,3 @@ namespace Modals {
});
}
}
}

View file

@ -1,9 +1,8 @@
/// <reference path="../../ui/elements/modal.ts" />
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
//TODO: Test if we could render this image and not only the browser by knowing the type.
import {createErrorModal, createModal} from "tc-shared/ui/elements/Modal";
import {tra} from "tc-shared/i18n/localize";
import {arrayBufferBase64} from "tc-shared/utils/buffers";
export function spawnAvatarUpload(callback_data: (data: ArrayBuffer | undefined | null) => any) {
const modal = createModal({
header: tr("Avatar Upload"),
@ -71,4 +70,3 @@ namespace Modals {
modal.close_listener.push(() => !_data_submitted && callback_data(undefined));
modal.open();
}
}

View file

@ -1,8 +1,12 @@
/// <reference path="../../ui/elements/modal.ts" />
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
import {createErrorModal, createModal} from "tc-shared/ui/elements/Modal";
import {LogCategory} from "tc-shared/log";
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {base64_encode_ab} from "tc-shared/utils/buffers";
import {media_image_type} from "tc-shared/FileManager";
import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
import {ClientEntry} from "tc-shared/ui/client";
import * as log from "tc-shared/log";
namespace Modals {
const avatar_to_uid = (id: string) => {
const buffer = new Uint8Array(id.length / 2);
for(let index = 0; index < id.length; index += 2) {
@ -20,6 +24,7 @@ namespace Modals {
return (size / Math.pow(1024, exp)).toFixed(2) + 'KMGTPE'.charAt(exp - 1) + "iB";
};
declare const moment;
export function spawnAvatarList(client: ConnectionHandler) {
const modal = createModal({
header: tr("Avatars"),
@ -159,4 +164,3 @@ namespace Modals {
setTimeout(() => update_avatar_list(), 250);
modal.open();
}
}

View file

@ -2,7 +2,12 @@
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
import PermissionType from "tc-shared/permission/PermissionType";
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {createModal} from "tc-shared/ui/elements/Modal";
import {duration_data} from "tc-shared/ui/modal/ModalBanList";
import * as tooltip from "tc-shared/ui/elements/Tooltip";
export type BanEntry = {
name?: string;
unique_id: string;
@ -165,7 +170,7 @@ namespace Modals {
update_button_ok();
}
tooltip(template);
tooltip.initialize(template);
return template.children();
},
footer: null,
@ -177,4 +182,3 @@ namespace Modals {
modal.htmlTag.find(".modal-body").addClass("modal-ban-client");
}
}

View file

@ -1,15 +1,20 @@
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../ui/elements/modal.ts" />
/// <reference path="../../i18n/localize.ts" />
/// <reference path="../../proto.ts" />
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {createErrorModal, createInfoModal, createModal, Modal} from "tc-shared/ui/elements/Modal";
import {SingleCommandHandler} from "tc-shared/connection/ConnectionBase";
import {CommandResult, ErrorID} from "tc-shared/connection/ServerConnectionDeclaration";
import PermissionType from "tc-shared/permission/PermissionType";
import {LogCategory} from "tc-shared/log";
import * as log from "tc-shared/log";
import * as tooltip from "tc-shared/ui/elements/Tooltip";
import * as htmltags from "tc-shared/ui/htmltags";
import {format_time, formatMessage} from "tc-shared/ui/frames/chat";
namespace Modals {
export function openBanList(client: ConnectionHandler) {
let modal: Modal;
let _callback_bans;
let _callback_triggers;
const single_ban_handler: connection.SingleCommandHandler = {
const single_ban_handler: SingleCommandHandler = {
command: "notifybanlist",
function: command => {
const json = command.arguments;
@ -41,7 +46,7 @@ namespace Modals {
return false; /* do not remove me */
}
};
const single_trigger_handler: connection.SingleCommandHandler = {
const single_trigger_handler: SingleCommandHandler = {
command: "notifybantriggerlist",
function: command => {
//TODO: Test the server id in the response?
@ -265,6 +270,7 @@ namespace Modals {
},
};
declare const moment;
function generate_dom(controller: BanListController) : JQuery {
const template = $("#tmpl_ban_list").renderTag();
@ -392,7 +398,7 @@ namespace Modals {
log.error(LogCategory.CLIENT, tr("Failed to delete ban: %o"), error);
if(error instanceof CommandResult)
error = error.id === ErrorID.PERMISSION_ERROR ? "no permissions" : error.extra_message || error.message;
createErrorModal(tr("Failed to delete ban"), MessageHelper.formatMessage(tr("Failed to delete ban. {:br:}Error: {}"), error)).open();
createErrorModal(tr("Failed to delete ban"), formatMessage(tr("Failed to delete ban. {:br:}Error: {}"), error)).open();
});
});
@ -721,7 +727,7 @@ namespace Modals {
if(index > 0) index--;
input_duration_type.find("option[value='" + periods[index] + "']").prop("selected", true);
input_duration_value.val(Math.ceil(duration / duration_data[periods[index]].scale));
tooltip_duration_detailed.text($.spawn("div").append(...MessageHelper.formatMessage(tr("The ban lasts for exact {}."), MessageHelper.format_time(duration * 1000, "never"))).text());
tooltip_duration_detailed.text($.spawn("div").append(...formatMessage(tr("The ban lasts for exact {}."), format_time(duration * 1000, "never"))).text());
} else {
tooltip_duration_detailed.text(tr("The ban is forever."));
input_duration_value.attr("placeholder", tr("for ever")).val(null).prop('disabled', true);
@ -783,7 +789,7 @@ namespace Modals {
log.error(LogCategory.CLIENT, tr("Failed to edited ban: %o"), error);
if(error instanceof CommandResult)
error = error.id === ErrorID.PERMISSION_ERROR ? "no permissions" : error.extra_message || error.message;
createErrorModal(tr("Failed to edited ban"), MessageHelper.formatMessage(tr("Failed to edited ban. {:br:}Error: {}"), error)).open();
createErrorModal(tr("Failed to edited ban"), formatMessage(tr("Failed to edited ban. {:br:}Error: {}"), error)).open();
});
});
@ -851,7 +857,7 @@ namespace Modals {
log.error(LogCategory.CLIENT, tr("Failed to add ban: %o"), error);
if(error instanceof CommandResult)
error = error.id === ErrorID.PERMISSION_ERROR ? "no permissions" : error.extra_message || error.message;
createErrorModal(tr("Failed to add ban"), MessageHelper.formatMessage(tr("Failed to add ban. {:br:}Error: {}"), error)).open();
createErrorModal(tr("Failed to add ban"), formatMessage(tr("Failed to add ban. {:br:}Error: {}"), error)).open();
});
});
}
@ -913,9 +919,6 @@ namespace Modals {
update_edit_window(false);
update_banlist();
tooltip(template);
tooltip.initialize(template);
return template.children();
}
}
//container-triggerlist

View file

@ -1,15 +1,31 @@
/// <reference path="../../ui/elements/modal.ts" />
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
import {createInputModal, createModal, Modal} from "tc-shared/ui/elements/Modal";
import {
Bookmark,
bookmarks,
BookmarkType, boorkmak_connect, create_bookmark, create_bookmark_directory,
delete_bookmark,
DirectoryBookmark,
save_bookmark
} from "tc-shared/bookmarks";
import {connection_log, Regex} from "tc-shared/ui/modal/ModalConnect";
import {IconManager} from "tc-shared/FileManager";
import {profiles} from "tc-shared/profiles/ConnectionProfile";
import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
import {Settings, settings} from "tc-shared/settings";
import {LogCategory} from "tc-shared/log";
import * as log from "tc-shared/log";
import * as i18nc from "tc-shared/i18n/country";
import {formatMessage} from "tc-shared/ui/frames/chat";
import {control_bar} from "tc-shared/ui/frames/ControlBar";
import * as top_menu from "../frames/MenuBar";
namespace Modals {
export function spawnBookmarkModal() {
let modal: Modal;
modal = createModal({
header: tr("Manage bookmarks"),
body: () => {
let template = $("#tmpl_manage_bookmarks").renderTag({ });
let selected_bookmark: bookmarks.Bookmark | bookmarks.DirectoryBookmark | undefined;
let selected_bookmark: Bookmark | DirectoryBookmark | undefined;
const button_delete = template.find(".button-delete");
const button_add_folder = template.find(".button-add-folder");
@ -35,31 +51,31 @@ namespace Modals {
const update_buttons = () => {
button_delete.prop("disabled", !selected_bookmark);
button_connect.prop("disabled", !selected_bookmark || selected_bookmark.type !== bookmarks.BookmarkType.ENTRY);
button_connect_tab.prop("disabled", !selected_bookmark || selected_bookmark.type !== bookmarks.BookmarkType.ENTRY);
button_connect.prop("disabled", !selected_bookmark || selected_bookmark.type !== BookmarkType.ENTRY);
button_connect_tab.prop("disabled", !selected_bookmark || selected_bookmark.type !== BookmarkType.ENTRY);
};
const update_connect_info = () => {
if(selected_bookmark && selected_bookmark.type === bookmarks.BookmarkType.ENTRY) {
const entry = selected_bookmark as bookmarks.Bookmark;
if(selected_bookmark && selected_bookmark.type === BookmarkType.ENTRY) {
const entry = selected_bookmark as Bookmark;
const history = connection_log.history().find(e => e.address.hostname === entry.server_properties.server_address && e.address.port === entry.server_properties.server_port);
if(history) {
label_server_name.text(history.name);
label_server_region.empty().append(
$.spawn("div").addClass("country flag-" + history.country.toLowerCase()),
$.spawn("div").text(i18n.country_name(history.country, tr("Global")))
$.spawn("div").text(i18nc.country_name(history.country, tr("Global")))
);
label_client_count.text(history.clients_online + "/" + history.clients_total);
label_connection_count.empty().append(
...MessageHelper.formatMessage(tr("You've connected {} times"), $.spawn("div").addClass("connect-count").text(history.total_connection))
...formatMessage(tr("You've connected {} times"), $.spawn("div").addClass("connect-count").text(history.total_connection))
);
} else {
label_server_name.text(tr("Unknown"));
label_server_region.empty().text(tr("Unknown"));
label_client_count.text(tr("Unknown"));
label_connection_count.empty().append(
...MessageHelper.formatMessage(tr("You {} connected to that server address"), $.spawn("div").addClass("connect-never").text("never"))
...formatMessage(tr("You {} connected to that server address"), $.spawn("div").addClass("connect-never").text("never"))
);
}
label_last_ping.text(tr("Average ping isn't yet supported"));
@ -74,17 +90,17 @@ namespace Modals {
const update_selected = () => {
input_bookmark_name.prop("disabled", !selected_bookmark);
input_connect_profile.prop("disabled", !selected_bookmark || selected_bookmark.type !== bookmarks.BookmarkType.ENTRY);
input_server_address.prop("disabled", !selected_bookmark || selected_bookmark.type !== bookmarks.BookmarkType.ENTRY);
input_server_password.prop("disabled", !selected_bookmark || selected_bookmark.type !== bookmarks.BookmarkType.ENTRY);
input_connect_profile.prop("disabled", !selected_bookmark || selected_bookmark.type !== BookmarkType.ENTRY);
input_server_address.prop("disabled", !selected_bookmark || selected_bookmark.type !== BookmarkType.ENTRY);
input_server_password.prop("disabled", !selected_bookmark || selected_bookmark.type !== BookmarkType.ENTRY);
if(selected_bookmark) {
input_bookmark_name.val(selected_bookmark.display_name);
label_bookmark_name.text(selected_bookmark.display_name);
}
if(selected_bookmark && selected_bookmark.type === bookmarks.BookmarkType.ENTRY) {
const entry = selected_bookmark as bookmarks.Bookmark;
if(selected_bookmark && selected_bookmark.type === BookmarkType.ENTRY) {
const entry = selected_bookmark as Bookmark;
const address = entry.server_properties.server_address + (entry.server_properties.server_port == 9987 ? "" : (" " + entry.server_properties.server_port));
label_server_address.text(address);
@ -113,9 +129,9 @@ namespace Modals {
update_selected();
const hide_links: boolean[] = [];
const build_entry = (entry: bookmarks.Bookmark | bookmarks.DirectoryBookmark, sibling_data: {first: boolean; last: boolean;}, index: number) => {
const build_entry = (entry: Bookmark | DirectoryBookmark, sibling_data: {first: boolean; last: boolean;}, index: number) => {
let container = $.spawn("div")
.addClass(entry.type === bookmarks.BookmarkType.ENTRY ? "bookmark" : "directory")
.addClass(entry.type === BookmarkType.ENTRY ? "bookmark" : "directory")
.addClass(index > 0 ? "linked" : "")
.addClass(sibling_data.first ? "link-start" : "");
for (let i = 0; i < index; i++) {
@ -127,8 +143,8 @@ namespace Modals {
);
}
if (entry.type === bookmarks.BookmarkType.ENTRY) {
const bookmark = entry as bookmarks.Bookmark;
if (entry.type === BookmarkType.ENTRY) {
const bookmark = entry as Bookmark;
container.append(
bookmark.last_icon_id ?
IconManager.generate_tag(IconManager.load_cached_icon(bookmark.last_icon_id || 0), {animate: false}) :
@ -160,14 +176,14 @@ namespace Modals {
hide_links.push(sibling_data.last);
let cindex = 0;
const children = (entry as bookmarks.DirectoryBookmark).content || [];
const children = (entry as DirectoryBookmark).content || [];
for (const child of children)
build_entry(child, {first: cindex++ == 0, last: cindex == children.length}, index + 1);
hide_links.pop();
};
let cindex = 0;
const children = bookmarks.bookmarks().content;
const children = bookmarks().content;
for (const bookmark of children)
build_entry(bookmark, {first: cindex++ == 0, last: cindex == children.length}, 0);
};
@ -180,7 +196,7 @@ namespace Modals {
.text("")
.css("display", "none")
);
for(const profile of profiles.profiles()) {
for(const profile of profiles()) {
input_connect_profile.append(
$.spawn("option")
.attr("value", profile.id)
@ -194,17 +210,17 @@ namespace Modals {
button_delete.on('click', event => {
if(!selected_bookmark) return;
if(selected_bookmark.type === bookmarks.BookmarkType.DIRECTORY && (selected_bookmark as bookmarks.DirectoryBookmark).content.length > 0) {
Modals.spawnYesNo(tr("Are you sure"), tr("Do you really want to delete this non empty directory?"), answer => {
if(selected_bookmark.type === BookmarkType.DIRECTORY && (selected_bookmark as DirectoryBookmark).content.length > 0) {
spawnYesNo(tr("Are you sure"), tr("Do you really want to delete this non empty directory?"), answer => {
if(answer) {
bookmarks.delete_bookmark(selected_bookmark);
bookmarks.save_bookmark(selected_bookmark);
delete_bookmark(selected_bookmark);
save_bookmark(selected_bookmark);
update_bookmark_list(undefined);
}
});
} else {
bookmarks.delete_bookmark(selected_bookmark);
bookmarks.save_bookmark(selected_bookmark);
delete_bookmark(selected_bookmark);
save_bookmark(selected_bookmark);
update_bookmark_list(undefined);
}
});
@ -214,15 +230,15 @@ namespace Modals {
return true;
}, result => {
if(result) {
const mark = bookmarks.create_bookmark_directory(
const mark = create_bookmark_directory(
selected_bookmark ?
selected_bookmark.type === bookmarks.BookmarkType.DIRECTORY ?
selected_bookmark as bookmarks.DirectoryBookmark :
selected_bookmark.type === BookmarkType.DIRECTORY ?
selected_bookmark as DirectoryBookmark :
selected_bookmark.parent :
bookmarks.bookmarks(),
bookmarks(),
result as string
);
bookmarks.save_bookmark(mark);
save_bookmark(mark);
update_bookmark_list(mark.unique_id);
}
}).open();
@ -233,30 +249,30 @@ namespace Modals {
return true;
}, result => {
if(result) {
const mark = bookmarks.create_bookmark(result as string,
const mark = create_bookmark(result as string,
selected_bookmark ?
selected_bookmark.type === bookmarks.BookmarkType.DIRECTORY ?
selected_bookmark as bookmarks.DirectoryBookmark :
selected_bookmark.type === BookmarkType.DIRECTORY ?
selected_bookmark as DirectoryBookmark :
selected_bookmark.parent :
bookmarks.bookmarks(), {
bookmarks(), {
server_password: "",
server_port: 9987,
server_address: "",
server_password_hash: ""
}, "");
bookmarks.save_bookmark(mark);
save_bookmark(mark);
update_bookmark_list(mark.unique_id);
}
}).open();
});
button_connect_tab.on('click', event => {
bookmarks.boorkmak_connect(selected_bookmark as bookmarks.Bookmark, true);
boorkmak_connect(selected_bookmark as Bookmark, true);
modal.close();
}).toggle(!settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION));
button_connect.on('click', event => {
bookmarks.boorkmak_connect(selected_bookmark as bookmarks.Bookmark, false);
boorkmak_connect(selected_bookmark as Bookmark, false);
modal.close();
});
}
@ -280,7 +296,7 @@ namespace Modals {
input_server_address.firstParent(".input-boxed").toggleClass("is-invalid", !valid);
if(valid) {
const entry = selected_bookmark as bookmarks.Bookmark;
const entry = selected_bookmark as Bookmark;
let _v6_end = address.indexOf(']');
let idx = address.lastIndexOf(':');
if(idx != -1 && idx > _v6_end) {
@ -298,9 +314,9 @@ namespace Modals {
input_connect_profile.on('change', event => {
const id = input_connect_profile.val() as string;
const profile = profiles.profiles().find(e => e.id === id);
const profile = profiles().find(e => e.id === id);
if(profile) {
(selected_bookmark as bookmarks.Bookmark).connect_profile = id;
(selected_bookmark as Bookmark).connect_profile = id;
} else {
log.warn(LogCategory.GENERAL, tr("Failed to change connect profile for profile %s to %s"), selected_bookmark.unique_id, id);
}
@ -365,4 +381,3 @@ namespace Modals {
modal.open();
}
}

View file

@ -1,10 +1,12 @@
/// <reference path="../../ui/elements/modal.ts" />
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
import {createModal, Modal} from "tc-shared/ui/elements/Modal";
import {ClientEntry} from "tc-shared/ui/client";
import {voice} from "tc-shared/connection/ConnectionBase";
import LatencySettings = voice.LatencySettings;
import {Slider, sliderfy} from "tc-shared/ui/elements/Slider";
import * as htmltags from "tc-shared/ui/htmltags";
namespace Modals {
let modal: Modal;
export function spawnChangeLatency(client: ClientEntry, current: connection.voice.LatencySettings, reset: () => connection.voice.LatencySettings, apply: (settings: connection.voice.LatencySettings) => any, callback_flush?: () => any) {
export function spawnChangeLatency(client: ClientEntry, current: LatencySettings, reset: () => LatencySettings, apply: (settings: LatencySettings) => any, callback_flush?: () => any) {
if(modal) modal.close();
const begin = Object.assign({}, current);
@ -111,4 +113,3 @@ namespace Modals {
modal.open();
modal.htmlTag.find(".modal-body").addClass("modal-latency");
}
}

View file

@ -1,10 +1,10 @@
/// <reference path="../../ui/elements/modal.ts" />
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
//TODO: Use the max limit!
import {sliderfy} from "tc-shared/ui/elements/Slider";
import {createModal, Modal} from "tc-shared/ui/elements/Modal";
import {ClientEntry} from "tc-shared/ui/client";
import * as htmltags from "tc-shared/ui/htmltags";
let modal: Modal;
export function spawnChangeVolume(client: ClientEntry, local: boolean, current: number, max: number | undefined, callback: (number) => void) {
if(modal) modal.close();
@ -74,4 +74,3 @@ namespace Modals {
modal.open();
modal.htmlTag.find(".modal-body").addClass("modal-volume");
}
}

View file

@ -1,4 +1,9 @@
namespace Modals {
import {createInfoModal, createModal, Modal} from "tc-shared/ui/elements/Modal";
import {ChannelEntry} from "tc-shared/ui/channel";
import {copy_to_clipboard} from "tc-shared/utils/helpers";
import * as tooltip from "tc-shared/ui/elements/Tooltip";
import {formatMessage} from "tc-shared/ui/frames/chat";
export function openChannelInfo(channel: ChannelEntry) {
let modal: Modal;
@ -22,7 +27,7 @@ namespace Modals {
button_update.on('click', event => update_values(modal.htmlTag));
update_values(template);
tooltip(template);
tooltip.initialize(template);
return template.children();
},
footer: null,
@ -33,6 +38,7 @@ namespace Modals {
modal.open();
}
declare const xbbcode;
function apply_channel_description(container: JQuery, channel: ChannelEntry) {
const container_value = container.find(".value");
const container_no_value = container.find(".no-value");
@ -85,7 +91,7 @@ namespace Modals {
else if(channel.properties.channel_conversation_history_length == 0)
tag.text(tr("Public; Permanent message saving"));
else
tag.append(MessageHelper.formatMessage(tr("Public; Saving last {} messages"), channel.properties.channel_conversation_history_length));
tag.append(formatMessage(tr("Public; Saving last {} messages"), channel.properties.channel_conversation_history_length));
}
}
@ -149,4 +155,3 @@ namespace Modals {
}
}
}
}

View file

@ -1,4 +1,11 @@
namespace Modals {
import {ClientConnectionInfo, ClientEntry} from "tc-shared/ui/client";
import PermissionType from "tc-shared/permission/PermissionType";
import {createInfoModal, createModal, Modal} from "tc-shared/ui/elements/Modal";
import {copy_to_clipboard} from "tc-shared/utils/helpers";
import * as i18nc from "tc-shared/i18n/country";
import * as tooltip from "tc-shared/ui/elements/Tooltip";
import {format_number, network} from "tc-shared/ui/frames/chat";
type InfoUpdateCallback = (info: ClientConnectionInfo) => any;
export function openClientInfo(client: ClientEntry) {
let modal: Modal;
@ -31,7 +38,7 @@ namespace Modals {
apply_groups(client, template.find(".container-groups"), modal, update_callbacks);
apply_packets(client, template.find(".container-packets"), modal, update_callbacks);
tooltip(template);
tooltip.initialize(template);
return template.children();
},
footer: null,
@ -119,6 +126,7 @@ namespace Modals {
}
}
declare const moment;
function apply_basic_info(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) {
/* Unique ID */
{
@ -191,7 +199,7 @@ namespace Modals {
const container = tag.find(".property-country");
container.find(".value").empty().append(
$.spawn("div").addClass("country flag-" + client.properties.client_country.toLowerCase()),
$.spawn("a").text(i18n.country_name(client.properties.client_country, tr("Unknown")))
$.spawn("a").text(i18nc.country_name(client.properties.client_country, tr("Unknown")))
);
}
@ -361,7 +369,7 @@ namespace Modals {
if(packets == 0 && info.connection_packets_received_keepalive == -1)
node_downstream.innerText = tr("Not calculated");
else
node_downstream.innerText = MessageHelper.format_number(packets, {unit: "Packets"});
node_downstream.innerText = format_number(packets, {unit: "Packets"});
});
node_downstream.innerText = tr("loading...");
}
@ -375,7 +383,7 @@ namespace Modals {
if(packets == 0 && info.connection_packets_sent_keepalive == -1)
node_upstream.innerText = tr("Not calculated");
else
node_upstream.innerText = MessageHelper.format_number(packets, {unit: "Packets"});
node_upstream.innerText = format_number(packets, {unit: "Packets"});
});
node_upstream.innerText = tr("loading...");
}
@ -396,7 +404,7 @@ namespace Modals {
if(bytes == 0 && info.connection_bytes_received_keepalive == -1)
node_downstream.innerText = tr("Not calculated");
else
node_downstream.innerText = MessageHelper.network.format_bytes(bytes);
node_downstream.innerText = network.format_bytes(bytes);
});
node_downstream.innerText = tr("loading...");
}
@ -410,7 +418,7 @@ namespace Modals {
if(bytes == 0 && info.connection_bytes_sent_keepalive == -1)
node_upstream.innerText = tr("Not calculated");
else
node_upstream.innerText = MessageHelper.network.format_bytes(bytes);
node_upstream.innerText = network.format_bytes(bytes);
});
node_upstream.innerText = tr("loading...");
}
@ -431,7 +439,7 @@ namespace Modals {
if(bytes == 0 && info.connection_bandwidth_received_last_second_keepalive == -1)
node_downstream.innerText = tr("Not calculated");
else
node_downstream.innerText = MessageHelper.network.format_bytes(bytes, {time: "s"});
node_downstream.innerText = network.format_bytes(bytes, {time: "s"});
});
node_downstream.innerText = tr("loading...");
}
@ -445,7 +453,7 @@ namespace Modals {
if(bytes == 0 && info.connection_bandwidth_sent_last_second_keepalive == -1)
node_upstream.innerText = tr("Not calculated");
else
node_upstream.innerText = MessageHelper.network.format_bytes(bytes, {time: "s"});
node_upstream.innerText = network.format_bytes(bytes, {time: "s"});
});
node_upstream.innerText = tr("loading...");
}
@ -466,7 +474,7 @@ namespace Modals {
if(bytes == 0 && info.connection_bandwidth_received_last_minute_keepalive == -1)
node_downstream.innerText = tr("Not calculated");
else
node_downstream.innerText = MessageHelper.network.format_bytes(bytes, {time: "s"});
node_downstream.innerText = network.format_bytes(bytes, {time: "s"});
});
node_downstream.innerText = tr("loading...");
}
@ -480,7 +488,7 @@ namespace Modals {
if(bytes == 0 && info.connection_bandwidth_sent_last_minute_keepalive == -1)
node_upstream.innerText = tr("Not calculated");
else
node_upstream.innerText = MessageHelper.network.format_bytes(bytes, {time: "s"});
node_upstream.innerText = network.format_bytes(bytes, {time: "s"});
});
node_upstream.innerText = tr("loading...");
}
@ -495,7 +503,7 @@ namespace Modals {
if(node_downstream) {
client.updateClientVariables().then(info => {
//TODO: Test for own client info and if so then show the max quota (needed permission)
node_downstream.innerText = MessageHelper.network.format_bytes(client.properties.client_month_bytes_downloaded, {exact: false});
node_downstream.innerText = network.format_bytes(client.properties.client_month_bytes_downloaded, {exact: false});
});
node_downstream.innerText = tr("loading...");
}
@ -503,10 +511,9 @@ namespace Modals {
if(node_upstream) {
client.updateClientVariables().then(info => {
//TODO: Test for own client info and if so then show the max quota (needed permission)
node_upstream.innerText = MessageHelper.network.format_bytes(client.properties.client_month_bytes_uploaded, {exact: false});
node_upstream.innerText = network.format_bytes(client.properties.client_month_bytes_uploaded, {exact: false});
});
node_upstream.innerText = tr("loading...");
}
}
}
}

View file

@ -1,7 +1,17 @@
/// <reference path="../../ui/elements/modal.ts" />
import {Settings, settings} from "tc-shared/settings";
import {LogCategory} from "tc-shared/log";
import * as log from "tc-shared/log";
import * as loader from "tc-loader";
import {createModal} from "tc-shared/ui/elements/Modal";
import {ConnectionProfile, default_profile, find_profile, profiles} from "tc-shared/profiles/ConnectionProfile";
import {KeyCode} from "tc-shared/PPTListener";
import {IconManager} from "tc-shared/FileManager";
import * as i18nc from "tc-shared/i18n/country";
import {spawnSettingsModal} from "tc-shared/ui/modal/ModalSettings";
import {server_connections} from "tc-shared/ui/frames/connection_handlers";
//FIXME: Move this shit out of this file!
namespace connection_log {
export namespace connection_log {
//TODO: Save password data
export type ConnectionData = {
name: string;
@ -91,11 +101,11 @@ namespace connection_log {
});
}
namespace Modals {
declare const native_client;
export function spawnConnectModal(options: {
default_connect_new_tab?: boolean /* default false */
}, defaultHost: { url: string, enforce: boolean} = { url: "ts.TeaSpeak.de", enforce: false}, connect_profile?: { profile: profiles.ConnectionProfile, enforce: boolean}) {
let selected_profile: profiles.ConnectionProfile;
}, defaultHost: { url: string, enforce: boolean} = { url: "ts.TeaSpeak.de", enforce: false}, connect_profile?: { profile: ConnectionProfile, enforce: boolean}) {
let selected_profile: ConnectionProfile;
const random_id = (() => {
const array = new Uint32Array(10);
@ -180,7 +190,7 @@ namespace Modals {
button_connect.trigger('click');
});
button_manage.on('click', event => {
const modal = Modals.spawnSettingsModal("identity-profiles");
const modal = spawnSettingsModal("identity-profiles");
modal.close_listener.push(() => {
input_profile.trigger('change');
});
@ -189,14 +199,14 @@ namespace Modals {
/* Connect Profiles */
{
for(const profile of profiles.profiles()) {
for(const profile of profiles()) {
input_profile.append(
$.spawn("option").text(profile.profile_name).val(profile.id)
);
}
input_profile.on('change', event => {
selected_profile = profiles.find_profile(input_profile.val() as string) || profiles.default_profile();
selected_profile = find_profile(input_profile.val() as string) || default_profile();
{
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, undefined);
input_nickname
@ -289,7 +299,7 @@ namespace Modals {
).append(
$.spawn("div").addClass("column country-name").append([
$.spawn("div").addClass("country flag-" + entry.country.toLowerCase()),
$.spawn("a").text(i18n.country_name(entry.country, tr("Global")))
$.spawn("a").text(i18nc.country_name(entry.country, tr("Global")))
])
).append(
$.spawn("div").addClass("column clients").text(entry.clients_online + "/" + entry.clients_total)
@ -328,4 +338,3 @@ namespace Modals {
IP_V6: /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/,
IP: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$|^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/,
};
}

View file

@ -1,6 +1,16 @@
/// <reference path="../../ui/elements/modal.ts" />
import PermissionType from "tc-shared/permission/PermissionType";
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {ChannelEntry, ChannelProperties} from "tc-shared/ui/channel";
import {PermissionManager, PermissionValue} from "tc-shared/permission/PermissionManager";
import {LogCategory} from "tc-shared/log";
import {createModal} from "tc-shared/ui/elements/Modal";
import * as log from "tc-shared/log";
import {Settings, settings} from "tc-shared/settings";
import * as tooltip from "tc-shared/ui/elements/Tooltip";
import {spawnIconSelect} from "tc-shared/ui/modal/ModalIconSelect";
import {hashPassword} from "tc-shared/utils/helpers";
import {sliderfy} from "tc-shared/ui/elements/Slider";
namespace Modals {
export function createChannelModal(connection: ConnectionHandler, channel: ChannelEntry | undefined, parent: ChannelEntry | undefined, permissions: PermissionManager, callback: (properties?: ChannelProperties, permissions?: PermissionValue[]) => any) {
let properties: ChannelProperties = { } as ChannelProperties; //The changes properties
const modal = createModal({
@ -92,7 +102,7 @@ namespace Modals {
callback(properties, updated); //First may create the channel
});
tooltip(modal.htmlTag);
tooltip.initialize(modal.htmlTag);
modal.htmlTag.find(".button_cancel").click(() => {
modal.close();
callback();
@ -121,7 +131,7 @@ namespace Modals {
}
tag.find(".button-select-icon").on('click', event => {
Modals.spawnIconSelect(connection, id => {
spawnIconSelect(connection, id => {
const icon_node = tag.find(".icon-preview");
icon_node.children().remove();
icon_node.append(connection.fileManager.icons.generateTag(id));
@ -145,7 +155,7 @@ namespace Modals {
tag.find(".channel_password").change(function (this: HTMLInputElement) {
properties.channel_flag_password = this.value.length != 0;
if(properties.channel_flag_password)
helpers.hashPassword(this.value).then(pass => properties.channel_password = pass);
hashPassword(this.value).then(pass => properties.channel_password = pass);
channel_password.removeClass("input_error");
if(!properties.channel_flag_password)
@ -708,4 +718,3 @@ namespace Modals {
}).prop("disabled", !permission).parent(".input-boxed").toggleClass("disabled", !permission);
}
}
}

Some files were not shown because too many files have changed in this diff Show more