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 ...APP_FILE_LIST_CLIENT_SOURCE
]; ];
/*
const WEB_APP_FILE_LIST = [ const WEB_APP_FILE_LIST = [
...APP_FILE_LIST_SHARED_SOURCE, ...APP_FILE_LIST_SHARED_SOURCE,
...APP_FILE_LIST_SHARED_VENDORS, ...APP_FILE_LIST_SHARED_VENDORS,
@ -451,6 +452,174 @@ const WEB_APP_FILE_LIST = [
...APP_FILE_LIST_WEB_TEASPEAK, ...APP_FILE_LIST_WEB_TEASPEAK,
...CERTACCEPT_FILE_LIST, ...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 //@ts-ignore
declare module "fs-extra" { declare module "fs-extra" {

View file

@ -1,25 +1,15 @@
/// <reference path="loader.ts" /> import * as loader from "./loader";
interface Window { declare global {
$: JQuery; interface Window {
native_client: boolean;
}
} }
namespace app { const node_require: typeof require = window.require;
export enum Type {
UNKNOWN,
CLIENT_RELEASE,
CLIENT_DEBUG,
WEB_DEBUG,
WEB_RELEASE
}
export let type: Type = Type.UNKNOWN;
export function is_web() { let _ui_version;
return type == Type.WEB_RELEASE || type == Type.WEB_DEBUG; export function ui_version() {
}
let _ui_version;
export function ui_version() {
if(typeof(_ui_version) !== "string") { if(typeof(_ui_version) !== "string") {
const version_node = document.getElementById("app_version"); const version_node = document.getElementById("app_version");
if(!version_node) return undefined; if(!version_node) return undefined;
@ -30,12 +20,20 @@ namespace app {
return (_ui_version = version); return (_ui_version = version);
} }
return _ui_version; return _ui_version;
}
} }
/* all javascript loaders */ /* all javascript loaders */
const loader_javascript = { const loader_javascript = {
detect_type: async () => { 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) { if(window.require) {
const request = new Request("js/proto.js"); const request = new Request("js/proto.js");
let file_path = request.url; let file_path = request.url;
@ -43,11 +41,11 @@ const loader_javascript = {
throw "Invalid file path (" + file_path + ")"; throw "Invalid file path (" + file_path + ")";
file_path = file_path.substring(process.platform === "win32" ? 8 : 7); file_path = file_path.substring(process.platform === "win32" ? 8 : 7);
const fs = require('fs'); const fs = node_require('fs');
if(fs.existsSync(file_path)) { if(fs.existsSync(file_path)) {
app.type = app.Type.CLIENT_DEBUG; //type = Type.CLIENT_DEBUG;
} else { } else {
app.type = app.Type.CLIENT_RELEASE; //type = Type.CLIENT_RELEASE;
} }
} else { } else {
/* test if js/proto.js is available. If so we're in debug mode */ /* test if js/proto.js is available. If so we're in debug mode */
@ -58,9 +56,9 @@ const loader_javascript = {
request.onreadystatechange = () => { request.onreadystatechange = () => {
if (request.readyState === 4){ if (request.readyState === 4){
if (request.status === 404) { if (request.status === 404) {
app.type = app.Type.WEB_RELEASE; //type = Type.WEB_RELEASE;
} else { } else {
app.type = app.Type.WEB_DEBUG; //type = Type.WEB_DEBUG;
} }
resolve(); resolve();
} }
@ -101,7 +99,7 @@ const loader_javascript = {
["vendor/emoji-picker/src/jquery.lsxemojipicker.js"] ["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, { loader.register_task(loader.Stage.JAVASCRIPT, {
name: "scripts release", name: "scripts release",
priority: 20, priority: 20,
@ -116,176 +114,8 @@ const loader_javascript = {
} }
}, },
load_scripts_debug: async () => { load_scripts_debug: async () => {
/* test if we're loading as TeaClient or WebClient */ await loader.load_scripts(["js/shared-app.js"])
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");
}, },
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 () => { load_release: async () => {
console.log("Load for release!"); console.log("Load for release!");
@ -329,7 +159,7 @@ const loader_style = {
["vendor/highlight/styles/darcula.css", ""], /* empty string means not required */ ["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(); await loader_style.load_style_debug();
} else { } else {
await loader_style.load_style_release(); await loader_style.load_style_release();
@ -494,23 +324,10 @@ loader.register_task(loader.Stage.TEMPLATES, {
loader.register_task(loader.Stage.LOADED, { loader.register_task(loader.Stage.LOADED, {
name: "loaded handler", name: "loaded handler",
function: async () => { function: async () => loader.hide_overlay(),
fadeoutLoader();
},
priority: 5 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, { loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "lsx emoji picker setup", name: "lsx emoji picker setup",
function: async () => await (window as any).setup_lsx_emoji_picker({twemoji: typeof(window.twemoji) !== "undefined"}), function: async () => await (window as any).setup_lsx_emoji_picker({twemoji: typeof(window.twemoji) !== "undefined"}),
@ -576,31 +393,26 @@ loader.register_task(loader.Stage.SETUP, {
priority: 100 priority: 100
}); });
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { export function run() {
name: "log enabled initialisation", window["Module"] = (window["Module"] || {}) as any;
function: async () => log.initialize(app.type === app.Type.CLIENT_DEBUG || app.type === app.Type.WEB_DEBUG ? log.LogType.TRACE : log.LogType.INFO), /* TeaClient */
priority: 150 if(node_require) {
}); const path = node_require("path");
const remote = node_require('electron').remote;
window["Module"] = (window["Module"] || {}) as any;
/* TeaClient */
if(window.require) {
const path = require("path");
const remote = require('electron').remote;
module.paths.push(path.join(remote.app.getAppPath(), "/modules")); module.paths.push(path.join(remote.app.getAppPath(), "/modules"));
module.paths.push(path.join(path.dirname(remote.getGlobal("browser-root")), "js")); module.paths.push(path.join(path.dirname(remote.getGlobal("browser-root")), "js"));
const connector = require("renderer"); //TODO: HERE!
console.log(connector); const connector = node_require("renderer");
loader.register_task(loader.Stage.INITIALIZING, { loader.register_task(loader.Stage.INITIALIZING, {
name: "teaclient initialize", name: "teaclient initialize",
function: connector.initialize, function: connector.initialize,
priority: 40 priority: 40
}); });
} }
if(!loader.running()) { if(!loader.running()) {
/* we know that we want to load the app */ /* we know that we want to load the app */
loader.execute_managed(); loader.execute_managed();
}
} }

View file

@ -1,4 +1,4 @@
/// <reference path="loader.ts" /> import * as loader from "./loader";
let is_debug = false; let is_debug = false;
@ -25,34 +25,8 @@ const loader_javascript = {
load_scripts: async () => { load_scripts: async () => {
await loader.load_script(["vendor/jquery/jquery.min.js"]); 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([ await loader.load_scripts([
["js/proto.js"], ["dist/certificate-popup.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"]
]); ]);
} }
}; };
@ -99,9 +73,7 @@ loader.register_task(loader.Stage.STYLE, {
loader.register_task(loader.Stage.LOADED, { loader.register_task(loader.Stage.LOADED, {
name: "loaded handler", name: "loaded handler",
function: async () => { function: async () => loader.hide_overlay(),
fadeoutLoader();
},
priority: 0 priority: 0
}); });
@ -123,25 +95,6 @@ loader.register_task(loader.Stage.INITIALIZING, {
priority: 50 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()) { if(!loader.running()) {
/* we know that we want to load the app */ /* we know that we want to load the app */
loader.execute_managed(); 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" "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": { "clap": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", "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", "minify-web-rel-file": "terser --compress --mangle --ecma 6 --keep_classnames --keep_fnames --output",
"start": "npm run compile-file-helper && node file.js ndevelop", "start": "npm run compile-file-helper && node file.js ndevelop",
"build": "webpack --config webpack.config.js", "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)", "author": "TeaSpeak (WolverinDEV)",
"license": "ISC", "license": "ISC",
@ -63,6 +65,7 @@
"homepage": "https://www.teaspeak.de", "homepage": "https://www.teaspeak.de",
"dependencies": { "dependencies": {
"@types/fs-extra": "^8.0.1", "@types/fs-extra": "^8.0.1",
"circular-dependency-plugin": "^5.2.0",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^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 { &.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 { &.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"> <meta name="app-loader-target" content="app">
<div id="scripts"> <div id="scripts">
<!--
<script type="application/javascript" src="loader/loader_app.min.js" async defer></script> <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_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> </div>
<!-- Loading screen --> <!-- Loading screen -->

View file

@ -2736,13 +2736,13 @@
</div> </div>
{{else type == "default" }} {{else type == "default" }}
<div class="entry default {{if selected}}selected{{/if}}"> <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 class="name">{{tr "English (Default / Fallback)" /}}</div>
</div> </div>
{{else}} {{else}}
<div class="entry translation {{if selected}}selected{{/if}}" parent-repository="{{:id}}"> <div class="entry translation {{if selected}}selected{{/if}}" parent-repository="{{:id}}">
<div class="country flag-{{:country_code}}" <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="name">{{> name}}</div>
<div class="button button-info"> <div class="button button-info">
<div class="icon client-about"></div> <div class="icon client-about"></div>

View file

@ -1,49 +1,47 @@
interface Window { import * as log from "tc-shared/log";
BroadcastChannel: BroadcastChannel; import {LogCategory} from "tc-shared/log";
}
namespace bipc { export interface BroadcastMessage {
export interface BroadcastMessage {
timestamp: number; timestamp: number;
receiver: string; receiver: string;
sender: string; sender: string;
type: string; type: string;
data: any; data: any;
} }
function uuidv4() { function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16); return v.toString(16);
}); });
} }
interface ProcessQuery { interface ProcessQuery {
timestamp: number timestamp: number
query_id: string; query_id: string;
} }
export interface ChannelMessage { export interface ChannelMessage {
channel_id: string; channel_id: string;
type: string; type: string;
data: any; data: any;
} }
export interface ProcessQueryResponse { export interface ProcessQueryResponse {
request_timestamp: number request_timestamp: number
request_query_id: string; request_query_id: string;
device_id: string; device_id: string;
protocol: number; protocol: number;
} }
export interface CertificateAcceptCallback { export interface CertificateAcceptCallback {
request_id: string; request_id: string;
} }
export interface CertificateAcceptSucceeded { } export interface CertificateAcceptSucceeded { }
export abstract class BasicIPCHandler { export abstract class BasicIPCHandler {
protected static readonly BROADCAST_UNIQUE_ID = "00000000-0000-4000-0000-000000000000"; protected static readonly BROADCAST_UNIQUE_ID = "00000000-0000-4000-0000-000000000000";
protected static readonly PROTOCOL_VERSION = 1; protected static readonly PROTOCOL_VERSION = 1;
@ -195,17 +193,17 @@ namespace bipc {
} as CertificateAcceptCallback, data[0]); } as CertificateAcceptCallback, data[0]);
}) })
} }
} }
export interface Channel { export interface Channel {
readonly channel_id: string; readonly channel_id: string;
target_id?: string; target_id?: string;
message_handler: (remote_id: string, broadcast: boolean, message: ChannelMessage) => any; message_handler: (remote_id: string, broadcast: boolean, message: ChannelMessage) => any;
send_message(type: string, message: any, target?: string); send_message(type: string, message: any, target?: string);
} }
class BroadcastChannelIPC extends BasicIPCHandler { class BroadcastChannelIPC extends BasicIPCHandler {
private static readonly CHANNEL_NAME = "TeaSpeak-Web"; private static readonly CHANNEL_NAME = "TeaSpeak-Web";
private channel: BroadcastChannel; private channel: BroadcastChannel;
@ -253,9 +251,9 @@ namespace bipc {
this.channel.postMessage(JSON.stringify(message)); this.channel.postMessage(JSON.stringify(message));
} }
} }
export namespace connect { export namespace connect {
export type ConnectRequestData = { export type ConnectRequestData = {
address: string; address: string;
@ -481,9 +479,9 @@ namespace bipc {
}) })
} }
} }
} }
export namespace mproxy { export namespace mproxy {
export interface MethodProxyInvokeData { export interface MethodProxyInvokeData {
method_name: string; method_name: string;
arguments: any[]; arguments: any[];
@ -696,12 +694,12 @@ namespace bipc {
protected abstract on_connected(); protected abstract on_connected();
protected abstract on_disconnected(); protected abstract on_disconnected();
} }
} }
let handler: BasicIPCHandler; let handler: BasicIPCHandler;
let connect_handler: connect.ConnectHandler; let connect_handler: connect.ConnectHandler;
export function setup() { export function setup() {
if(!supported()) if(!supported())
return; return;
@ -710,18 +708,17 @@ namespace bipc {
connect_handler = new connect.ConnectHandler(handler); connect_handler = new connect.ConnectHandler(handler);
connect_handler.setup(); connect_handler.setup();
} }
export function get_handler() { export function get_handler() {
return handler; return handler;
} }
export function get_connect_handler() { export function get_connect_handler() {
return connect_handler; return connect_handler;
} }
export function supported() { export function supported() {
/* ios does not support this */ /* ios does not support this */
return typeof(window.BroadcastChannel) !== "undefined"; return typeof(window.BroadcastChannel) !== "undefined";
}
} }

View file

@ -1,14 +1,38 @@
/// <reference path="log.ts" /> import {ChannelTree} from "tc-shared/ui/view";
/// <reference path="proto.ts" /> import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
/// <reference path="ui/view.ts" /> import {PermissionManager} from "tc-shared/permission/PermissionManager";
/// <reference path="settings.ts" /> import {GroupManager} from "tc-shared/permission/GroupManager";
/// <reference path="FileManager.ts" /> import {ServerSettings, Settings, StaticSettings} from "tc-shared/settings";
/// <reference path="permission/PermissionManager.ts" /> import {Sound, SoundManager} from "tc-shared/sound/Sounds";
/// <reference path="permission/GroupManager.ts" /> import {LocalClientEntry} from "tc-shared/ui/client";
/// <reference path="ui/frames/ControlBar.ts" /> import {ServerLog} from "tc-shared/ui/frames/server_log";
/// <reference path="connection/ConnectionBase.ts" /> 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, HANDLER_DESTROYED,
REQUESTED, REQUESTED,
DNS_FAILED, DNS_FAILED,
@ -28,7 +52,7 @@ enum DisconnectReason {
UNKNOWN UNKNOWN
} }
enum ConnectionState { export enum ConnectionState {
UNCONNECTED, UNCONNECTED,
CONNECTING, CONNECTING,
INITIALISING, INITIALISING,
@ -36,7 +60,7 @@ enum ConnectionState {
DISCONNECTING DISCONNECTING
} }
enum ViewReasonId { export enum ViewReasonId {
VREASON_USER_ACTION = 0, VREASON_USER_ACTION = 0,
VREASON_MOVED = 1, VREASON_MOVED = 1,
VREASON_SYSTEM = 2, VREASON_SYSTEM = 2,
@ -51,7 +75,7 @@ enum ViewReasonId {
VREASON_SERVER_SHUTDOWN = 11 VREASON_SERVER_SHUTDOWN = 11
} }
interface VoiceStatus { export interface VoiceStatus {
input_hardware: boolean; input_hardware: boolean;
input_muted: boolean; input_muted: boolean;
output_muted: boolean; output_muted: boolean;
@ -68,7 +92,7 @@ interface VoiceStatus {
queries_visible: boolean; queries_visible: boolean;
} }
interface ConnectParameters { export interface ConnectParameters {
nickname?: string; nickname?: string;
channel?: { channel?: {
target: string | number; target: string | number;
@ -79,20 +103,21 @@ interface ConnectParameters {
auto_reconnect_attempt?: boolean; auto_reconnect_attempt?: boolean;
} }
class ConnectionHandler { declare const native_client;
export class ConnectionHandler {
channelTree: ChannelTree; channelTree: ChannelTree;
serverConnection: connection.AbstractServerConnection; serverConnection: AbstractServerConnection;
fileManager: FileManager; fileManager: FileManager;
permissions: PermissionManager; permissions: PermissionManager;
groups: GroupManager; groups: GroupManager;
side_bar: chat.Frame; side_bar: Frame;
settings: ServerSettings; settings: ServerSettings;
sound: sound.SoundManager; sound: SoundManager;
hostbanner: Hostbanner; hostbanner: Hostbanner;
@ -121,15 +146,15 @@ class ConnectionHandler {
}; };
invoke_resized_on_activate: boolean = false; invoke_resized_on_activate: boolean = false;
log: log.ServerLog; log: ServerLog;
constructor() { constructor() {
this.settings = new ServerSettings(); this.settings = new ServerSettings();
this.log = new log.ServerLog(this); this.log = new ServerLog(this);
this.channelTree = new ChannelTree(this); this.channelTree = new ChannelTree(this);
this.side_bar = new chat.Frame(this); this.side_bar = new Frame(this);
this.sound = new sound.SoundManager(this); this.sound = new SoundManager(this);
this.hostbanner = new Hostbanner(this); this.hostbanner = new Hostbanner(this);
this.serverConnection = connection.spawn_server_connection(this); this.serverConnection = connection.spawn_server_connection(this);
@ -169,7 +194,7 @@ class ConnectionHandler {
setup() { } 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.tab_set_name(tr("Connecting"));
this.cancel_reconnect(false); this.cancel_reconnect(false);
this._reconnect_attempt = parameters.auto_reconnect_attempt || 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); 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: { address: {
server_hostname: server_address.host, server_hostname: server_address.host,
server_port: server_address.port server_port: server_address.port
@ -203,7 +228,7 @@ class ConnectionHandler {
if(parameters.password && !parameters.password.hashed){ if(parameters.password && !parameters.password.hashed){
try { try {
const password = await helpers.hashPassword(parameters.password.password); const password = await hashPassword(parameters.password.password);
parameters.password = { parameters.password = {
hashed: true, hashed: true,
password: password password: password
@ -221,9 +246,9 @@ class ConnectionHandler {
} }
const original_address = {host: server_address.host, port: server_address.port}; 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; 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 { try {
const resolved = await dns.resolve_address(server_address, { timeout: 5000 }) || {} as any; const resolved = await dns.resolve_address(server_address, { timeout: 5000 }) || {} as any;
if(id != this._connect_initialize_id) 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.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; 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: { address: {
server_port: server_address.port, server_port: server_address.port,
server_hostname: server_address.host 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(() => { setTimeout(() => {
const connected = this.serverConnection.connected(); const connected = this.serverConnection.connected();
if(user_action && connected) { if(user_action && connected) {
@ -270,7 +295,7 @@ class ConnectionHandler {
return this._clientId; 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 */ 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); const cprops = this.reconnect_properties(profile);
this.startConnection(properties.connect_address, profile, true, cprops); this.startConnection(properties.connect_address, profile, true, cprops);
}); });
@ -418,12 +443,12 @@ class ConnectionHandler {
case DisconnectReason.HANDLER_DESTROYED: case DisconnectReason.HANDLER_DESTROYED:
if(data) { if(data) {
this.sound.play(Sound.CONNECTION_DISCONNECTED); this.sound.play(Sound.CONNECTION_DISCONNECTED);
this.log.log(log.server.Type.DISCONNECTED, {}); this.log.log(server_log.Type.DISCONNECTED, {});
} }
break; break;
case DisconnectReason.DNS_FAILED: case DisconnectReason.DNS_FAILED:
log.error(LogCategory.CLIENT, tr("Failed to resolve hostname: %o"), data); 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 message: data as any
}); });
this.sound.play(Sound.CONNECTION_REFUSED); this.sound.play(Sound.CONNECTION_REFUSED);
@ -431,7 +456,7 @@ class ConnectionHandler {
case DisconnectReason.CONNECT_FAILURE: case DisconnectReason.CONNECT_FAILURE:
if(this._reconnect_attempt) { if(this._reconnect_attempt) {
auto_reconnect = true; auto_reconnect = true;
this.log.log(log.server.Type.CONNECTION_FAILED, {}); this.log.log(server_log.Type.CONNECTION_FAILED, {});
break; break;
} }
if(data) if(data)
@ -452,7 +477,7 @@ class ConnectionHandler {
this._certificate_modal = createErrorModal( this._certificate_modal = createErrorModal(
tr("Could not connect"), 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.close_listener.push(() => this._certificate_modal = undefined);
this._certificate_modal.open(); this._certificate_modal.open();
@ -470,7 +495,7 @@ class ConnectionHandler {
case DisconnectReason.HANDSHAKE_TEAMSPEAK_REQUIRED: case DisconnectReason.HANDSHAKE_TEAMSPEAK_REQUIRED:
createErrorModal( createErrorModal(
tr("Target server is a TeamSpeak server"), 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(); ).open();
this.sound.play(Sound.CONNECTION_DISCONNECTED); this.sound.play(Sound.CONNECTION_DISCONNECTED);
auto_reconnect = false; auto_reconnect = false;
@ -478,7 +503,7 @@ class ConnectionHandler {
case DisconnectReason.IDENTITY_TOO_LOW: case DisconnectReason.IDENTITY_TOO_LOW:
createErrorModal( createErrorModal(
tr("Identity level is too low"), 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(); ).open();
this.sound.play(Sound.CONNECTION_DISCONNECTED); this.sound.play(Sound.CONNECTION_DISCONNECTED);
@ -506,7 +531,7 @@ class ConnectionHandler {
break; break;
case DisconnectReason.SERVER_CLOSED: 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( createErrorModal(
tr("Server closed"), tr("Server closed"),
@ -518,7 +543,7 @@ class ConnectionHandler {
auto_reconnect = true; auto_reconnect = true;
break; break;
case DisconnectReason.SERVER_REQUIRES_PASSWORD: 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 => { createInputModal(tr("Server password"), tr("Enter server password:"), password => password.length != 0, password => {
if(!(typeof password === "string")) return; if(!(typeof password === "string")) return;
@ -540,7 +565,7 @@ class ConnectionHandler {
const have_invoker = typeof(data["invokerid"]) !== "undefined" && parseInt(data["invokerid"]) !== 0; const have_invoker = typeof(data["invokerid"]) !== "undefined" && parseInt(data["invokerid"]) !== 0;
const modal = createErrorModal( const modal = createErrorModal(
tr("You've been kicked"), 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 ? tr("You've been kicked from the server by {0}:{:br:}{1}") : tr("You've been kicked from the server:{:br:}{1}"),
have_invoker ? have_invoker ?
htmltags.generate_client_object({ client_id: parseInt(data["invokerid"]), client_unique_id: data["invokeruid"], client_name: data["invokername"]}) : 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); this.sound.play(Sound.CONNECTION_BANNED);
break; break;
case DisconnectReason.CLIENT_BANNED: case DisconnectReason.CLIENT_BANNED:
this.log.log(log.server.Type.SERVER_BANNED, { this.log.log(server_log.Type.SERVER_BANNED, {
invoker: { invoker: {
client_name: data["invokername"], client_name: data["invokername"],
client_id: parseInt(data["invokerid"]), 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...")); log.info(LogCategory.NETWORKING, tr("Allowed to auto reconnect but cant reconnect because we dont have any information left..."));
return; 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")); log.info(LogCategory.NETWORKING, tr("Allowed to auto reconnect. Reconnecting in 5000ms"));
const server_address = this.serverConnection.remote_address(); const server_address = this.serverConnection.remote_address();
@ -600,7 +625,7 @@ class ConnectionHandler {
this._reconnect_timer = setTimeout(() => { this._reconnect_timer = setTimeout(() => {
this._reconnect_timer = undefined; 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...")); 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})); 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) { cancel_reconnect(log_event: boolean) {
if(this._reconnect_timer) { 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); clearTimeout(this._reconnect_timer);
this._reconnect_timer = undefined; this._reconnect_timer = undefined;
} }
@ -665,7 +690,7 @@ class ConnectionHandler {
if(Object.keys(property_update).length > 0) { if(Object.keys(property_update).length > 0) {
this.serverConnection.send_command("clientupdate", property_update).catch(error => { this.serverConnection.send_command("clientupdate", property_update).catch(error => {
log.warn(LogCategory.GENERAL, tr("Failed to update client audio hardware properties. Error: %o"), 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) */ /* Update these properties anyways (for case the server fails to handle the command) */
const updates = []; const updates = [];
@ -708,15 +733,15 @@ class ConnectionHandler {
const input = vconnection.voice_recorder().input; const input = vconnection.voice_recorder().input;
if(input) { if(input) {
if(active && this.serverConnection.connected()) { if(active && this.serverConnection.connected()) {
if(input.current_state() === audio.recorder.InputState.PAUSED) { if(input.current_state() === InputState.PAUSED) {
input.start().then(result => { input.start().then(result => {
if(result != audio.recorder.InputStartResult.EOK) if(result != InputStartResult.EOK)
throw result; throw result;
}).catch(error => { }).catch(error => {
log.warn(LogCategory.VOICE, tr("Failed to start microphone input (%s)."), error); log.warn(LogCategory.VOICE, tr("Failed to start microphone input (%s)."), error);
if(Date.now() - (this._last_record_error_popup || 0) > 10 * 1000) { if(Date.now() - (this._last_record_error_popup || 0) > 10 * 1000) {
this._last_record_error_popup = Date.now(); 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 client_output_hardware: this.client_status.sound_playback_supported
}).catch(error => { }).catch(error => {
log.warn(LogCategory.GENERAL, tr("Failed to sync handler state with server. Error: %o"), 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 : "", client_away_message: typeof(this.client_status.away) === "string" ? this.client_status.away : "",
}).catch(error => { }).catch(error => {
log.warn(LogCategory.GENERAL, tr("Failed to update away status. Error: %o"), 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(); 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() : "") || const name = (this.getClient() ? this.getClient().clientNickName() : "") ||
(this.serverConnection && this.serverConnection.handshake_handler() ? this.serverConnection.handshake_handler().parameters.nickname : "") || (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"; "Another TeaSpeak user";
const channel = (this.getClient() && this.getClient().currentChannel() ? this.getClient().currentChannel().channelId : 0) || 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 : ""); (this.serverConnection && this.serverConnection.handshake_handler() ? (this.serverConnection.handshake_handler().parameters.channel || {} as any).target : "");
@ -798,7 +823,7 @@ class ConnectionHandler {
} }
update_avatar() { update_avatar() {
Modals.spawnAvatarUpload(data => { spawnAvatarUpload(data => {
if(typeof(data) === "undefined") if(typeof(data) === "undefined")
return; return;
if(data === null) { if(data === null) {
@ -814,16 +839,16 @@ class ConnectionHandler {
let message; let message;
if(error instanceof CommandResult) 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) 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(); createErrorModal(tr("Failed to delete avatar"), message).open();
return; return;
}); });
} else { } else {
log.info(LogCategory.CLIENT, tr("Uploading new avatar")); log.info(LogCategory.CLIENT, tr("Uploading new avatar"));
(async () => { (async () => {
let key: transfer.UploadKey; let key: UploadKey;
try { try {
key = await this.fileManager.upload_file({ key = await this.fileManager.upload_file({
size: data.byteLength, size: data.byteLength,
@ -840,28 +865,28 @@ class ConnectionHandler {
//TODO: Resolve permission name //TODO: Resolve permission name
//i_client_max_avatar_filesize //i_client_max_avatar_filesize
if(error.id == ErrorID.PERMISSION_ERROR) { 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 { } 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) 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(); createErrorModal(tr("Failed to upload avatar"), message).open();
return; return;
} }
try { try {
await transfer.spawn_upload_transfer(key).put_data(data); await spawn_upload_transfer(key).put_data(data);
} catch(error) { } catch(error) {
log.error(LogCategory.GENERAL, tr("Failed to upload avatar: %o"), error); log.error(LogCategory.GENERAL, tr("Failed to upload avatar: %o"), error);
let message; let message;
if(typeof(error) === "string") 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) 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(); createErrorModal(tr("Failed to upload avatar"), message).open();
return; return;
} }
@ -874,9 +899,9 @@ class ConnectionHandler {
let message; let message;
if(error instanceof CommandResult) 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) 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(); createErrorModal(tr("Failed to set avatar"), message).open();
return; return;
} }

View file

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

View file

@ -1,5 +1,12 @@
namespace messages.formatter { import {Settings, settings} from "tc-shared/settings";
export namespace bbcode { 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 = (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}) --]/; 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}) --]/;
const sanitizer_escaped_map: {[key: string]: string} = {}; const sanitizer_escaped_map: {[key: string]: string} = {};
@ -118,7 +125,7 @@ namespace messages.formatter {
}, },
name: tr("Open URL in Browser"), name: tr("Open URL in Browser"),
type: contextmenu.MenuEntryType.ENTRY, type: contextmenu.MenuEntryType.ENTRY,
visible: !app.is_web() && false // Currently not possible visible: loader.version().type === "native" && false // Currently not possible
}, contextmenu.Entry.HR(), { }, contextmenu.Entry.HR(), {
callback: () => copy_to_clipboard(url), callback: () => copy_to_clipboard(url),
name: tr("Copy URL to clipboard"), name: tr("Copy URL to clipboard"),
@ -236,9 +243,9 @@ namespace messages.formatter {
}, },
priority: 10 priority: 10
}); });
} }
export function sanitize_text(text: string) : string { export function sanitize_text(text: string) : string {
return $(DOMPurify.sanitize("<a>" + text + "</a>", { return $(DOMPurify.sanitize("<a>" + text + "</a>", {
ADD_ATTR: [ ADD_ATTR: [
"x-highlight-type", "x-highlight-type",
@ -246,5 +253,28 @@ namespace messages.formatter {
"x-image-url" "x-image-url"
] ]
})).text(); })).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_CANCEL = 3,
KEY_HELP = 6, KEY_HELP = 6,
KEY_BACK_SPACE = 8, KEY_BACK_SPACE = 8,
@ -118,44 +118,43 @@ enum KeyCode {
KEY_META = 224 KEY_META = 224
} }
namespace ppt { export enum EventType {
export enum EventType {
KEY_PRESS, KEY_PRESS,
KEY_RELEASE, KEY_RELEASE,
KEY_TYPED KEY_TYPED
} }
export enum SpecialKey { export enum SpecialKey {
CTRL, CTRL,
WINDOWS, WINDOWS,
SHIFT, SHIFT,
ALT ALT
} }
export interface KeyDescriptor { export interface KeyDescriptor {
key_code: string; key_code: string;
key_ctrl: boolean; key_ctrl: boolean;
key_windows: boolean; key_windows: boolean;
key_shift: boolean; key_shift: boolean;
key_alt: boolean; key_alt: boolean;
} }
export interface KeyEvent extends KeyDescriptor { export interface KeyEvent extends KeyDescriptor {
readonly type: EventType; readonly type: EventType;
readonly key: string; readonly key: string;
} }
export interface KeyHook extends KeyDescriptor { export interface KeyHook extends KeyDescriptor {
cancel: boolean; cancel: boolean;
callback_press: () => any; callback_press: () => any;
callback_release: () => any; callback_release: () => any;
} }
export function key_description(key: KeyDescriptor) { export function key_description(key: KeyDescriptor) {
let result = ""; let result = "";
if(key.key_shift) if(key.key_shift)
result += " + " + tr("Shift"); result += " + " + tr("Shift");
@ -172,5 +171,4 @@ namespace ppt {
if(key.key_code) if(key.key_code)
result += " + " + key.key_code; result += " + " + key.key_code;
return result.substr(3); 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 { import * as log from "tc-shared/log";
function guid() { import {LogCategory} from "tc-shared/log";
function s4() { import {guid} from "tc-shared/crypto/uid";
return Math import {createErrorModal, createInfoModal, createInputModal} from "tc-shared/ui/elements/Modal";
.floor((1 + Math.random()) * 0x10000) import {default_profile, find_profile} from "tc-shared/profiles/ConnectionProfile";
.toString(16) import {server_connections} from "tc-shared/ui/frames/connection_handlers";
.substring(1); import {spawnConnectModal} from "tc-shared/ui/modal/ModalConnect";
} import {control_bar} from "tc-shared/ui/frames/ControlBar";
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); import * as top_menu from "./ui/frames/MenuBar";
}
export const boorkmak_connect = (mark: Bookmark, new_tab?: boolean) => { 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()) { if(profile.valid()) {
const connection = (typeof(new_tab) !== "boolean" || !new_tab) ? server_connections.active_connection_handler() : server_connections.spawn_server_connection_handler(); 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); server_connections.set_active_connection_handler(connection);
@ -30,7 +29,7 @@ namespace bookmarks {
} }
); );
} else { } else {
Modals.spawnConnectModal({}, { spawnConnectModal({}, {
url: mark.server_properties.server_address + ":" + mark.server_properties.server_port, url: mark.server_properties.server_address + ":" + mark.server_properties.server_port,
enforce: true enforce: true
}, { }, {
@ -38,21 +37,21 @@ namespace bookmarks {
enforce: true enforce: true
}) })
} }
}; };
export interface ServerProperties { export interface ServerProperties {
server_address: string; server_address: string;
server_port: number; server_port: number;
server_password_hash?: string; server_password_hash?: string;
server_password?: string; server_password?: string;
} }
export enum BookmarkType { export enum BookmarkType {
ENTRY, ENTRY,
DIRECTORY DIRECTORY
} }
export interface Bookmark { export interface Bookmark {
type: /* BookmarkType.ENTRY */ BookmarkType; type: /* BookmarkType.ENTRY */ BookmarkType;
/* readonly */ parent: DirectoryBookmark; /* readonly */ parent: DirectoryBookmark;
@ -68,25 +67,25 @@ namespace bookmarks {
connect_profile: string; connect_profile: string;
last_icon_id?: number; last_icon_id?: number;
} }
export interface DirectoryBookmark { export interface DirectoryBookmark {
type: /* BookmarkType.DIRECTORY */ BookmarkType; type: /* BookmarkType.DIRECTORY */ BookmarkType;
/* readonly */ parent: DirectoryBookmark; /* readonly */ parent: DirectoryBookmark;
readonly content: (Bookmark | DirectoryBookmark)[]; readonly content: (Bookmark | DirectoryBookmark)[];
unique_id: string; unique_id: string;
display_name: string; display_name: string;
} }
interface BookmarkConfig { interface BookmarkConfig {
root_bookmark?: DirectoryBookmark; root_bookmark?: DirectoryBookmark;
default_added?: boolean; default_added?: boolean;
} }
let _bookmark_config: BookmarkConfig; let _bookmark_config: BookmarkConfig;
function bookmark_config() : BookmarkConfig { function bookmark_config() : BookmarkConfig {
if(_bookmark_config) if(_bookmark_config)
return _bookmark_config; return _bookmark_config;
@ -122,21 +121,21 @@ namespace bookmarks {
fix_parent(_bookmark_config.root_bookmark, entry); fix_parent(_bookmark_config.root_bookmark, entry);
return _bookmark_config; return _bookmark_config;
} }
function save_config() { function save_config() {
localStorage.setItem("bookmarks", JSON.stringify(bookmark_config(), (key, value) => { localStorage.setItem("bookmarks", JSON.stringify(bookmark_config(), (key, value) => {
if(key === "parent") if(key === "parent")
return undefined; return undefined;
return value; return value;
})); }));
} }
export function bookmarks() : DirectoryBookmark { export function bookmarks() : DirectoryBookmark {
return bookmark_config().root_bookmark; return bookmark_config().root_bookmark;
} }
export function bookmarks_flat() : Bookmark[] { export function bookmarks_flat() : Bookmark[] {
const result: Bookmark[] = []; const result: Bookmark[] = [];
const _flat = (bookmark: Bookmark | DirectoryBookmark) => { const _flat = (bookmark: Bookmark | DirectoryBookmark) => {
if(bookmark.type == BookmarkType.DIRECTORY) if(bookmark.type == BookmarkType.DIRECTORY)
@ -147,9 +146,9 @@ namespace bookmarks {
}; };
_flat(bookmark_config().root_bookmark); _flat(bookmark_config().root_bookmark);
return result; return result;
} }
function find_bookmark_recursive(parent: DirectoryBookmark, uuid: string) : Bookmark | DirectoryBookmark { function find_bookmark_recursive(parent: DirectoryBookmark, uuid: string) : Bookmark | DirectoryBookmark {
for(const entry of parent.content) { for(const entry of parent.content) {
if(entry.unique_id == uuid) if(entry.unique_id == uuid)
return entry; return entry;
@ -159,13 +158,13 @@ namespace bookmarks {
} }
} }
return undefined; return undefined;
} }
export function find_bookmark(uuid: string) : Bookmark | DirectoryBookmark | undefined { export function find_bookmark(uuid: string) : Bookmark | DirectoryBookmark | undefined {
return find_bookmark_recursive(bookmarks(), uuid); return find_bookmark_recursive(bookmarks(), uuid);
} }
export function parent_bookmark(bookmark: Bookmark) : DirectoryBookmark { export function parent_bookmark(bookmark: Bookmark) : DirectoryBookmark {
const books: (DirectoryBookmark | Bookmark)[] = [bookmarks()]; const books: (DirectoryBookmark | Bookmark)[] = [bookmarks()];
while(!books.length) { while(!books.length) {
const directory = books.pop_front(); const directory = books.pop_front();
@ -178,9 +177,9 @@ namespace bookmarks {
} }
} }
return bookmarks(); return bookmarks();
} }
export function create_bookmark(display_name: string, directory: DirectoryBookmark, server_properties: ServerProperties, nickname: string) : Bookmark { export function create_bookmark(display_name: string, directory: DirectoryBookmark, server_properties: ServerProperties, nickname: string) : Bookmark {
const bookmark = { const bookmark = {
display_name: display_name, display_name: display_name,
server_properties: server_properties, server_properties: server_properties,
@ -193,9 +192,9 @@ namespace bookmarks {
directory.content.push(bookmark); directory.content.push(bookmark);
return bookmark; return bookmark;
} }
export function create_bookmark_directory(parent: DirectoryBookmark, name: string) : DirectoryBookmark { export function create_bookmark_directory(parent: DirectoryBookmark, name: string) : DirectoryBookmark {
const bookmark = { const bookmark = {
type: BookmarkType.DIRECTORY, type: BookmarkType.DIRECTORY,
@ -207,19 +206,19 @@ namespace bookmarks {
parent.content.push(bookmark); parent.content.push(bookmark);
return bookmark; return bookmark;
} }
//TODO test if the new parent is within the old bookmark //TODO test if the new parent is within the old bookmark
export function change_directory(parent: DirectoryBookmark, bookmark: Bookmark | DirectoryBookmark) { export function change_directory(parent: DirectoryBookmark, bookmark: Bookmark | DirectoryBookmark) {
delete_bookmark(bookmark); delete_bookmark(bookmark);
parent.content.push(bookmark) parent.content.push(bookmark)
} }
export function save_bookmark(bookmark?: Bookmark | DirectoryBookmark) { export function save_bookmark(bookmark?: Bookmark | DirectoryBookmark) {
save_config(); /* nvm we dont give a fuck... saving everything */ save_config(); /* nvm we dont give a fuck... saving everything */
} }
function delete_bookmark_recursive(parent: DirectoryBookmark, bookmark: Bookmark | DirectoryBookmark) { function delete_bookmark_recursive(parent: DirectoryBookmark, bookmark: Bookmark | DirectoryBookmark) {
const index = parent.content.indexOf(bookmark); const index = parent.content.indexOf(bookmark);
if(index != -1) if(index != -1)
parent.content.remove(bookmark); parent.content.remove(bookmark);
@ -227,13 +226,13 @@ namespace bookmarks {
for(const entry of parent.content) for(const entry of parent.content)
if(entry.type == BookmarkType.DIRECTORY) if(entry.type == BookmarkType.DIRECTORY)
delete_bookmark_recursive(entry as DirectoryBookmark, bookmark) delete_bookmark_recursive(entry as DirectoryBookmark, bookmark)
} }
export function delete_bookmark(bookmark: Bookmark | DirectoryBookmark) { export function delete_bookmark(bookmark: Bookmark | DirectoryBookmark) {
delete_bookmark_recursive(bookmarks(), bookmark) delete_bookmark_recursive(bookmarks(), bookmark)
} }
export function add_current_server() { export function add_current_server() {
const ch = server_connections.active_connection_handler(); const ch = server_connections.active_connection_handler();
if(ch && ch.connected) { if(ch && ch.connected) {
const ce = ch.getClient(); const ce = ch.getClient();
@ -258,5 +257,4 @@ namespace bookmarks {
} else { } else {
createErrorModal(tr("You have to be connected"), tr("You have to be connected!")).open(); 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,16 +1,36 @@
/// <reference path="ConnectionBase.ts" /> 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";
namespace connection { export class ServerConnectionCommandBoss extends AbstractCommandHandlerBoss {
import Conversation = chat.channel.Conversation;
import MusicInfo = chat.MusicInfo;
export class ServerConnectionCommandBoss extends AbstractCommandHandlerBoss {
constructor(connection: AbstractServerConnection) { constructor(connection: AbstractServerConnection) {
super(connection); super(connection);
} }
} }
export class ConnectionCommandHandler extends AbstractCommandHandler { export class ConnectionCommandHandler extends AbstractCommandHandler {
readonly connection: AbstractServerConnection; readonly connection: AbstractServerConnection;
readonly connection_handler: ConnectionHandler; readonly connection_handler: ConnectionHandler;
@ -65,7 +85,7 @@ namespace connection {
this["notifyplaylistsongloaded"] = this.handleNotifyPlaylistSongLoaded; 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); const id = parseInt(client_id);
if(typeof(client_id) === "undefined" || Number.isNaN(id)) if(typeof(client_id) === "undefined" || Number.isNaN(id))
return undefined; 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) if(!options.process_result)
return promise; return promise;
@ -96,18 +116,18 @@ namespace connection {
if(res.id == ErrorID.PERMISSION_ERROR) { //Permission error if(res.id == ErrorID.PERMISSION_ERROR) { //Permission error
const permission = this.connection_handler.permissions.resolveInfo(res.json["failed_permid"] as number); 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"); 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) permission: this.connection_handler.permissions.resolveInfo(res.json["failed_permid"] as number)
}); });
this.connection_handler.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS); this.connection_handler.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
} else if(res.id != ErrorID.EMPTY_RESULT) { } 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 message: res.extra_message.length == 0 ? res.message : res.extra_message
}); });
} }
} }
} else if(typeof(ex) === "string") { } 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 { } else {
log.error(LogCategory.NETWORKING, tr("Invalid promise result type: %s. Result: %o"), typeof (ex), ex); 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 > 0) {
if(properties.virtualserver_hostmessage_mode == 1) { if(properties.virtualserver_hostmessage_mode == 1) {
/* show in log */ /* 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 message: properties.virtualserver_hostmessage
}); });
} else { } else {
/* create modal/create modal and quit */ /* create modal/create modal and quit */
createModal({ createModal({
header: tr("Host message"), header: tr("Host message"),
body: MessageHelper.bbcode_chat(properties.virtualserver_hostmessage), body: bbcode_chat(properties.virtualserver_hostmessage),
footer: undefined footer: undefined
}).open(); }).open();
if(properties.virtualserver_hostmessage_mode == 3) { if(properties.virtualserver_hostmessage_mode == 3) {
/* first let the client initialize his stuff */ /* first let the client initialize his stuff */
setTimeout(() => { 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 message: properties.virtualserver_welcomemessage
}); });
@ -218,7 +238,7 @@ namespace connection {
/* welcome message */ /* welcome message */
if(properties.virtualserver_welcomemessage) { 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 message: properties.virtualserver_welcomemessage
}); });
} }
@ -235,12 +255,12 @@ namespace connection {
}).then(() => { }).then(() => {
createInfoModal(tr("Use privilege key"), tr("Privilege key successfully used!")).open(); createInfoModal(tr("Use privilege key"), tr("Privilege key successfully used!")).open();
}).catch(error => { }).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(); }, { 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() own_client: this.connection_handler.getClient().log_data()
}); });
this.connection_handler.sound.play(Sound.CONNECTION_CONNECTED); 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) { if(this.connection_handler.client_status.queries_visible || client.properties.client_type != ClientType.CLIENT_QUERY) {
const own_channel = this.connection.client.getClient().currentChannel(); 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_from: old_channel ? old_channel.log_data() : undefined,
channel_to: channel ? channel.log_data() : undefined, channel_to: channel ? channel.log_data() : undefined,
client: client.log_data(), client: client.log_data(),
@ -521,7 +541,7 @@ namespace connection {
let channel_to = tree.findChannel(entry["ctid"]); let channel_to = tree.findChannel(entry["ctid"]);
const is_own_channel = channel_from == own_channel; 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_from: channel_from ? channel_from.log_data() : undefined,
channel_to: channel_to ? channel_to.log_data() : undefined, channel_to: channel_to ? channel_to.log_data() : undefined,
client: client.log_data(), client: client.log_data(),
@ -564,7 +584,7 @@ namespace connection {
attach: false attach: false
}); });
if(conversation) { 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(); 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_from: channel_from ? {
channel_id: channel_from.channelId, channel_id: channel_from.channelId,
channel_name: channel_from.channelName() channel_name: channel_from.channelName()
@ -750,7 +770,7 @@ namespace connection {
const target_own = target_client_id === this.connection.client.getClientId(); const target_own = target_client_id === this.connection.client.getClientId();
if(target_own && target_client_id === json["invokerid"]) { 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; return;
} }
@ -808,7 +828,7 @@ namespace connection {
if(conversation.is_unread() && channel) if(conversation.is_unread() && channel)
channel.flag_text_unread = true; channel.flag_text_unread = true;
} else if(mode == 3) { } 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"], message: json["msg"],
sender: { sender: {
client_unique_id: json["invokeruid"], 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.")); log.warn(LogCategory.GENERAL, tr("Received chat close for client, but we haven't a chat open."));
return; return;
} }
conversation.set_state(chat.PrivateConversationState.CLOSED); conversation.set_state(PrivateConversationState.CLOSED);
} }
handleNotifyClientUpdated(json) { handleNotifyClientUpdated(json) {
@ -944,7 +964,7 @@ namespace connection {
handleNotifyClientPoke(json) { handleNotifyClientPoke(json) {
json = json[0]; json = json[0];
Modals.spawnPoke(this.connection_handler, { spawnPoke(this.connection_handler, {
id: parseInt(json["invokerid"]), id: parseInt(json["invokerid"]),
name: json["invokername"], name: json["invokername"],
unique_id: json["invokeruid"] unique_id: json["invokeruid"]
@ -1156,5 +1176,4 @@ namespace connection {
metadata: json["song_metadata"] metadata: json["song_metadata"]
}); });
} }
}
} }

View file

@ -1,5 +1,19 @@
namespace connection { import {ServerCommand, SingleCommandHandler} from "tc-shared/connection/ConnectionBase";
export class CommandHelper extends AbstractCommandHandler { 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 _who_am_i: any;
private _awaiters_unique_ids: {[unique_id: string]:((resolved: ClientNameInfo) => any)[]} = {}; private _awaiters_unique_ids: {[unique_id: string]:((resolved: ClientNameInfo) => any)[]} = {};
private _awaiters_unique_dbid: {[database_id: number]:((resolved: ClientNameInfo) => any)[]} = {}; private _awaiters_unique_dbid: {[database_id: number]:((resolved: ClientNameInfo) => any)[]} = {};
@ -23,7 +37,7 @@ namespace connection {
this._awaiters_unique_ids = undefined; this._awaiters_unique_ids = undefined;
} }
handle_command(command: connection.ServerCommand): boolean { handle_command(command: ServerCommand): boolean {
if(command.command == "notifyclientnamefromuid") if(command.command == "notifyclientnamefromuid")
this.handle_notifyclientnamefromuid(command.arguments); this.handle_notifyclientnamefromuid(command.arguments);
if(command.command == "notifyclientgetnamefromdbid") if(command.command == "notifyclientgetnamefromdbid")
@ -444,5 +458,4 @@ namespace connection {
}); });
}); });
} }
}
} }

View file

@ -1,18 +1,25 @@
namespace connection { import {CommandHelper} from "tc-shared/connection/CommandHelper";
export interface CommandOptions { 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: [] */ flagset?: string[]; /* default: [] */
process_result?: boolean; /* default: true */ process_result?: boolean; /* default: true */
timeout?: number /* default: 1000 */; timeout?: number /* default: 1000 */;
} }
export const CommandOptionDefaults: CommandOptions = { export const CommandOptionDefaults: CommandOptions = {
flagset: [], flagset: [],
process_result: true, process_result: true,
timeout: 1000 timeout: 1000
}; };
export type ConnectionStateListener = (old_state: ConnectionState, new_state: ConnectionState) => any; export type ConnectionStateListener = (old_state: ConnectionState, new_state: ConnectionState) => any;
export abstract class AbstractServerConnection { export abstract class AbstractServerConnection {
readonly client: ConnectionHandler; readonly client: ConnectionHandler;
readonly command_helper: CommandHelper; readonly command_helper: CommandHelper;
@ -44,9 +51,9 @@ namespace connection {
native: number, native: number,
javascript?: number javascript?: number
}; };
} }
export namespace voice { export namespace voice {
export enum PlayerState { export enum PlayerState {
PREBUFFERING, PREBUFFERING,
PLAYING, PLAYING,
@ -105,112 +112,18 @@ namespace connection {
abstract get_encoder_codec() : number; abstract get_encoder_codec() : number;
abstract set_encoder_codec(codec: number); abstract set_encoder_codec(codec: number);
} }
} }
export class ServerCommand { export class ServerCommand {
command: string; command: string;
arguments: any[]; arguments: any[];
} }
export abstract class AbstractCommandHandler { export interface SingleCommandHandler {
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; name?: string;
command?: string; command?: string;
timeout?: number; timeout?: number;
/* if the return is true then the command handler will be removed */ /* if the return is true then the command handler will be removed */
function: (command: ServerCommand) => boolean; 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,20 +1,28 @@
namespace connection { import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
export interface HandshakeIdentityHandler { 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; connection: AbstractServerConnection;
start_handshake(); start_handshake();
register_callback(callback: (success: boolean, message?: string) => any); register_callback(callback: (success: boolean, message?: string) => any);
} }
export class HandshakeHandler { declare const native_client;
export class HandshakeHandler {
private connection: AbstractServerConnection; private connection: AbstractServerConnection;
private handshake_handler: HandshakeIdentityHandler; private handshake_handler: HandshakeIdentityHandler;
private failed = false; private failed = false;
readonly profile: profiles.ConnectionProfile; readonly profile: ConnectionProfile;
readonly parameters: ConnectParameters; readonly parameters: ConnectParameters;
constructor(profile: profiles.ConnectionProfile, parameters: ConnectParameters) { constructor(profile: ConnectionProfile, parameters: ConnectParameters) {
this.profile = profile; this.profile = profile;
this.parameters = parameters; this.parameters = parameters;
} }
@ -48,7 +56,7 @@ namespace connection {
on_teamspeak() { on_teamspeak() {
const type = this.profile.selected_type(); const type = this.profile.selected_type();
if(type == profiles.identities.IdentitifyType.TEAMSPEAK) if(type == IdentitifyType.TEAMSPEAK)
this.handshake_finished(); this.handshake_finished();
else { else {
@ -122,8 +130,8 @@ namespace connection {
} }
/* required to keep compatibility */ /* required to keep compatibility */
if(this.profile.selected_type() === profiles.identities.IdentitifyType.TEAMSPEAK) { if(this.profile.selected_type() === IdentitifyType.TEAMSPEAK) {
data["client_key_offset"] = (this.profile.selected_identity() as profiles.identities.TeaSpeakIdentity).hash_number; data["client_key_offset"] = (this.profile.selected_identity() as TeaSpeakIdentity).hash_number;
} }
this.connection.send_command("clientinit", data).catch(error => { this.connection.send_command("clientinit", data).catch(error => {
@ -142,5 +150,4 @@ namespace connection {
this.connection.disconnect(); this.connection.disconnect();
}); });
} }
}
} }

View file

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

View file

@ -14,24 +14,23 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
namespace asn1 { declare class Int10 {
declare class Int10 {
constructor(value?: any); constructor(value?: any);
sub(sub: number); sub(sub: number);
mulAdd(mul: number, add: number); mulAdd(mul: number, add: number);
simplify(); simplify();
} }
const ellipsis = "\u2026"; const ellipsis = "\u2026";
function string_cut(str, len) { function string_cut(str, len) {
if (str.length > len) if (str.length > len)
str = str.substring(0, len) + ellipsis; str = str.substring(0, len) + ellipsis;
return str; return str;
} }
export class Stream { export class Stream {
private static HEX_DIGITS = "0123456789ABCDEF"; private static HEX_DIGITS = "0123456789ABCDEF";
private static reTimeS = /^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/; private static reTimeS = /^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/;
private static reTimeL = /^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/; private static reTimeL = /^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/;
@ -258,16 +257,16 @@ namespace asn1 {
*/ */
return s; return s;
}; };
} }
export enum TagClass { export enum TagClass {
UNIVERSAL = 0x00, UNIVERSAL = 0x00,
APPLICATION = 0x01, APPLICATION = 0x01,
CONTEXT = 0x02, CONTEXT = 0x02,
PRIVATE = 0x03 PRIVATE = 0x03
} }
export enum TagType { export enum TagType {
EOC = 0x00, EOC = 0x00,
BOOLEAN = 0x01, BOOLEAN = 0x01,
INTEGER = 0x02, INTEGER = 0x02,
@ -295,9 +294,9 @@ namespace asn1 {
GeneralString = 0x1B, GeneralString = 0x1B,
UniversalString = 0x1C, UniversalString = 0x1C,
BMPString = 0x1E BMPString = 0x1E
} }
class ASN1Tag { class ASN1Tag {
tagClass: TagClass; tagClass: TagClass;
type: TagType; type: TagType;
tagConstructed: boolean; tagConstructed: boolean;
@ -325,9 +324,9 @@ namespace asn1 {
isEOC() { isEOC() {
return this.tagClass === 0x00 && this.tagNumber === 0x00; return this.tagClass === 0x00 && this.tagNumber === 0x00;
}; };
} }
export class ASN1 { export class ASN1 {
stream: Stream; stream: Stream;
header: number; header: number;
length: number; length: number;
@ -481,9 +480,9 @@ namespace asn1 {
} }
} }
} }
} }
function decode0(stream: Stream) { function decode0(stream: Stream) {
const streamStart = new Stream(stream, 0); /* copy */ const streamStart = new Stream(stream, 0); /* copy */
const tag = new ASN1Tag(stream); const tag = new ASN1Tag(stream);
let len = ASN1.decodeLength(stream); let len = ASN1.decodeLength(stream);
@ -539,9 +538,8 @@ namespace asn1 {
stream.position = start + Math.abs(len); stream.position = start + Math.abs(len);
} }
return new ASN1(streamStart, length_header, len, tag, children); return new ASN1(streamStart, length_header, len, tag, children);
} }
export function decode(stream: string | ArrayBuffer) { export function decode(stream: string | ArrayBuffer) {
return decode0(new Stream(stream, 0)); return decode0(new Stream(stream, 0));
}
} }

View file

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

View file

@ -1,5 +1,4 @@
namespace hex { export function encode(buffer) {
export function encode(buffer) {
let hexCodes = []; let hexCodes = [];
let view = new DataView(buffer); let view = new DataView(buffer);
for (let i = 0; i < view.byteLength % 4; i ++) { for (let i = 0; i < view.byteLength % 4; i ++) {
@ -16,5 +15,4 @@ namespace hex {
} }
return hexCodes.join(""); return hexCodes.join("");
}
} }

View file

@ -6,12 +6,11 @@ declare class _sha1 {
/* /*
interface Window { interface Window {
TextEncoder: any; TextEncoder: any;
} }
*/ */
namespace sha { /*
/*
* [js-sha1]{@link https://github.com/emn178/js-sha1} * [js-sha1]{@link https://github.com/emn178/js-sha1}
* *
* @version 0.6.0 * @version 0.6.0
@ -19,17 +18,11 @@ namespace sha {
* @copyright Chen, Yi-Cyuan 2014-2017 * @copyright Chen, Yi-Cyuan 2014-2017
* @license MIT * @license MIT
*/ */
/*jslint bitwise: true */ /*jslint bitwise: true */
(function() { (function() {
'use strict'; 'use strict';
let root: any = typeof window === 'object' ? window : {}; 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 HEX_CHARS = '0123456789abcdef'.split('');
let EXTRA = [-2147483648, 8388608, 32768, 128]; let EXTRA = [-2147483648, 8388608, 32768, 128];
let SHIFT = [24, 16, 8, 0]; let SHIFT = [24, 16, 8, 0];
@ -45,9 +38,6 @@ namespace sha {
let createMethod = function () { let createMethod = function () {
let method: any = createOutputMethod('hex'); let method: any = createOutputMethod('hex');
if (NODE_JS) {
method = nodeWrap(method);
}
method.create = function () { method.create = function () {
return new (Sha1 as any)(); return new (Sha1 as any)();
}; };
@ -61,22 +51,6 @@ namespace sha {
return method; 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) { function Sha1(sharedMemory) {
if (sharedMemory) { if (sharedMemory) {
blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] =
@ -359,8 +333,8 @@ namespace sha {
Sha1.prototype.arrayBuffer = function () { Sha1.prototype.arrayBuffer = function () {
this.finalize(); this.finalize();
var buffer = new ArrayBuffer(20); const buffer = new ArrayBuffer(20);
var dataView = new DataView(buffer); const dataView = new DataView(buffer);
dataView.setUint32(0, this.h0); dataView.setUint32(0, this.h0);
dataView.setUint32(4, this.h1); dataView.setUint32(4, this.h1);
dataView.setUint32(8, this.h2); dataView.setUint32(8, this.h2);
@ -369,21 +343,10 @@ namespace sha {
return buffer; return buffer;
}; };
var exports = createMethod(); createMethod();
})();
if (COMMON_JS) { export function encode_text(buffer: string) : ArrayBuffer {
module.exports = exports;
} else {
root._sha1 = exports;
if (AMD) {
define(function () {
return exports;
});
}
}
})();
export function encode_text(buffer: string) : ArrayBuffer {
if ((window as any).TextEncoder) { if ((window as any).TextEncoder) {
return new TextEncoder().encode(buffer).buffer; return new TextEncoder().encode(buffer).buffer;
} }
@ -393,8 +356,9 @@ namespace sha {
result[i] = utf8.charCodeAt(i); result[i] = utf8.charCodeAt(i);
} }
return result.buffer; return result.buffer;
} }
export function sha1(message: string | ArrayBuffer) : PromiseLike<ArrayBuffer> {
export function sha1(message: string | ArrayBuffer) : PromiseLike<ArrayBuffer> {
if(!(typeof(message) === "string" || message instanceof ArrayBuffer)) throw "Invalid type!"; if(!(typeof(message) === "string" || message instanceof ArrayBuffer)) throw "Invalid type!";
let buffer = message instanceof ArrayBuffer ? message : encode_text(message as string); let buffer = message instanceof ArrayBuffer ? message : encode_text(message as string);
@ -405,6 +369,4 @@ namespace sha {
}); });
else else
return crypto.subtle.digest("SHA-1", buffer); 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,10 +1,9 @@
namespace dns { export interface AddressTarget {
export interface AddressTarget {
target_ip: string; target_ip: string;
target_port?: number; target_port?: number;
} }
export interface ResolveOptions { export interface ResolveOptions {
timeout?: number; timeout?: number;
allow_cache?: boolean; allow_cache?: boolean;
max_depth?: number; max_depth?: number;
@ -14,11 +13,10 @@ namespace dns {
allow_any?: boolean; allow_any?: boolean;
allow_a?: boolean; allow_a?: boolean;
allow_aaaa?: boolean; allow_aaaa?: boolean;
} }
export const default_options: ResolveOptions = { export const default_options: ResolveOptions = {
timeout: 5000, timeout: 5000,
allow_cache: true, allow_cache: true,
max_depth: 5 max_depth: 5
}; };
}

View file

@ -1,20 +1,24 @@
namespace events { //TODO: Combine EventConvert and Event?
export interface EventConvert<All> { 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]; as<T extends keyof All>() : All[T];
} }
export interface Event<T> { export interface Event<T> {
readonly type: T; readonly type: T;
} }
export class SingletonEvent implements Event<"singletone-instance"> { export class SingletonEvent implements Event<"singletone-instance"> {
static readonly instance = new SingletonEvent(); static readonly instance = new SingletonEvent();
readonly type = "singletone-instance"; readonly type = "singletone-instance";
private constructor() { } private constructor() { }
} }
export class Registry<Events> { export class Registry<Events> {
private readonly registry_uuid; private readonly registry_uuid;
private handler: {[key: string]: ((event) => void)[]} = {}; private handler: {[key: string]: ((event) => void)[]} = {};
@ -117,13 +121,9 @@ namespace events {
destory() { destory() {
this.handler = {}; this.handler = {};
} }
} }
namespace global { export namespace channel_tree {
}
export namespace channel_tree {
export interface client { export interface client {
"enter_view": {}, "enter_view": {},
"left_view": {}, "left_view": {},
@ -146,9 +146,9 @@ namespace events {
"playlist_song_reorder": { song_id: number, previous_song_id: number }, "playlist_song_reorder": { song_id: number, previous_song_id: number },
"playlist_song_loaded": { song_id: number, success: boolean, error_msg?: string, metadata?: string }, "playlist_song_loaded": { song_id: number, success: boolean, error_msg?: string, metadata?: string },
} }
} }
export namespace sidebar { export namespace sidebar {
export interface music { export interface music {
"open": {}, /* triggers when frame should be shown */ "open": {}, /* triggers when frame should be shown */
"close": {}, /* triggers when frame will be closed */ "close": {}, /* triggers when frame will be closed */
@ -193,9 +193,9 @@ namespace events {
"playlist_song_reorder": channel_tree.client["playlist_song_reorder"], "playlist_song_reorder": channel_tree.client["playlist_song_reorder"],
"playlist_song_loaded": channel_tree.client["playlist_song_loaded"] & { html_entry?: JQuery }, "playlist_song_loaded": channel_tree.client["playlist_song_loaded"] & { html_entry?: JQuery },
} }
} }
export namespace modal { export namespace modal {
export type BotStatusType = "name" | "description" | "volume" | "country_code" | "channel_commander" | "priority_speaker"; export type BotStatusType = "name" | "description" | "volume" | "country_code" | "channel_commander" | "priority_speaker";
export type PlaylistStatusType = "replay_mode" | "finished" | "delete_played" | "max_size" | "notify_song_change"; export type PlaylistStatusType = "replay_mode" | "finished" | "delete_played" | "max_size" | "notify_song_change";
export interface music_manage { export interface music_manage {
@ -549,7 +549,7 @@ namespace events {
vad_type: string, vad_type: string,
vad_ppt: { vad_ppt: {
key: ppt.KeyDescriptor, key: any, /* ppt.KeyDescriptor */
release_delay: number, release_delay: number,
release_delay_active: boolean release_delay_active: boolean
}, },
@ -593,12 +593,13 @@ namespace events {
"deinitialize": {} "deinitialize": {}
} }
} }
}
} }
/*
//Some test code
const eclient = new events.Registry<events.channel_tree.client>(); const eclient = new events.Registry<events.channel_tree.client>();
const emusic = new events.Registry<events.sidebar.music>(); const emusic = new events.Registry<events.sidebar.music>();
eclient.connect("playlist_song_loaded", emusic); eclient.connect("playlist_song_loaded", emusic);
eclient.connect("playlist_song_loaded", emusic); eclient.connect("playlist_song_loaded", emusic);
*/

View file

@ -1,15 +1,13 @@
interface CountryInfo {
namespace i18n {
interface CountryInfo {
name: string; name: string;
alpha_2: string; alpha_2: string;
alpha_3: string; alpha_3: string;
un_code: number; un_code: number;
} }
const country_infos: CountryInfo[] = []; const country_infos: CountryInfo[] = [];
const alpha_2_map: {[name: string]:CountryInfo} = {}; const alpha_2_map: {[name: string]:CountryInfo} = {};
const fill_country_infos = (array: CountryInfo[]) => { const fill_country_infos = (array: CountryInfo[]) => {
array.push({ array.push({
name: "Afghanistan", name: "Afghanistan",
alpha_2: "AF", alpha_2: "AF",
@ -1492,13 +1490,12 @@ namespace i18n {
alpha_3: "ZWE", alpha_3: "ZWE",
un_code: 716 un_code: 716
}); });
}; };
export function country_name(alpha_code: string, fallback?: string) { export function country_name(alpha_code: string, fallback?: string) {
return (alpha_2_map[alpha_code.toUpperCase()] || {name: fallback || tr("unknown country")}).name; return (alpha_2_map[alpha_code.toUpperCase()] || {name: fallback || tr("unknown country")}).name;
}
fill_country_infos(country_infos);
for(const country of country_infos)
alpha_2_map[country.alpha_2] = country;
} }
fill_country_infos(country_infos);
for(const country of country_infos)
alpha_2_map[country.alpha_2] = country;

View file

@ -1,59 +1,57 @@
function guid() { import * as log from "tc-shared/log";
function s4() { import {LogCategory} from "tc-shared/log";
return Math.floor((1 + Math.random()) * 0x10000) import {guid} from "tc-shared/crypto/uid";
.toString(16) import {StaticSettings} from "tc-shared/settings";
.substring(1); import {createErrorModal} from "tc-shared/ui/elements/Modal";
} import * as loader from "tc-loader";
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); import {formatMessage} from "tc-shared/ui/frames/chat";
}
namespace i18n { export interface TranslationKey {
export interface TranslationKey {
message: string; message: string;
line?: number; line?: number;
character?: number; character?: number;
filename?: string; filename?: string;
} }
export interface Translation { export interface Translation {
key: TranslationKey; key: TranslationKey;
translated: string; translated: string;
flags?: string[]; flags?: string[];
} }
export interface Contributor { export interface Contributor {
name: string; name: string;
email: string; email: string;
} }
export interface TranslationFile { export interface TranslationFile {
path: string; path: string;
full_url: string; full_url: string;
translations: Translation[]; translations: Translation[];
} }
export interface RepositoryTranslation { export interface RepositoryTranslation {
key: string; key: string;
path: string; path: string;
country_code: string; country_code: string;
name: string; name: string;
contributors: Contributor[]; contributors: Contributor[];
} }
export interface TranslationRepository { export interface TranslationRepository {
unique_id: string; unique_id: string;
url: string; url: string;
name?: string; name?: string;
contact?: string; contact?: string;
translations?: RepositoryTranslation[]; translations?: RepositoryTranslation[];
load_timestamp?: number; load_timestamp?: number;
} }
let translations: Translation[] = []; let translations: Translation[] = [];
let fast_translate: { [key:string]:string; } = {}; let fast_translate: { [key:string]:string; } = {};
export function tr(message: string, key?: string) { export function tr(message: string, key?: string) {
const sloppy = fast_translate[message]; const sloppy = fast_translate[message];
if(sloppy) return sloppy; if(sloppy) return sloppy;
@ -69,14 +67,14 @@ namespace i18n {
fast_translate[message] = translated; fast_translate[message] = translated;
return translated; return translated;
} }
export function tra(message: string, ...args: any[]) { export function tra(message: string, ...args: any[]) {
message = tr(message); message = tr(message);
return MessageHelper.formatMessage(message, ...args); return formatMessage(message, ...args);
} }
async function load_translation_file(url: string, path: string) : Promise<TranslationFile> { async function load_translation_file(url: string, path: string) : Promise<TranslationFile> {
return new Promise<TranslationFile>((resolve, reject) => { return new Promise<TranslationFile>((resolve, reject) => {
$.ajax({ $.ajax({
url: url, url: url,
@ -104,9 +102,9 @@ namespace i18n {
} }
}) })
}); });
} }
export function load_file(url: string, path: string) : Promise<void> { export function load_file(url: string, path: string) : Promise<void> {
return load_translation_file(url, path).then(async result => { return load_translation_file(url, path).then(async result => {
/* TODO: Improve this test?!*/ /* TODO: Improve this test?!*/
try { try {
@ -121,9 +119,9 @@ namespace i18n {
log.warn(LogCategory.I18N, tr("Failed to load translation file from \"%s\". Error: %o"), url, error); log.warn(LogCategory.I18N, tr("Failed to load translation file from \"%s\". Error: %o"), url, error);
return Promise.reject(error); return Promise.reject(error);
}); });
} }
async function load_repository0(repo: TranslationRepository, reload: boolean) { async function load_repository0(repo: TranslationRepository, reload: boolean) {
if(!repo.load_timestamp || repo.load_timestamp < 1000 || reload) { if(!repo.load_timestamp || repo.load_timestamp < 1000 || reload) {
const info_json = await new Promise((resolve, reject) => { const info_json = await new Promise((resolve, reject) => {
$.ajax({ $.ajax({
@ -153,16 +151,16 @@ namespace i18n {
repo.translations = repo.translations || []; repo.translations = repo.translations || [];
repo.load_timestamp = Date.now(); repo.load_timestamp = Date.now();
} }
export async function load_repository(url: string) : Promise<TranslationRepository> { export async function load_repository(url: string) : Promise<TranslationRepository> {
const result = {} as TranslationRepository; const result = {} as TranslationRepository;
result.url = url; result.url = url;
await load_repository0(result, false); await load_repository0(result, false);
return result; return result;
} }
export namespace config { export namespace config {
export interface TranslationConfig { export interface TranslationConfig {
current_repository_url?: string; current_repository_url?: string;
current_language?: string; current_language?: string;
@ -232,9 +230,9 @@ namespace i18n {
export function save_translation_config() { export function save_translation_config() {
localStorage.setItem(translation_config_key, JSON.stringify(_cached_translation_config)); localStorage.setItem(translation_config_key, JSON.stringify(_cached_translation_config));
} }
} }
export function register_repository(repository: TranslationRepository) { export function register_repository(repository: TranslationRepository) {
if(!repository) return; if(!repository) return;
for(const repo of config.repository_config().repositories) for(const repo of config.repository_config().repositories)
@ -242,13 +240,13 @@ namespace i18n {
config.repository_config().repositories.push(repository); config.repository_config().repositories.push(repository);
config.save_repository_config(); config.save_repository_config();
} }
export function registered_repositories() : TranslationRepository[] { export function registered_repositories() : TranslationRepository[] {
return config.repository_config().repositories.map(e => e.repository || {url: e.url, load_timestamp: 0} as TranslationRepository); return config.repository_config().repositories.map(e => e.repository || {url: e.url, load_timestamp: 0} as TranslationRepository);
} }
export function delete_repository(repository: TranslationRepository) { export function delete_repository(repository: TranslationRepository) {
if(!repository) return; if(!repository) return;
for(const repo of [...config.repository_config().repositories]) for(const repo of [...config.repository_config().repositories])
@ -256,9 +254,9 @@ namespace i18n {
config.repository_config().repositories.remove(repo); config.repository_config().repositories.remove(repo);
} }
config.save_repository_config(); config.save_repository_config();
} }
export async function iterate_repositories(callback_entry: (repository: TranslationRepository) => any) { export async function iterate_repositories(callback_entry: (repository: TranslationRepository) => any) {
const promises = []; const promises = [];
for(const repository of registered_repositories()) { for(const repository of registered_repositories()) {
@ -268,9 +266,9 @@ namespace i18n {
} }
await Promise.all(promises); await Promise.all(promises);
} }
export function select_translation(repository: TranslationRepository, entry: RepositoryTranslation) { export function select_translation(repository: TranslationRepository, entry: RepositoryTranslation) {
const cfg = config.translation_config(); const cfg = config.translation_config();
if(entry && repository) { if(entry && repository) {
@ -286,10 +284,10 @@ namespace i18n {
} }
config.save_translation_config(); 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() { export async function initialize() {
const rcfg = config.repository_config(); /* initialize */ const rcfg = config.repository_config(); /* initialize */
const cfg = config.translation_config(); const cfg = config.translation_config();
@ -313,12 +311,7 @@ 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/de_DE.translation");
// await load_file("http://localhost/home/TeaSpeak/TeaSpeak/Web-Client/web/environment/development/i18n/test.json"); // await load_file("http://localhost/home/TeaSpeak/TeaSpeak/Web-Client/web/environment/development/i18n/test.json");
}
} }
// @ts-ignore window.tr = tr;
const tr: typeof i18n.tr = i18n.tr; window.tra = tra;
const tra: typeof i18n.tra = i18n.tra;
(window as any).tr = i18n.tr;
(window as any).tra = i18n.tra;

View file

@ -1,6 +1,8 @@
//Used by CertAccept popup //Used by CertAccept popup
import {settings} from "tc-shared/settings";
import * as loader from "tc-loader";
enum LogCategory { export enum LogCategory {
CHANNEL, CHANNEL,
CHANNEL_PROPERTIES, /* separating channel and channel properties because on channel init logging is a big bottleneck */ CHANNEL_PROPERTIES, /* separating channel and channel properties because on channel init logging is a big bottleneck */
CLIENT, CLIENT,
@ -19,16 +21,15 @@ enum LogCategory {
DNS DNS
} }
namespace log { export enum LogType {
export enum LogType {
TRACE, TRACE,
DEBUG, DEBUG,
INFO, INFO,
WARNING, WARNING,
ERROR ERROR
} }
let category_mapping = new Map<number, string>([ let category_mapping = new Map<number, string>([
[LogCategory.CHANNEL, "Channel "], [LogCategory.CHANNEL, "Channel "],
[LogCategory.CHANNEL_PROPERTIES, "Channel "], [LogCategory.CHANNEL_PROPERTIES, "Channel "],
[LogCategory.CLIENT, "Client "], [LogCategory.CLIENT, "Client "],
@ -45,9 +46,9 @@ namespace log {
[LogCategory.IPC, "IPC "], [LogCategory.IPC, "IPC "],
[LogCategory.STATISTICS, "Statistics "], [LogCategory.STATISTICS, "Statistics "],
[LogCategory.DNS, "DNS "] [LogCategory.DNS, "DNS "]
]); ]);
export let enabled_mapping = new Map<number, boolean>([ export let enabled_mapping = new Map<number, boolean>([
[LogCategory.CHANNEL, true], [LogCategory.CHANNEL, true],
[LogCategory.CHANNEL_PROPERTIES, false], [LogCategory.CHANNEL_PROPERTIES, false],
[LogCategory.CLIENT, true], [LogCategory.CLIENT, true],
@ -64,27 +65,27 @@ namespace log {
[LogCategory.IPC, true], [LogCategory.IPC, true],
[LogCategory.STATISTICS, true], [LogCategory.STATISTICS, true],
[LogCategory.DNS, true] [LogCategory.DNS, true]
]); ]);
//Values will be overridden by initialize() //Values will be overridden by initialize()
export let level_mapping = new Map<LogType, boolean>([ export let level_mapping = new Map<LogType, boolean>([
[LogType.TRACE, true], [LogType.TRACE, true],
[LogType.DEBUG, true], [LogType.DEBUG, true],
[LogType.INFO, true], [LogType.INFO, true],
[LogType.WARNING, true], [LogType.WARNING, true],
[LogType.ERROR, true] [LogType.ERROR, true]
]); ]);
enum GroupMode { enum GroupMode {
NATIVE, NATIVE,
PREFIX PREFIX
} }
const group_mode: GroupMode = GroupMode.PREFIX; const group_mode: GroupMode = GroupMode.PREFIX;
//Category Example: <url>?log.i18n.enabled=0 //Category Example: <url>?log.i18n.enabled=0
//Level Example A: <url>?log.level.trace.enabled=0 //Level Example A: <url>?log.level.trace.enabled=0
//Level Example B: <url>?log.level=0 //Level Example B: <url>?log.level=0
export function initialize(default_level: LogType) { export function initialize(default_level: LogType) {
for(const category of Object.keys(LogCategory).map(e => parseInt(e))) { for(const category of Object.keys(LogCategory).map(e => parseInt(e))) {
if(isNaN(category)) continue; if(isNaN(category)) continue;
const category_name = LogCategory[category].toLowerCase(); const category_name = LogCategory[category].toLowerCase();
@ -99,9 +100,9 @@ namespace log {
const level_name = LogType[level].toLowerCase(); const level_name = LogType[level].toLowerCase();
level_mapping.set(level, settings.static_global<boolean>("log." + level_name + ".enabled", level >= base_level)); level_mapping.set(level, settings.static_global<boolean>("log." + level_name + ".enabled", level >= base_level));
} }
} }
function logDirect(type: LogType, message: string, ...optionalParams: any[]) { function logDirect(type: LogType, message: string, ...optionalParams: any[]) {
if(!level_mapping.get(type)) if(!level_mapping.get(type))
return; return;
@ -120,56 +121,56 @@ namespace log {
console.error(message, ...optionalParams); console.error(message, ...optionalParams);
break; break;
} }
} }
export function log(type: LogType, category: LogCategory, message: string, ...optionalParams: any[]) { export function log(type: LogType, category: LogCategory, message: string, ...optionalParams: any[]) {
if(!enabled_mapping.get(category)) return; if(!enabled_mapping.get(category)) return;
optionalParams.unshift(category_mapping.get(category)); optionalParams.unshift(category_mapping.get(category));
message = "[%s] " + message; message = "[%s] " + message;
logDirect(type, message, ...optionalParams); logDirect(type, message, ...optionalParams);
} }
export function trace(category: LogCategory, message: string, ...optionalParams: any[]) { export function trace(category: LogCategory, message: string, ...optionalParams: any[]) {
log(LogType.TRACE, category, message, ...optionalParams); log(LogType.TRACE, category, message, ...optionalParams);
} }
export function debug(category: LogCategory, message: string, ...optionalParams: any[]) { export function debug(category: LogCategory, message: string, ...optionalParams: any[]) {
log(LogType.DEBUG, category, message, ...optionalParams); log(LogType.DEBUG, category, message, ...optionalParams);
} }
export function info(category: LogCategory, message: string, ...optionalParams: any[]) { export function info(category: LogCategory, message: string, ...optionalParams: any[]) {
log(LogType.INFO, category, message, ...optionalParams); log(LogType.INFO, category, message, ...optionalParams);
} }
export function warn(category: LogCategory, message: string, ...optionalParams: any[]) { export function warn(category: LogCategory, message: string, ...optionalParams: any[]) {
log(LogType.WARNING, category, message, ...optionalParams); log(LogType.WARNING, category, message, ...optionalParams);
} }
export function error(category: LogCategory, message: string, ...optionalParams: any[]) { export function error(category: LogCategory, message: string, ...optionalParams: any[]) {
log(LogType.ERROR, category, message, ...optionalParams); log(LogType.ERROR, category, message, ...optionalParams);
} }
export function group(level: LogType, category: LogCategory, name: string, ...optionalParams: any[]) : Group { export function group(level: LogType, category: LogCategory, name: string, ...optionalParams: any[]) : Group {
name = "[%s] " + name; name = "[%s] " + name;
optionalParams.unshift(category_mapping.get(category)); optionalParams.unshift(category_mapping.get(category));
return new Group(group_mode, level, category, name, optionalParams); 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) { if(group_mode == GroupMode.NATIVE) {
console.groupCollapsed(title); console.groupCollapsed(title);
console.table(arguments); console.table(args);
console.groupEnd(); console.groupEnd();
} else { } else {
if(!enabled_mapping.get(category) || !level_mapping.get(level)) if(!enabled_mapping.get(category) || !level_mapping.get(level))
return; return;
logDirect(level, tr("Snipped table \"%s\""), title); logDirect(level, tr("Snipped table \"%s\""), title);
} }
} }
export class Group { export class Group {
readonly mode: GroupMode; readonly mode: GroupMode;
readonly level: LogType; readonly level: LogType;
readonly category: LogCategory; readonly category: LogCategory;
@ -245,7 +246,13 @@ namespace log {
set prefix(prefix: string) { set prefix(prefix: string) {
this._log_prefix = prefix; 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,31 +1,41 @@
/// <reference path="ui/frames/chat.ts" /> import * as loader from "tc-loader";
/// <reference path="ui/modal/ModalConnect.ts" /> import {settings, Settings} from "tc-shared/settings";
/// <reference path="ui/modal/ModalCreateChannel.ts" /> import * as profiles from "tc-shared/profiles/ConnectionProfile";
/// <reference path="ui/modal/ModalBanClient.ts" /> import {LogCategory} from "tc-shared/log";
/// <reference path="ui/modal/ModalYesNo.ts" /> import * as log from "tc-shared/log";
/// <reference path="ui/modal/ModalBanList.ts" /> import * as bipc from "./BrowserIPC";
/// <reference path="settings.ts" /> import * as sound from "./sound/Sounds";
/// <reference path="log.ts" /> import * as i18n from "./i18n/localize";
/// <reference path="PPTListener.ts" /> 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 js_render = window.jsrender || $;
const native_client = window.require !== undefined; const native_client = window.require !== undefined;
function getUserMediaFunctionPromise() : (constraints: MediaStreamConstraints) => Promise<MediaStream> { declare global {
if('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices) interface Window {
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));
}
interface Window {
open_connected_question: () => Promise<boolean>; open_connected_question: () => Promise<boolean>;
}
} }
function setup_close() { function setup_close() {
@ -50,7 +60,7 @@ function setup_close() {
})); }));
const exit = () => { const exit = () => {
const {remote} = require('electron'); const {remote} = window.require('electron');
remote.getCurrentWindow().close(); remote.getCurrentWindow().close();
}; };
@ -133,7 +143,7 @@ async function initialize_app() {
try { //Initialize main template try { //Initialize main template
const main = $("#tmpl_main").renderTag({ const main = $("#tmpl_main").renderTag({
multi_session: !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION), multi_session: !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION),
app_version: app.ui_version() app_version: loader.version().ui
}).dividerfy(); }).dividerfy();
$("body").append(main); $("body").append(main);
@ -143,21 +153,21 @@ async function initialize_app() {
return; 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!")); console.warn(tr("Failed to initialize audio controller!"));
audio.player.on_ready(() => { aplayer.on_ready(() => {
if(audio.player.set_master_volume) if(aplayer.set_master_volume)
audio.player.on_ready(() => audio.player.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER) / 100)); aplayer.on_ready(() => aplayer.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER) / 100));
else else
log.warn(LogCategory.GENERAL, tr("Client does not support audio.player.set_master_volume()... May client is too old?")); log.warn(LogCategory.GENERAL, tr("Client does not support aplayer.set_master_volume()... May client is too old?"));
if(audio.recorder.device_refresh_available()) if(arecorder.device_refresh_available())
audio.recorder.refresh_devices(); arecorder.refresh_devices();
}); });
default_recorder = new RecorderProfile("default"); set_default_recorder(new RecorderProfile("default"));
default_recorder.initialize().catch(error => { default_recorder.initialize().catch(error => {
log.error(LogCategory.AUDIO, tr("Failed to initialize default recorder: %o"), error); log.error(LogCategory.AUDIO, tr("Failed to initialize default recorder: %o"), error);
}); });
@ -180,78 +190,6 @@ async function initialize_app() {
setup_close(); 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 { class TestProxy extends bipc.MethodProxy {
constructor(params: bipc.MethodProxyConnectParameters) { constructor(params: bipc.MethodProxyConnectParameters) {
@ -313,7 +251,7 @@ function handle_connect_request(properties: bipc.connect.ConnectRequestData, con
}); });
server_connections.set_active_connection_handler(connection); server_connections.set_active_connection_handler(connection);
} else { } else {
Modals.spawnConnectModal({},{ spawnConnectModal({},{
url: properties.address, url: properties.address,
enforce: true enforce: true
}, { }, {
@ -356,14 +294,14 @@ function main() {
top_menu.initialize(); top_menu.initialize();
server_connections = new ServerConnectionManager($("#connection-handlers")); cmanager.initialize(new ServerConnectionManager($("#connection-handlers")));
control_bar.initialise(); /* before connection handler to allow property apply */ control_bar.control_bar.initialise(); /* before connection handler to allow property apply */
const initial_handler = server_connections.spawn_server_connection_handler(); const initial_handler = server_connections.spawn_server_connection_handler();
initial_handler.acquire_recorder(default_recorder, false); 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 **/ /** Setup the XF forum identity **/
profiles.identities.update_forum(); fidentity.update_forum();
let _resize_timeout: NodeJS.Timer; let _resize_timeout: NodeJS.Timer;
$(window).on('resize', event => { $(window).on('resize', event => {
@ -481,7 +419,7 @@ function main() {
/* for testing */ /* for testing */
if(settings.static_global(Settings.KEY_USER_IS_NEW)) { 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)); modal.close_listener.push(() => settings.changeGlobal(Settings.KEY_USER_IS_NEW, false));
} }
} }
@ -492,12 +430,12 @@ const task_teaweb_starter: loader.Task = {
try { try {
await initialize_app(); await initialize_app();
main(); main();
if(!audio.player.initialized()) { if(!aplayer.initialized()) {
log.info(LogCategory.VOICE, tr("Initialize audio controller later!")); log.info(LogCategory.VOICE, tr("Initialize audio controller later!"));
if(!audio.player.initializeFromGesture) { if(!aplayer.initializeFromGesture) {
console.error(tr("Missing audio.player.initializeFromGesture")); console.error(tr("Missing aplayer.initializeFromGesture"));
} else } else
$(document).one('click', event => audio.player.initializeFromGesture()); $(document).one('click', event => aplayer.initializeFromGesture());
} }
} catch (ex) { } catch (ex) {
console.error(ex.stack); console.error(ex.stack);
@ -543,7 +481,7 @@ const task_connect_handler: loader.Task = {
"You could now close this page."; "You could now close this page.";
createInfoModal( createInfoModal(
tr("Connecting successfully within other instance"), tr("Connecting successfully within other instance"),
MessageHelper.formatMessage(tr(message), connect_data.address), formatMessage(tr(message), connect_data.address),
{ {
closeable: false, closeable: false,
footer: undefined footer: undefined
@ -610,7 +548,7 @@ const task_certificate_callback: loader.Task = {
"This page will close in {0} seconds."; "This page will close in {0} seconds.";
createInfoModal( createInfoModal(
tr("Certificate acccepted successfully"), tr("Certificate acccepted successfully"),
MessageHelper.formatMessage(tr(message), seconds_tag), formatMessage(tr(message), seconds_tag),
{ {
closeable: false, closeable: false,
footer: undefined footer: undefined
@ -650,7 +588,7 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
try { try {
await initialize(); await initialize();
if(app.is_web()) { if(loader.version().type == "web") {
loader.register_task(loader.Stage.LOADED, task_certificate_callback); loader.register_task(loader.Stage.LOADED, task_certificate_callback);
} else { } else {
loader.register_task(loader.Stage.LOADED, task_teaweb_starter); loader.register_task(loader.Stage.LOADED, task_teaweb_starter);
@ -667,3 +605,15 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
priority: 1000 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, QUERY,
TEMPLATE, TEMPLATE,
NORMAL NORMAL
} }
enum GroupTarget { export enum GroupTarget {
SERVER, SERVER,
CHANNEL CHANNEL
} }
class GroupProperties { export class GroupProperties {
iconid: number = 0; iconid: number = 0;
sortid: number = 0; sortid: number = 0;
@ -19,12 +26,12 @@ class GroupProperties {
namemode: number = 0; namemode: number = 0;
} }
class GroupPermissionRequest { export class GroupPermissionRequest {
group_id: number; group_id: number;
promise: LaterPromise<PermissionValue[]>; promise: LaterPromise<PermissionValue[]>;
} }
class Group { export class Group {
properties: GroupProperties = new GroupProperties(); properties: GroupProperties = new GroupProperties();
readonly handle: GroupManager; readonly handle: GroupManager;
@ -63,7 +70,7 @@ class Group {
} }
} }
class GroupManager extends connection.AbstractCommandHandler { export class GroupManager extends AbstractCommandHandler {
readonly handle: ConnectionHandler; readonly handle: ConnectionHandler;
serverGroups: Group[] = []; serverGroups: Group[] = [];
@ -83,7 +90,7 @@ class GroupManager extends connection.AbstractCommandHandler {
this.channelGroups = undefined; this.channelGroups = undefined;
} }
handle_command(command: connection.ServerCommand): boolean { handle_command(command: ServerCommand): boolean {
switch (command.command) { switch (command.command) {
case "notifyservergrouplist": case "notifyservergrouplist":
case "notifychannelgrouplist": 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"]); 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 == "sgid") continue;
if(key == "cgid") continue; if(key == "cgid") continue;
if(key == "type") continue; if(key == "type") continue;

View file

@ -1,358 +1,13 @@
/// <reference path="../ConnectionHandler.ts" /> import * as log from "tc-shared/log";
/// <reference path="../connection/ConnectionBase.ts" /> import {LogCategory, LogType} from "tc-shared/log";
/// <reference path="../i18n/localize.ts" /> 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 { export class PermissionInfo {
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 {
name: string; name: string;
id: number; id: number;
description: string; description: string;
@ -363,21 +18,21 @@ class PermissionInfo {
} }
} }
class PermissionGroup { export class PermissionGroup {
begin: number; begin: number;
end: number; end: number;
deep: number; deep: number;
name: string; name: string;
} }
class GroupedPermissions { export class GroupedPermissions {
group: PermissionGroup; group: PermissionGroup;
permissions: PermissionInfo[]; permissions: PermissionInfo[];
children: GroupedPermissions[]; children: GroupedPermissions[];
parent: GroupedPermissions; parent: GroupedPermissions;
} }
class PermissionValue { export class PermissionValue {
readonly type: PermissionInfo; readonly type: PermissionInfo;
value: number; value: number;
flag_skip: boolean; flag_skip: boolean;
@ -411,25 +66,24 @@ class PermissionValue {
} }
} }
class NeededPermissionValue extends PermissionValue { export class NeededPermissionValue extends PermissionValue {
constructor(type, value) { constructor(type, value) {
super(type, value); super(type, value);
} }
} }
namespace permissions { export type PermissionRequestKeys = {
export type PermissionRequestKeys = {
client_id?: number; client_id?: number;
channel_id?: number; channel_id?: number;
playlist_id?: number; playlist_id?: number;
} }
export type PermissionRequest = PermissionRequestKeys & { export type PermissionRequest = PermissionRequestKeys & {
timeout_id: any; timeout_id: any;
promise: LaterPromise<PermissionValue[]>; promise: LaterPromise<PermissionValue[]>;
}; };
export namespace find { export namespace find {
export type Entry = { export type Entry = {
type: "server" | "channel" | "client" | "client_channel" | "channel_group" | "server_group"; type: "server" | "channel" | "client" | "client_channel" | "channel_group" | "server_group";
value: number; value: number;
@ -470,16 +124,16 @@ namespace permissions {
group_id: number; group_id: number;
} }
}
} }
type RequestLists = export type RequestLists =
"requests_channel_permissions" | "requests_channel_permissions" |
"requests_client_permissions" | "requests_client_permissions" |
"requests_client_channel_permissions" | "requests_client_channel_permissions" |
"requests_playlist_permissions" | "requests_playlist_permissions" |
"requests_playlist_client_permissions"; "requests_playlist_client_permissions";
class PermissionManager extends connection.AbstractCommandHandler {
export class PermissionManager extends AbstractCommandHandler {
readonly handle: ConnectionHandler; readonly handle: ConnectionHandler;
permissionList: PermissionInfo[] = []; permissionList: PermissionInfo[] = [];
@ -488,11 +142,11 @@ class PermissionManager extends connection.AbstractCommandHandler {
needed_permission_change_listener: {[permission: string]:(() => any)[]} = {}; needed_permission_change_listener: {[permission: string]:(() => any)[]} = {};
requests_channel_permissions: permissions.PermissionRequest[] = []; requests_channel_permissions: PermissionRequest[] = [];
requests_client_permissions: permissions.PermissionRequest[] = []; requests_client_permissions: PermissionRequest[] = [];
requests_client_channel_permissions: permissions.PermissionRequest[] = []; requests_client_channel_permissions: PermissionRequest[] = [];
requests_playlist_permissions: permissions.PermissionRequest[] = []; requests_playlist_permissions: PermissionRequest[] = [];
requests_playlist_client_permissions: permissions.PermissionRequest[] = []; requests_playlist_client_permissions: PermissionRequest[] = [];
requests_permfind: { requests_permfind: {
timeout_id: number, timeout_id: number,
@ -603,7 +257,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
this._cacheNeededPermissions = undefined; this._cacheNeededPermissions = undefined;
} }
handle_command(command: connection.ServerCommand): boolean { handle_command(command: ServerCommand): boolean {
switch (command.command) { switch (command.command) {
case "notifyclientneededpermissions": case "notifyclientneededpermissions":
this.onNeededPermissions(command.arguments); this.onNeededPermissions(command.arguments);
@ -775,7 +429,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions)); }, "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 => { this.handle.serverConnection.send_command("channelpermlist", {"cid": request.channel_id}).catch(error => {
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
this.fullfill_permission_request("requests_channel_permissions", request, "success", []); this.fullfill_permission_request("requests_channel_permissions", request, "success", []);
@ -785,7 +439,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
} }
requestChannelPermissions(channelId: number) : Promise<PermissionValue[]> { requestChannelPermissions(channelId: number) : Promise<PermissionValue[]> {
const keys: permissions.PermissionRequestKeys = { const keys: PermissionRequestKeys = {
channel_id: channelId channel_id: channelId
}; };
return this.execute_permission_request("requests_channel_permissions", keys, this.execute_channel_permission_request.bind(this)); 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)); }, "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 => { this.handle.serverConnection.send_command("clientpermlist", {cldbid: request.client_id}).catch(error => {
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
this.fullfill_permission_request("requests_client_permissions", request, "success", []); this.fullfill_permission_request("requests_client_permissions", request, "success", []);
@ -809,7 +463,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
} }
requestClientPermissions(client_id: number) : Promise<PermissionValue[]> { requestClientPermissions(client_id: number) : Promise<PermissionValue[]> {
const keys: permissions.PermissionRequestKeys = { const keys: PermissionRequestKeys = {
client_id: client_id client_id: client_id
}; };
return this.execute_permission_request("requests_client_permissions", keys, this.execute_client_permission_request.bind(this)); 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)); }, "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}) this.handle.serverConnection.send_command("channelclientpermlist", {cldbid: request.client_id, cid: request.channel_id})
.catch(error => { .catch(error => {
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) 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[]> { requestClientChannelPermissions(client_id: number, channel_id: number) : Promise<PermissionValue[]> {
const keys: permissions.PermissionRequestKeys = { const keys: PermissionRequestKeys = {
client_id: client_id client_id: client_id
}; };
return this.execute_permission_request("requests_client_channel_permissions", keys, this.execute_client_channel_permission_request.bind(this)); 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)); }, "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}) this.handle.serverConnection.send_command("playlistpermlist", {playlist_id: request.playlist_id})
.catch(error => { .catch(error => {
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
@ -863,7 +517,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
} }
requestPlaylistPermissions(playlist_id: number) : Promise<PermissionValue[]> { requestPlaylistPermissions(playlist_id: number) : Promise<PermissionValue[]> {
const keys: permissions.PermissionRequestKeys = { const keys: PermissionRequestKeys = {
playlist_id: playlist_id playlist_id: playlist_id
}; };
return this.execute_permission_request("requests_playlist_permissions", keys, this.execute_playlist_permission_request.bind(this)); 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)); }, "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}) this.handle.serverConnection.send_command("playlistclientpermlist", {playlist_id: request.playlist_id, cldbid: request.client_id})
.catch(error => { .catch(error => {
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) 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[]> { requestPlaylistClientPermissions(playlist_id: number, client_database_id: number) : Promise<PermissionValue[]> {
const keys: permissions.PermissionRequestKeys = { const keys: PermissionRequestKeys = {
playlist_id: playlist_id, playlist_id: playlist_id,
client_id: client_database_id client_id: client_database_id
}; };
@ -907,8 +561,8 @@ class PermissionManager extends connection.AbstractCommandHandler {
}; };
private execute_permission_request(list: RequestLists, private execute_permission_request(list: RequestLists,
criteria: permissions.PermissionRequestKeys, criteria: PermissionRequestKeys,
execute: (criteria: permissions.PermissionRequestKeys) => any) : Promise<PermissionValue[]> { execute: (criteria: PermissionRequestKeys) => any) : Promise<PermissionValue[]> {
for(const request of this[list]) for(const request of this[list])
if(this.criteria_equal(request, criteria) && request.promise.time() + 1000 < Date.now()) if(this.criteria_equal(request, criteria) && request.promise.time() + 1000 < Date.now())
return request.promise; return request.promise;
@ -922,7 +576,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
return result.promise; 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]) { for(const request of this[list]) {
if(this.criteria_equal(request, criteria)) { if(this.criteria_equal(request, criteria)) {
this[list].remove(request); 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 = []; const permission_ids = [];
for(const permission of permissions) { for(const permission of permissions) {
const info = this.resolveInfo(permission); const info = this.resolveInfo(permission);
@ -942,11 +596,11 @@ class PermissionManager extends connection.AbstractCommandHandler {
} }
if(!permission_ids.length) return Promise.resolve([]); 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 = { const single_handler = {
command: "notifypermfind", command: "notifypermfind",
function: command => { function: command => {
const result: permissions.find.Entry[] = []; const result: find.Entry[] = [];
for(const entry of command.arguments) { for(const entry of command.arguments) {
const perm_id = parseInt(entry["p"]); const perm_id = parseInt(entry["p"]);
if(permission_ids.indexOf(perm_id) === -1) return; /* not our permfind result */ if(permission_ids.indexOf(perm_id) === -1) return; /* not our permfind result */
@ -960,32 +614,32 @@ class PermissionManager extends connection.AbstractCommandHandler {
data = { data = {
type: "server_group", type: "server_group",
group_id: parseInt(entry["id1"]), group_id: parseInt(entry["id1"]),
} as permissions.find.ServerGroup; } as find.ServerGroup;
break; break;
case 1: case 1:
data = { data = {
type: "client", type: "client",
client_id: parseInt(entry["id2"]), client_id: parseInt(entry["id2"]),
} as permissions.find.Client; } as find.Client;
break; break;
case 2: case 2:
data = { data = {
type: "channel", type: "channel",
channel_id: parseInt(entry["id2"]), channel_id: parseInt(entry["id2"]),
} as permissions.find.Channel; } as find.Channel;
break; break;
case 3: case 3:
data = { data = {
type: "channel_group", type: "channel_group",
group_id: parseInt(entry["id1"]), group_id: parseInt(entry["id1"]),
} as permissions.find.ChannelGroup; } as find.ChannelGroup;
break; break;
case 4: case 4:
data = { data = {
type: "client_channel", type: "client_channel",
client_id: parseInt(entry["id1"]), client_id: parseInt(entry["id1"]),
channel_id: parseInt(entry["id1"]), channel_id: parseInt(entry["id1"]),
} as permissions.find.ClientChannel; } as find.ClientChannel;
break; break;
default: default:
continue; 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,5 +1,13 @@
namespace profiles { import {decode_identity, IdentitifyType, Identity} from "tc-shared/profiles/Identity";
export class ConnectionProfile { 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; id: string;
profile_name: string; profile_name: string;
@ -7,7 +15,7 @@ namespace profiles {
default_password: string; default_password: string;
selected_identity_type: string = "unset"; selected_identity_type: string = "unset";
identities: { [key: string]: identities.Identity } = {}; identities: { [key: string]: Identity } = {};
constructor(id: string) { constructor(id: string) {
this.id = id; this.id = id;
@ -22,31 +30,31 @@ namespace profiles {
return name || "Another TeaSpeak user"; return name || "Another TeaSpeak user";
} }
selected_identity(current_type?: identities.IdentitifyType): identities.Identity { selected_identity(current_type?: IdentitifyType): Identity {
if (!current_type) if (!current_type)
current_type = this.selected_type(); current_type = this.selected_type();
if (current_type === undefined) if (current_type === undefined)
return undefined; return undefined;
if (current_type == identities.IdentitifyType.TEAFORO) { if (current_type == IdentitifyType.TEAFORO) {
return identities.static_forum_identity(); return TeaForumIdentity.identity();
} else if (current_type == identities.IdentitifyType.TEAMSPEAK || current_type == identities.IdentitifyType.NICKNAME) { } else if (current_type == IdentitifyType.TEAMSPEAK || current_type == IdentitifyType.NICKNAME) {
return this.identities[identities.IdentitifyType[current_type].toLowerCase()]; return this.identities[IdentitifyType[current_type].toLowerCase()];
} }
return undefined; return undefined;
} }
selected_type?(): identities.IdentitifyType { selected_type?(): IdentitifyType {
return this.selected_identity_type ? identities.IdentitifyType[this.selected_identity_type.toUpperCase()] : undefined; return this.selected_identity_type ? IdentitifyType[this.selected_identity_type.toUpperCase()] : undefined;
} }
set_identity(type: identities.IdentitifyType, identity: identities.Identity) { set_identity(type: IdentitifyType, identity: Identity) {
this.identities[identities.IdentitifyType[type].toLowerCase()] = 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(); const identity = this.selected_identity();
if (!identity) if (!identity)
return undefined; return undefined;
@ -72,13 +80,12 @@ namespace profiles {
valid(): boolean { valid(): boolean {
const identity = this.selected_identity(); const identity = this.selected_identity();
if (!identity || !identity.valid()) return false;
return true; return !!identity && identity.valid();
}
} }
}
async function decode_profile(data): Promise<ConnectionProfile | string> { async function decode_profile(data): Promise<ConnectionProfile | string> {
data = JSON.parse(data); data = JSON.parse(data);
if (data.version !== 1) if (data.version !== 1)
return "invalid version"; return "invalid version";
@ -90,12 +97,12 @@ namespace profiles {
result.selected_identity_type = (data.identity_type || "").toLowerCase(); result.selected_identity_type = (data.identity_type || "").toLowerCase();
if (data.identity_data) { if (data.identity_data) {
for (const key in data.identity_data) { for (const key of Object.keys(data.identity_data)) {
const type = identities.IdentitifyType[key.toUpperCase() as string]; const type = IdentitifyType[key.toUpperCase() as string];
const _data = data.identity_data[key]; const _data = data.identity_data[key];
if (type == undefined) continue; if (type == undefined) continue;
const identity = await identities.decode_identity(type, _data); const identity = await decode_identity(type, _data);
if (identity == undefined) continue; if (identity == undefined) continue;
result.identities[key.toLowerCase()] = identity; result.identities[key.toLowerCase()] = identity;
@ -103,16 +110,16 @@ namespace profiles {
} }
return result; return result;
} }
interface ProfilesData { interface ProfilesData {
version: number; version: number;
profiles: string[]; profiles: string[];
} }
let available_profiles: ConnectionProfile[] = []; let available_profiles: ConnectionProfile[] = [];
export async function load() { export async function load() {
available_profiles = []; available_profiles = [];
const profiles_json = localStorage.getItem("profiles"); const profiles_json = localStorage.getItem("profiles");
@ -122,7 +129,7 @@ namespace profiles {
} catch (error) { } catch (error) {
debugger; debugger;
console.error(tr("Invalid profile json! Resetting profiles :( (%o)"), profiles_json); 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}; return {version: 0};
} }
})(); })();
@ -153,14 +160,14 @@ namespace profiles {
/* generate default identity */ /* generate default identity */
try { try {
const identity = await identities.TeaSpeakIdentity.generate_new(); const identity = await TeaSpeakIdentity.generate_new();
let active = true; let active = true;
setTimeout(() => { setTimeout(() => {
active = false; active = false;
}, 1000); }, 1000);
await identity.improve_level(8, 1, () => active); await identity.improve_level(8, 1, () => active);
profile.set_identity(identities.IdentitifyType.TEAMSPEAK, identity); profile.set_identity(IdentitifyType.TEAMSPEAK, identity);
profile.selected_identity_type = identities.IdentitifyType[identities.IdentitifyType.TEAMSPEAK]; profile.selected_identity_type = IdentitifyType[IdentitifyType.TEAMSPEAK];
} catch (error) { } 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(); 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,25 +179,25 @@ namespace profiles {
profile.default_username = ""; profile.default_username = "";
profile.profile_name = "TeaSpeak Forum profile"; profile.profile_name = "TeaSpeak Forum profile";
profile.set_identity(identities.IdentitifyType.TEAFORO, identities.static_forum_identity()); profile.set_identity(IdentitifyType.TEAFORO, TeaForumIdentity.identity());
profile.selected_identity_type = identities.IdentitifyType[identities.IdentitifyType.TEAFORO]; profile.selected_identity_type = IdentitifyType[IdentitifyType.TEAFORO];
} }
save(); save();
} }
} }
export function create_new_profile(name: string, id?: string): ConnectionProfile { export function create_new_profile(name: string, id?: string): ConnectionProfile {
const profile = new ConnectionProfile(id || guid()); const profile = new ConnectionProfile(id || guid());
profile.profile_name = name; profile.profile_name = name;
profile.default_username = ""; profile.default_username = "";
available_profiles.push(profile); available_profiles.push(profile);
return profile; return profile;
} }
let _requires_save = false; let _requires_save = false;
export function save() { export function save() {
const profiles: string[] = []; const profiles: string[] = [];
for (const profile of available_profiles) for (const profile of available_profiles)
profiles.push(profile.encode()); profiles.push(profile.encode());
@ -200,52 +207,51 @@ namespace profiles {
profiles: profiles profiles: profiles
}); });
localStorage.setItem("profiles", data); localStorage.setItem("profiles", data);
} }
export function mark_need_save() { export function mark_need_save() {
_requires_save = true; _requires_save = true;
} }
export function requires_save(): boolean { export function requires_save(): boolean {
return _requires_save; return _requires_save;
} }
export function profiles(): ConnectionProfile[] { export function profiles(): ConnectionProfile[] {
return available_profiles; return available_profiles;
} }
export function find_profile(id: string): ConnectionProfile | undefined { export function find_profile(id: string): ConnectionProfile | undefined {
for (const profile of profiles()) for (const profile of profiles())
if (profile.id == id) if (profile.id == id)
return profile; return profile;
return undefined; return undefined;
} }
export function find_profile_by_name(name: string): ConnectionProfile | undefined { export function find_profile_by_name(name: string): ConnectionProfile | undefined {
name = name.toLowerCase(); name = name.toLowerCase();
for (const profile of profiles()) for (const profile of profiles())
if ((profile.profile_name || "").toLowerCase() == name) if ((profile.profile_name || "").toLowerCase() == name)
return profile; return profile;
return undefined; return undefined;
} }
export function default_profile(): ConnectionProfile { export function default_profile(): ConnectionProfile {
return find_profile("default"); return find_profile("default");
} }
export function set_default_profile(profile: ConnectionProfile) { export function set_default_profile(profile: ConnectionProfile) {
const old_default = default_profile(); const old_default = default_profile();
if (old_default && old_default != profile) { if (old_default && old_default != profile) {
old_default.id = guid(); old_default.id = guid();
} }
profile.id = "default"; profile.id = "default";
return old_default; return old_default;
} }
export function delete_profile(profile: ConnectionProfile) { export function delete_profile(profile: ConnectionProfile) {
available_profiles.remove(profile); available_profiles.remove(profile);
}
} }

View file

@ -1,11 +1,14 @@
namespace profiles.identities { import {AbstractServerConnection, ServerCommand} from "tc-shared/connection/ConnectionBase";
export enum IdentitifyType { import {HandshakeIdentityHandler} from "tc-shared/connection/HandshakeHandler";
import {AbstractCommandHandler} from "tc-shared/connection/AbstractCommandHandler";
export enum IdentitifyType {
TEAFORO, TEAFORO,
TEAMSPEAK, TEAMSPEAK,
NICKNAME NICKNAME
} }
export interface Identity { export interface Identity {
fallback_name(): string | undefined ; fallback_name(): string | undefined ;
uid() : string; uid() : string;
type() : IdentitifyType; type() : IdentitifyType;
@ -15,20 +18,24 @@ namespace profiles.identities {
encode?() : string; encode?() : string;
decode(data: string) : Promise<void>; decode(data: string) : Promise<void>;
spawn_identity_handshake_handler(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler; spawn_identity_handshake_handler(connection: AbstractServerConnection) : HandshakeIdentityHandler;
} }
export async function decode_identity(type: IdentitifyType, data: string) : Promise<Identity> { /* avoid circular dependencies here */
export async function decode_identity(type: IdentitifyType, data: string) : Promise<Identity> {
let identity: Identity; let identity: Identity;
switch (type) { switch (type) {
case IdentitifyType.NICKNAME: case IdentitifyType.NICKNAME:
identity = new NameIdentity(); const nidentity = require("tc-shared/profiles/identities/NameIdentity");
identity = new nidentity.NameIdentity();
break; break;
case IdentitifyType.TEAFORO: case IdentitifyType.TEAFORO:
identity = new TeaForumIdentity(undefined); const fidentity = require("tc-shared/profiles/identities/TeaForumIdentity");
identity = new fidentity.TeaForumIdentity(undefined);
break; break;
case IdentitifyType.TEAMSPEAK: case IdentitifyType.TEAMSPEAK:
identity = new TeaSpeakIdentity(undefined, undefined); const tidentity = require("tc-shared/profiles/identities/TeamSpeakIdentity");
identity = new tidentity.TeaSpeakIdentity(undefined, undefined);
break; break;
} }
if(!identity) if(!identity)
@ -43,34 +50,37 @@ namespace profiles.identities {
} }
return identity; return identity;
} }
export function create_identity(type: IdentitifyType) { export function create_identity(type: IdentitifyType) {
let identity: Identity; let identity: Identity;
switch (type) { switch (type) {
case IdentitifyType.NICKNAME: case IdentitifyType.NICKNAME:
identity = new NameIdentity(); const nidentity = require("tc-shared/profiles/identities/NameIdentity");
identity = new nidentity.NameIdentity();
break; break;
case IdentitifyType.TEAFORO: case IdentitifyType.TEAFORO:
identity = new TeaForumIdentity(undefined); const fidentity = require("tc-shared/profiles/identities/TeaForumIdentity");
identity = new fidentity.TeaForumIdentity(undefined);
break; break;
case IdentitifyType.TEAMSPEAK: case IdentitifyType.TEAMSPEAK:
identity = new TeaSpeakIdentity(undefined, undefined); const tidentity = require("tc-shared/profiles/identities/TeamSpeakIdentity");
identity = new tidentity.TeaSpeakIdentity(undefined, undefined);
break; break;
} }
return identity; return identity;
} }
export class HandshakeCommandHandler<T extends AbstractHandshakeIdentityHandler> extends connection.AbstractCommandHandler { export class HandshakeCommandHandler<T extends AbstractHandshakeIdentityHandler> extends AbstractCommandHandler {
readonly handle: T; readonly handle: T;
constructor(connection: connection.AbstractServerConnection, handle: T) { constructor(connection: AbstractServerConnection, handle: T) {
super(connection); super(connection);
this.handle = handle; this.handle = handle;
} }
handle_command(command: connection.ServerCommand): boolean { handle_command(command: ServerCommand): boolean {
if($.isFunction(this[command.command])) if($.isFunction(this[command.command]))
this[command.command](command.arguments); this[command.command](command.arguments);
else if(command.command == "error") { else if(command.command == "error") {
@ -80,14 +90,14 @@ namespace profiles.identities {
} }
return true; return true;
} }
} }
export abstract class AbstractHandshakeIdentityHandler implements connection.HandshakeIdentityHandler { export abstract class AbstractHandshakeIdentityHandler implements HandshakeIdentityHandler {
connection: connection.AbstractServerConnection; connection: AbstractServerConnection;
protected callbacks: ((success: boolean, message?: string) => any)[] = []; protected callbacks: ((success: boolean, message?: string) => any)[] = [];
protected constructor(connection: connection.AbstractServerConnection) { protected constructor(connection: AbstractServerConnection) {
this.connection = connection; this.connection = connection;
} }
@ -106,5 +116,4 @@ namespace profiles.identities {
for(const callback of this.callbacks) for(const callback of this.callbacks)
callback(false, message); 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 { class NameHandshakeHandler extends AbstractHandshakeIdentityHandler {
readonly identity: NameIdentity; readonly identity: NameIdentity;
handler: HandshakeCommandHandler<NameHandshakeHandler>; handler: HandshakeCommandHandler<NameHandshakeHandler>;
constructor(connection: connection.AbstractServerConnection, identity: profiles.identities.NameIdentity) { constructor(connection: AbstractServerConnection, identity: NameIdentity) {
super(connection); super(connection);
this.identity = identity; this.identity = identity;
@ -36,9 +46,9 @@ namespace profiles.identities {
this.connection.command_handler_boss().unregister_handler(this.handler); this.connection.command_handler_boss().unregister_handler(this.handler);
super.trigger_success(); super.trigger_success();
} }
} }
export class NameIdentity implements Identity { export class NameIdentity implements Identity {
private _name: string; private _name: string;
constructor(name?: string) { constructor(name?: string) {
@ -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); 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 {
class TeaForumHandshakeHandler extends AbstractHandshakeIdentityHandler {
readonly identity: TeaForumIdentity; readonly identity: TeaForumIdentity;
handler: HandshakeCommandHandler<TeaForumHandshakeHandler>; handler: HandshakeCommandHandler<TeaForumHandshakeHandler>;
constructor(connection: connection.AbstractServerConnection, identity: profiles.identities.TeaForumIdentity) { constructor(connection: AbstractServerConnection, identity: TeaForumIdentity) {
super(connection); super(connection);
this.identity = identity; this.identity = identity;
this.handler = new HandshakeCommandHandler(connection, this); this.handler = new HandshakeCommandHandler(connection, this);
@ -49,9 +59,9 @@ namespace profiles.identities {
this.connection.command_handler_boss().unregister_handler(this.handler); this.connection.command_handler_boss().unregister_handler(this.handler);
super.trigger_success(); super.trigger_success();
} }
} }
export class TeaForumIdentity implements Identity { export class TeaForumIdentity implements Identity {
private readonly identity_data: forum.Data; private readonly identity_data: forum.Data;
valid() : boolean { valid() : boolean {
@ -62,7 +72,7 @@ namespace profiles.identities {
this.identity_data = data; this.identity_data = data;
} }
data() : forum.Data { data() {
return this.identity_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); return new TeaForumHandshakeHandler(connection, this);
} }
@ -88,7 +98,7 @@ namespace profiles.identities {
return this.identity_data ? this.identity_data.name() : undefined; return this.identity_data ? this.identity_data.name() : undefined;
} }
type(): profiles.identities.IdentitifyType { type(): IdentitifyType {
return IdentitifyType.TEAFORO; return IdentitifyType.TEAFORO;
} }
@ -96,27 +106,30 @@ namespace profiles.identities {
//FIXME: Real UID! //FIXME: Real UID!
return "TeaForo#" + ((this.identity_data ? this.identity_data.name() : "Another TeaSpeak user")); return "TeaForo#" + ((this.identity_data ? this.identity_data.name() : "Another TeaSpeak user"));
} }
public static identity() {
return static_identity;
} }
}
let static_identity: TeaForumIdentity; let static_identity: TeaForumIdentity;
export function set_static_identity(identity: TeaForumIdentity) { export function set_static_identity(identity: TeaForumIdentity) {
static_identity = identity; static_identity = identity;
} }
export function update_forum() { export function update_forum() {
if(forum.logged_in() && (!static_identity || static_identity.data() !== forum.data())) { if(forum.logged_in() && (!static_identity || static_identity.data() !== forum.data())) {
static_identity = new TeaForumIdentity(forum.data()); static_identity = new TeaForumIdentity(forum.data());
} else { } else {
static_identity = undefined; static_identity = undefined;
} }
} }
export function valid_static_forum_identity() : boolean { export function valid_static_forum_identity() : boolean {
return static_identity && static_identity.valid(); return static_identity && static_identity.valid();
} }
export function static_forum_identity() : TeaForumIdentity | undefined { export function static_forum_identity() : TeaForumIdentity | undefined {
return static_identity; 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";
namespace profiles.identities { import {
export namespace CryptoHelper { 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";
export namespace CryptoHelper {
export function base64_url_encode(str){ 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){ export function base64_url_decode(str: string, pad?: boolean){
@ -192,11 +206,6 @@ namespace profiles.identities {
k = k.substr(1); 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 { return {
crv: "P-256", crv: "P-256",
d: base64_url_encode(btoa(k)), d: base64_url_encode(btoa(k)),
@ -208,13 +217,13 @@ namespace profiles.identities {
kty:"EC", kty:"EC",
}; };
} }
} }
class TeaSpeakHandshakeHandler extends AbstractHandshakeIdentityHandler { class TeaSpeakHandshakeHandler extends AbstractHandshakeIdentityHandler {
identity: TeaSpeakIdentity; identity: TeaSpeakIdentity;
handler: HandshakeCommandHandler<TeaSpeakHandshakeHandler>; handler: HandshakeCommandHandler<TeaSpeakHandshakeHandler>;
constructor(connection: connection.AbstractServerConnection, identity: TeaSpeakIdentity) { constructor(connection: AbstractServerConnection, identity: TeaSpeakIdentity) {
super(connection); super(connection);
this.identity = identity; this.identity = identity;
this.handler = new HandshakeCommandHandler(connection, this); this.handler = new HandshakeCommandHandler(connection, this);
@ -264,9 +273,9 @@ namespace profiles.identities {
this.connection.command_handler_boss().unregister_handler(this.handler); this.connection.command_handler_boss().unregister_handler(this.handler);
super.trigger_success(); super.trigger_success();
} }
} }
class IdentityPOWWorker { class IdentityPOWWorker {
private _worker: Worker; private _worker: Worker;
private _current_hash: string; private _current_hash: string;
private _best_level: number; private _best_level: number;
@ -418,9 +427,9 @@ namespace profiles.identities {
private handle_message(message: any) { private handle_message(message: any) {
log.info(LogCategory.IDENTITIES, tr("Received message: %o"), message); log.info(LogCategory.IDENTITIES, tr("Received message: %o"), message);
} }
} }
export class TeaSpeakIdentity implements Identity { export class TeaSpeakIdentity implements Identity {
static async generate_new() : Promise<TeaSpeakIdentity> { static async generate_new() : Promise<TeaSpeakIdentity> {
let key: CryptoKeyPair; let key: CryptoKeyPair;
try { try {
@ -802,7 +811,6 @@ namespace profiles.identities {
} }
this._initialized = true; this._initialized = true;
//const public_key = await profiles.identities.CryptoHelper.export_ecc_key(key, true);
} }
async export_ts(ini?: boolean) : Promise<string> { async export_ts(ini?: boolean) : Promise<string> {
@ -864,8 +872,7 @@ namespace profiles.identities {
return base64_encode_ab(buffer.subarray(0, index)); 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); return new TeaSpeakHandshakeHandler(connection, this);
} }
}
} }

View file

@ -1,5 +1,11 @@
interface Window { import {settings, Settings} from "tc-shared/settings";
import * as loader from "tc-loader";
import * as fidentity from "./TeaForumIdentity";
declare global {
interface Window {
grecaptcha: GReCaptcha; grecaptcha: GReCaptcha;
}
} }
interface GReCaptcha { interface GReCaptcha {
@ -18,8 +24,7 @@ interface GReCaptcha {
reset(widget_id?: string); reset(widget_id?: string);
} }
namespace forum { export namespace gcaptcha {
export namespace gcaptcha {
export async function initialize() { export async function initialize() {
if(typeof(window.grecaptcha) === "undefined") { if(typeof(window.grecaptcha) === "undefined") {
let script = document.createElement("script"); let script = document.createElement("script");
@ -46,7 +51,7 @@ namespace forum {
if(script) if(script)
script.onerror = undefined; script.onerror = undefined;
delete window[callback_name]; delete window[callback_name];
clearTimeout(timeout); timeout && clearTimeout(timeout);
} }
} }
@ -70,13 +75,13 @@ namespace forum {
})); }));
} }
} }
} }
function api_url() { function api_url() {
return settings.static_global(Settings.KEY_TEAFORO_URL); return settings.static_global(Settings.KEY_TEAFORO_URL);
} }
export class Data { export class Data {
readonly auth_key: string; readonly auth_key: string;
readonly raw: string; readonly raw: string;
readonly sign: string; readonly sign: string;
@ -117,16 +122,16 @@ namespace forum {
is_expired() : boolean { return this.parsed.data_age + 48 * 60 * 60 * 1000 < Date.now(); } is_expired() : boolean { return this.parsed.data_age + 48 * 60 * 60 * 1000 < Date.now(); }
should_renew() : boolean { return this.parsed.data_age + 24 * 60 * 60 * 1000 < Date.now(); } /* renew data all 24hrs */ should_renew() : boolean { return this.parsed.data_age + 24 * 60 * 60 * 1000 < Date.now(); } /* renew data all 24hrs */
} }
let _data: Data | undefined; let _data: Data | undefined;
export function logged_in() : boolean { export function logged_in() : boolean {
return !!_data && !_data.is_expired(); return !!_data && !_data.is_expired();
} }
export function data() : Data { return _data; } export function data() : Data { return _data; }
export interface LoginResult { export interface LoginResult {
status: "success" | "captcha" | "error"; status: "success" | "captcha" | "error";
error_message?: string; error_message?: string;
@ -134,9 +139,9 @@ namespace forum {
type: "gre-captcha" | "unknown"; type: "gre-captcha" | "unknown";
data: any; /* in case of gre-captcha it would be the side key */ data: any; /* in case of gre-captcha it would be the side key */
}; };
} }
export async function login(username: string, password: string, captcha?: any) : Promise<LoginResult> { export async function login(username: string, password: string, captcha?: any) : Promise<LoginResult> {
let response; let response;
try { try {
response = await new Promise<any>((resolve, reject) => { response = await new Promise<any>((resolve, reject) => {
@ -206,7 +211,7 @@ namespace forum {
localStorage.setItem("teaspeak-forum-data", response["data"]); localStorage.setItem("teaspeak-forum-data", response["data"]);
localStorage.setItem("teaspeak-forum-sign", response["sign"]); localStorage.setItem("teaspeak-forum-sign", response["sign"]);
localStorage.setItem("teaspeak-forum-auth", response["auth-key"]); localStorage.setItem("teaspeak-forum-auth", response["auth-key"]);
profiles.identities.update_forum(); fidentity.update_forum();
} catch(error) { } catch(error) {
console.error(tr("Failed to parse forum given data: %o"), error); console.error(tr("Failed to parse forum given data: %o"), error);
return { return {
@ -218,9 +223,9 @@ namespace forum {
return { return {
status: "success" status: "success"
}; };
} }
export async function renew_data() : Promise<"success" | "login-required"> { export async function renew_data() : Promise<"success" | "login-required"> {
let response; let response;
try { try {
response = await new Promise<any>((resolve, reject) => { response = await new Promise<any>((resolve, reject) => {
@ -266,16 +271,16 @@ namespace forum {
_data = new Data(_data.auth_key, response["data"], response["sign"]); _data = new Data(_data.auth_key, response["data"], response["sign"]);
localStorage.setItem("teaspeak-forum-data", response["data"]); localStorage.setItem("teaspeak-forum-data", response["data"]);
localStorage.setItem("teaspeak-forum-sign", response["sign"]); localStorage.setItem("teaspeak-forum-sign", response["sign"]);
profiles.identities.update_forum(); fidentity.update_forum();
} catch(error) { } catch(error) {
console.error(tr("Failed to parse forum given data: %o"), error); console.error(tr("Failed to parse forum given data: %o"), error);
throw tr("failed to parse data"); throw tr("failed to parse data");
} }
return "success"; return "success";
} }
export async function logout() : Promise<void> { export async function logout() : Promise<void> {
if(!logged_in()) if(!logged_in())
return; return;
@ -320,10 +325,10 @@ namespace forum {
localStorage.removeItem("teaspeak-forum-data"); localStorage.removeItem("teaspeak-forum-data");
localStorage.removeItem("teaspeak-forum-sign"); localStorage.removeItem("teaspeak-forum-sign");
localStorage.removeItem("teaspeak-forum-auth"); localStorage.removeItem("teaspeak-forum-auth");
profiles.identities.update_forum(); fidentity.update_forum();
} }
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "TeaForo initialize", name: "TeaForo initialize",
priority: 10, priority: 10,
function: async () => { function: async () => {
@ -362,5 +367,4 @@ namespace forum {
console.error(tr("TeaForo data is expired. TeaForo connection isn't available!")); console.error(tr("TeaForo data is expired. TeaForo connection isn't available!"));
} }
} }
}) })
}

View file

@ -1,19 +1,20 @@
//Used by CertAccept popup //Used by CertAccept popup
interface Array<T> { declare global {
interface Array<T> {
remove(elem?: T): boolean; remove(elem?: T): boolean;
last?(): T; last?(): T;
pop_front(): T | undefined; pop_front(): T | undefined;
} }
interface JSON { interface JSON {
map_to<T>(object: T, json: any, variables?: string | string[], validator?: (map_field: string, map_value: string) => boolean, variable_direction?: number) : number; map_to<T>(object: T, json: any, variables?: string | string[], validator?: (map_field: string, map_value: string) => boolean, variable_direction?: number) : number;
map_field_to<T>(object: T, value: any, field: string) : boolean; map_field_to<T>(object: T, value: any, field: string) : boolean;
} }
type JQueryScrollType = "height" | "width"; type JQueryScrollType = "height" | "width";
interface JQuery<TElement = HTMLElement> { interface JQuery<TElement = HTMLElement> {
render(values?: any) : string; render(values?: any) : string;
renderTag(values?: any) : JQuery<TElement>; renderTag(values?: any) : JQuery<TElement>;
hasScrollBar(direction?: JQueryScrollType) : boolean; hasScrollBar(direction?: JQueryScrollType) : boolean;
@ -29,18 +30,81 @@ interface JQuery<TElement = HTMLElement> {
/* first element which matches the selector, could be the element itself or a parent */ /* first element which matches the selector, could be the element itself or a parent */
firstParent(selector: string) : JQuery; firstParent(selector: string) : JQuery;
} }
interface JQueryStatic<TElement extends Node = HTMLElement> { interface JQueryStatic<TElement extends Node = HTMLElement> {
spawn<K extends keyof HTMLElementTagNameMap>(tagName: K): JQuery<HTMLElementTagNameMap[K]>; spawn<K extends keyof HTMLElementTagNameMap>(tagName: K): JQuery<HTMLElementTagNameMap[K]>;
views: any; views: any;
} }
interface String { interface String {
format(...fmt): string; format(...fmt): string;
format(arguments: string[]): 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) { 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 { 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; if (!validator) validator = (a, b) => true;
@ -131,6 +195,7 @@ if(typeof ($) !== "undefined") {
return $(document.createElement(tagName) as any); return $(document.createElement(tagName) as any);
} }
} }
if(!$.fn.renderTag) { if(!$.fn.renderTag) {
$.fn.renderTag = function (this: JQuery, values?: any) : JQuery { $.fn.renderTag = function (this: JQuery, values?: any) : JQuery {
let result; let result;
@ -184,7 +249,8 @@ if(typeof ($) !== "undefined") {
const result = this.height(); const result = this.height();
this.attr("style", original_style || ""); this.attr("style", original_style || "");
return result; return result;
} };
if(!$.fn.visible_width) if(!$.fn.visible_width)
$.fn.visible_width = function (this: JQuery<HTMLElement>) { $.fn.visible_width = function (this: JQuery<HTMLElement>) {
const original_style = this.attr("style"); const original_style = this.attr("style");
@ -197,7 +263,8 @@ if(typeof ($) !== "undefined") {
const result = this.width(); const result = this.width();
this.attr("style", original_style || ""); this.attr("style", original_style || "");
return result; return result;
} };
if(!$.fn.firstParent) if(!$.fn.firstParent)
$.fn.firstParent = function (this: JQuery<HTMLElement>, selector: string) { $.fn.firstParent = function (this: JQuery<HTMLElement>, selector: string) {
if(this.is(selector)) if(this.is(selector))
@ -232,30 +299,6 @@ function concatenate(resultConstructor, ...arrays) {
return result; 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 { function calculate_width(text: string) : number {
let element = $.spawn("div"); let element = $.spawn("div");
element.text(text) element.text(text)
@ -266,63 +309,3 @@ function calculate_width(text: string) : number {
element.detach(); element.detach();
return size; 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 //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") { if(typeof(customElements) !== "undefined") {
try { try {
class X_Properties extends HTMLElement {} class X_Properties extends HTMLElement {}
@ -9,12 +13,12 @@ if(typeof(customElements) !== "undefined") {
customElements.define('x-properties', X_Properties, { extends: 'div' }); customElements.define('x-properties', X_Properties, { extends: 'div' });
customElements.define('x-property', X_Property, { extends: 'div' }); customElements.define('x-property', X_Property, { extends: 'div' });
} catch(error) { } catch(error) {
console.warn("failed to define costum elements"); console.warn("failed to define costume elements");
} }
} }
/* T = value type */ /* T = value type */
interface SettingsKey<T> { export interface SettingsKey<T> {
key: string; key: string;
fallback_keys?: string | string[]; fallback_keys?: string | string[];
@ -25,7 +29,7 @@ interface SettingsKey<T> {
require_restart?: boolean; require_restart?: boolean;
} }
class SettingsBase { export class SettingsBase {
protected static readonly UPDATE_DIRECT: boolean = true; protected static readonly UPDATE_DIRECT: boolean = true;
protected static transformStO?<T>(input?: string, _default?: T, default_type?: string) : T { 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; private static _instance: StaticSettings;
static get instance() : StaticSettings { static get instance() : StaticSettings {
if(!this._instance) 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> = { static readonly KEY_USER_IS_NEW: SettingsKey<boolean> = {
key: 'user_is_new_user', key: 'user_is_new_user',
default_value: true default_value: true
@ -433,7 +437,7 @@ class Settings extends StaticSettings {
} }
} }
class ServerSettings extends SettingsBase { export class ServerSettings extends SettingsBase {
private cacheServer = {}; private cacheServer = {};
private _server_unique_id: string; private _server_unique_id: string;
private _server_save_worker: NodeJS.Timer; 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_TEST = "sound.test",
SOUND_EGG = "sound.egg", SOUND_EGG = "sound.egg",
@ -61,33 +67,32 @@ enum Sound {
GROUP_CHANNEL_CHANGED_SELF = "group.channel.changed.self" GROUP_CHANNEL_CHANGED_SELF = "group.channel.changed.self"
} }
namespace sound { export interface SoundHandle {
export interface SoundHandle {
key: string; key: string;
filename: string; filename: string;
} }
export interface SoundFile { export interface SoundFile {
path: string; path: string;
volume?: number; volume?: number;
} }
let speech_mapping: {[key: string]:SoundHandle} = {}; let speech_mapping: {[key: string]:SoundHandle} = {};
let volume_require_save = false; let volume_require_save = false;
let speech_volume: {[key: string]:number} = {}; let speech_volume: {[key: string]:number} = {};
let master_volume: number; let master_volume: number;
let overlap_sounds: boolean; let overlap_sounds: boolean;
let ignore_muted: boolean; let ignore_muted: boolean;
let master_mixed: GainNode; let master_mixed: GainNode;
function register_sound(key: string, file: string) { function register_sound(key: string, file: string) {
speech_mapping[key] = {key: key, filename: file} as SoundHandle; speech_mapping[key] = {key: key, filename: file} as SoundHandle;
} }
export function get_sound_volume(sound: Sound, default_volume?: number) : number { export function get_sound_volume(sound: Sound, default_volume?: number) : number {
let result = speech_volume[sound]; let result = speech_volume[sound];
if(typeof(result) === "undefined") { if(typeof(result) === "undefined") {
if(typeof(default_volume) !== "undefined") if(typeof(default_volume) !== "undefined")
@ -96,18 +101,18 @@ namespace sound {
result = 1; result = 1;
} }
return result; return result;
} }
export function set_sound_volume(sound: Sound, volume: number) { export function set_sound_volume(sound: Sound, volume: number) {
volume_require_save = volume_require_save || speech_volume[sound] != volume; volume_require_save = volume_require_save || speech_volume[sound] != volume;
speech_volume[sound] = volume == 1 ? undefined : volume; speech_volume[sound] = volume == 1 ? undefined : volume;
} }
export function get_master_volume() : number { export function get_master_volume() : number {
return master_volume; return master_volume;
} }
export function set_master_volume(volume: number) { export function set_master_volume(volume: number) {
volume_require_save = volume_require_save || master_volume != volume; volume_require_save = volume_require_save || master_volume != volume;
master_volume = volume; master_volume = volume;
if(master_mixed) { if(master_mixed) {
@ -116,49 +121,34 @@ namespace sound {
else else
master_mixed.gain.value = volume; master_mixed.gain.value = volume;
} }
} }
export function overlap_activated() : boolean { export function overlap_activated() : boolean {
return overlap_sounds; return overlap_sounds;
} }
export function set_overlap_activated(flag: boolean) { export function set_overlap_activated(flag: boolean) {
volume_require_save = volume_require_save || overlap_sounds != flag; volume_require_save = volume_require_save || overlap_sounds != flag;
overlap_sounds = flag; overlap_sounds = flag;
} }
export function ignore_output_muted() : boolean { export function ignore_output_muted() : boolean {
return ignore_muted; return ignore_muted;
} }
export function set_ignore_output_muted(flag: boolean) { export function set_ignore_output_muted(flag: boolean) {
volume_require_save = volume_require_save || ignore_muted != flag; volume_require_save = volume_require_save || ignore_muted != flag;
ignore_muted = flag; ignore_muted = flag;
} }
export function reinitialisize_audio() { export function save() {
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) { if(volume_require_save) {
volume_require_save = false; volume_require_save = false;
const data: any = {}; const data: any = {};
data.version = 1; data.version = 1;
for(const key in Sound) { for(const key of Object.keys(Sound)) {
if(typeof(speech_volume[Sound[key]]) !== "undefined") if(typeof(speech_volume[Sound[key]]) !== "undefined")
data[Sound[key]] = speech_volume[Sound[key]]; data[Sound[key]] = speech_volume[Sound[key]];
} }
@ -168,9 +158,9 @@ namespace sound {
settings.changeGlobal("sound_volume", JSON.stringify(data)); settings.changeGlobal("sound_volume", JSON.stringify(data));
} }
} }
export function initialize() : Promise<void> { export function initialize() : Promise<void> {
$.ajaxSetup({ $.ajaxSetup({
beforeSend: function(jqXHR,settings){ beforeSend: function(jqXHR,settings){
if (settings.dataType === 'binary') { if (settings.dataType === 'binary') {
@ -183,7 +173,7 @@ namespace sound {
/* volumes */ /* volumes */
{ {
const data = JSON.parse(settings.static_global("sound_volume", "{}")); 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") if(typeof(data[Sound[sound_key]]) !== "undefined")
speech_volume[Sound[sound_key]] = data[Sound[sound_key]]; speech_volume[Sound[sound_key]] = data[Sound[sound_key]];
} }
@ -197,7 +187,6 @@ namespace sound {
register_sound("message.send", "effects/message_send.wav"); register_sound("message.send", "effects/message_send.wav");
manager = new SoundManager(undefined); manager = new SoundManager(undefined);
audio.player.on_ready(reinitialisize_audio);
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
$.ajax({ $.ajax({
url: "audio/speech/mapping.json", url: "audio/speech/mapping.json",
@ -217,27 +206,27 @@ namespace sound {
type: 'GET' type: 'GET'
}); });
}); });
} }
export interface PlaybackOptions { export interface PlaybackOptions {
ignore_muted?: boolean; ignore_muted?: boolean;
ignore_overlap?: boolean; ignore_overlap?: boolean;
default_volume?: number; default_volume?: number;
callback?: (flag: boolean) => any; callback?: (flag: boolean) => any;
} }
export async function resolve_sound(sound: Sound) : Promise<SoundHandle> { export async function resolve_sound(sound: Sound) : Promise<SoundHandle> {
const file: SoundHandle = speech_mapping[sound]; const file: SoundHandle = speech_mapping[sound];
if(!file) throw tr("Missing sound handle"); if(!file) throw tr("Missing sound handle");
return file; return file;
} }
export let manager: SoundManager; export let manager: SoundManager;
export class SoundManager { export class SoundManager {
private readonly _handle: ConnectionHandler; private readonly _handle: ConnectionHandler;
private _playing_sounds: {[key: string]:number} = {}; private _playing_sounds: {[key: string]:number} = {};
@ -254,25 +243,19 @@ namespace sound {
if(volume == 0 || master_volume == 0) if(volume == 0 || master_volume == 0)
return; 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; return;
const context = audio.player.context(); resolve_sound(_sound).then(handle => {
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 => {
if(!handle) return; 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); log.info(LogCategory.AUDIO, tr("Dropping requested playback for sound %s because it would overlap."), _sound);
return; return;
} }
this._playing_sounds[handle.filename] = (this._playing_sounds[handle.filename] || 0) + 1; this._playing_sounds[handle.filename] = (this._playing_sounds[handle.filename] || 0) + 1;
audio.sounds.play_sound({ sbackend.play_sound({
path: "audio/" + handle.filename, path: "audio/" + handle.filename,
volume: volume * master_volume volume: volume * master_volume
}).then(() => { }).then(() => {
@ -286,10 +269,9 @@ namespace sound {
this._playing_sounds[handle.filename]--; this._playing_sounds[handle.filename]--;
}); });
}).catch(error => { }).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) if(options.callback)
options.callback(false); options.callback(false);
}); });
} }
}
} }

View file

@ -1,22 +1,24 @@
namespace stats { import {LogCategory} from "tc-shared/log";
const LOG_PREFIX = "[Statistics] "; import * as log from "tc-shared/log";
export enum CloseCodes { const LOG_PREFIX = "[Statistics] ";
enum CloseCodes {
UNSET = 3000, UNSET = 3000,
RECONNECT = 3001, RECONNECT = 3001,
INTERNAL_ERROR = 3002, INTERNAL_ERROR = 3002,
BANNED = 3100, BANNED = 3100,
} }
enum ConnectionState { enum ConnectionState {
CONNECTING, CONNECTING,
INITIALIZING, INITIALIZING,
CONNECTED, CONNECTED,
UNSET UNSET
} }
export class SessionConfig { export class SessionConfig {
/* /*
* All collected statistics will only be cached by the stats server. * All collected statistics will only be cached by the stats server.
* No data will be saved. * No data will be saved.
@ -28,35 +30,35 @@ namespace stats {
* This option is quite useless when volatile_collection_only is active. * This option is quite useless when volatile_collection_only is active.
*/ */
anonymize_ip_addresses?: boolean; anonymize_ip_addresses?: boolean;
} }
export class Config extends SessionConfig { export class Config extends SessionConfig {
verbose?: boolean; verbose?: boolean;
reconnect_interval?: number; reconnect_interval?: number;
} }
export interface UserCountData { export interface UserCountData {
online_users: number; online_users: number;
unique_online_users: number; unique_online_users: number;
} }
export type UserCountListener = (data: UserCountData) => any; export type UserCountListener = (data: UserCountData) => any;
let reconnect_timer: NodeJS.Timer; let reconnect_timer: NodeJS.Timer;
let current_config: Config; let current_config: Config;
let last_user_count_update: number; let last_user_count_update: number;
let user_count_listener: UserCountListener[] = []; let user_count_listener: UserCountListener[] = [];
const DEFAULT_CONFIG: Config = { const DEFAULT_CONFIG: Config = {
verbose: true, verbose: true,
reconnect_interval: 5000, reconnect_interval: 5000,
anonymize_ip_addresses: true, anonymize_ip_addresses: true,
volatile_collection_only: false volatile_collection_only: false
}; };
function initialize_config_object(target_object: any, source_object: any) : any { function initialize_config_object(target_object: any, source_object: any) : any {
for(const key of Object.keys(source_object)) { for(const key of Object.keys(source_object)) {
if(typeof(source_object[key]) === 'object') if(typeof(source_object[key]) === 'object')
initialize_config_object(target_object[key] || (target_object[key] = {}), source_object[key]); initialize_config_object(target_object[key] || (target_object[key] = {}), source_object[key]);
@ -68,29 +70,29 @@ namespace stats {
} }
return target_object; return target_object;
} }
export function initialize(config: Config) { export function initialize(config: Config) {
current_config = initialize_config_object(config || {}, DEFAULT_CONFIG); current_config = initialize_config_object(config || {}, DEFAULT_CONFIG);
if(current_config.verbose) if(current_config.verbose)
log.info(LogCategory.STATISTICS, tr("Initializing statistics with this config: %o"), current_config); log.info(LogCategory.STATISTICS, tr("Initializing statistics with this config: %o"), current_config);
connection.start_connection(); connection.start_connection();
} }
export function register_user_count_listener(listener: UserCountListener) { export function register_user_count_listener(listener: UserCountListener) {
user_count_listener.push(listener); user_count_listener.push(listener);
} }
export function all_user_count_listener() : UserCountListener[] { export function all_user_count_listener() : UserCountListener[] {
return user_count_listener; return user_count_listener;
} }
export function deregister_user_count_listener(listener: UserCountListener) { export function deregister_user_count_listener(listener: UserCountListener) {
user_count_listener.remove(listener); user_count_listener.remove(listener);
} }
namespace connection { namespace connection {
let connection: WebSocket; let connection: WebSocket;
export let connection_state: ConnectionState = ConnectionState.UNSET; export let connection_state: ConnectionState = ConnectionState.UNSET;
@ -239,5 +241,4 @@ namespace stats {
handler["notifyinitialized"] = handle_notify_initialized; handler["notifyinitialized"] = handle_notify_initialized;
handler["notifyusercount"] = handle_notify_user_count; handler["notifyusercount"] = handle_notify_user_count;
} }
}
} }

View file

@ -1,12 +1,26 @@
/// <reference path="view.ts" /> import {ChannelTree} from "tc-shared/ui/view";
/// <reference path="../utils/helpers.ts" /> 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, PERMANENT,
SEMI_PERMANENT, SEMI_PERMANENT,
TEMPORARY TEMPORARY
} }
namespace ChannelType { export namespace ChannelType {
export function normalize(mode: ChannelType) { export function normalize(mode: ChannelType) {
let value: string = ChannelType[mode]; let value: string = ChannelType[mode];
value = value.toLowerCase(); value = value.toLowerCase();
@ -14,13 +28,13 @@ namespace ChannelType {
} }
} }
enum ChannelSubscribeMode { export enum ChannelSubscribeMode {
SUBSCRIBED, SUBSCRIBED,
UNSUBSCRIBED, UNSUBSCRIBED,
INHERITED INHERITED
} }
class ChannelProperties { export class ChannelProperties {
channel_order: number = 0; channel_order: number = 0;
channel_name: string = ""; channel_name: string = "";
channel_name_phonetic: string = ""; channel_name_phonetic: string = "";
@ -55,7 +69,7 @@ class ChannelProperties {
channel_conversation_history_length: number = -1; channel_conversation_history_length: number = -1;
} }
class ChannelEntry { export class ChannelEntry {
channelTree: ChannelTree; channelTree: ChannelTree;
channelId: number; channelId: number;
parent?: ChannelEntry; parent?: ChannelEntry;
@ -525,7 +539,7 @@ class ChannelEntry {
name: tr("Show channel info"), name: tr("Show channel info"),
callback: () => { callback: () => {
trigger_close = false; trigger_close = false;
Modals.openChannelInfo(this); openChannelInfo(this);
}, },
icon_class: "client-about" icon_class: "client-about"
}, },
@ -565,7 +579,7 @@ class ChannelEntry {
name: tr("Edit channel"), name: tr("Edit channel"),
invalidPermission: !channelModify, invalidPermission: !channelModify,
callback: () => { 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) { if(changes) {
changes["cid"] = this.channelId; changes["cid"] = this.channelId;
this.channelTree.client.serverConnection.send_command("channeledit", changes); this.channelTree.client.serverConnection.send_command("channeledit", changes);
@ -617,7 +631,7 @@ class ChannelEntry {
error = error.extra_message || error.message; 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)) { !this.channelTree.client.permissions.neededPermission(PermissionType.B_CHANNEL_JOIN_IGNORE_PASSWORD).granted(1)) {
createInputModal(tr("Channel password"), tr("Channel password:"), () => true, text => { createInputModal(tr("Channel password"), tr("Channel password:"), () => true, text => {
if(typeof(text) == typeof(true)) return; if(typeof(text) == typeof(true)) return;
helpers.hashPassword(text as string).then(result => { hashPassword(text as string).then(result => {
this._cachedPassword = result; this._cachedPassword = result;
this.joinChannel(); this.joinChannel();
this.updateChannelTypeIcon(); this.updateChannelTypeIcon();
@ -923,7 +937,7 @@ class ChannelEntry {
this._tag_channel.find(".marker-text-unread").toggleClass("hidden", !flag); this._tag_channel.find(".marker-text-unread").toggleClass("hidden", !flag);
} }
log_data() : log.server.base.Channel { log_data() : server_log.base.Channel {
return { return {
channel_name: this.channelName(), channel_name: this.channelName(),
channel_id: this.channelId channel_id: this.channelId

View file

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

View file

@ -1,5 +1,15 @@
interface JQuery<TElement = HTMLElement> { 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; dividerfy() : this;
}
}
export function initialize() {
} }
if(!$.fn.dividerfy) { if(!$.fn.dividerfy) {
@ -58,8 +68,8 @@ if(!$.fn.dividerfy) {
Math.max(previous_offset.top + previous_element.height(), next_offset.top + next_element.height()); Math.max(previous_offset.top + previous_element.height(), next_offset.top + next_element.height());
*/ */
let previous = 0; let previous;
let next = 0; let next;
if(current < min) { if(current < min) {
previous = 0; previous = 0;
next = 1; next = 1;
@ -89,7 +99,7 @@ if(!$.fn.dividerfy) {
})); }));
}; };
const listener_up = (event: MouseEvent) => { const listener_up = () => {
document.removeEventListener('mousemove', listener_move); document.removeEventListener('mousemove', listener_move);
document.removeEventListener('touchmove', listener_move); document.removeEventListener('touchmove', listener_move);

View file

@ -1,5 +1,4 @@
namespace contextmenu { export interface MenuEntry {
export interface MenuEntry {
callback?: () => void; callback?: () => void;
type: MenuEntryType; type: MenuEntryType;
name: (() => string) | string; name: (() => string) | string;
@ -12,17 +11,17 @@ namespace contextmenu {
invalidPermission?: boolean; invalidPermission?: boolean;
sub_menu?: MenuEntry[]; sub_menu?: MenuEntry[];
} }
export enum MenuEntryType { export enum MenuEntryType {
CLOSE, CLOSE,
ENTRY, ENTRY,
CHECKBOX, CHECKBOX,
HR, HR,
SUB_MENU SUB_MENU
} }
export class Entry { export class Entry {
static HR() { static HR() {
return { return {
callback: () => {}, callback: () => {},
@ -40,9 +39,9 @@ namespace contextmenu {
icon: "" icon: ""
}; };
} }
} }
export interface ContextMenuProvider { export interface ContextMenuProvider {
despawn_context_menu(); despawn_context_menu();
spawn_context_menu(x: number, y: number, ...entries: MenuEntry[]); spawn_context_menu(x: number, y: number, ...entries: MenuEntry[]);
@ -50,33 +49,32 @@ namespace contextmenu {
finalize(); finalize();
html_format_enabled() : boolean; html_format_enabled() : boolean;
} }
let provider: ContextMenuProvider; let provider: ContextMenuProvider;
export function spawn_context_menu(x: number, y: number, ...entries: MenuEntry[]) { export function spawn_context_menu(x: number, y: number, ...entries: MenuEntry[]) {
if(!provider) { if(!provider) {
console.error(tr("Failed to spawn context menu! Missing provider!")); console.error(tr("Failed to spawn context menu! Missing provider!"));
return; return;
} }
provider.spawn_context_menu(x, y, ...entries); provider.spawn_context_menu(x, y, ...entries);
} }
export function despawn_context_menu() { export function despawn_context_menu() {
if(!provider) if(!provider)
return; return;
provider.despawn_context_menu(); provider.despawn_context_menu();
}
export function get_provider() : ContextMenuProvider { return provider; }
export function set_provider(_provider: ContextMenuProvider) {
provider = _provider;
provider.initialize();
}
} }
class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider { export function get_provider() : ContextMenuProvider { return provider; }
export function set_provider(_provider: ContextMenuProvider) {
provider = _provider;
provider.initialize();
}
class HTMLContextMenuProvider implements ContextMenuProvider {
private _global_click_listener: (event) => any; private _global_click_listener: (event) => any;
private _context_menu: JQuery; private _context_menu: JQuery;
private _close_callbacks: (() => any)[] = []; private _close_callbacks: (() => any)[] = [];
@ -118,10 +116,10 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
} }
} }
private generate_tag(entry: contextmenu.MenuEntry) : JQuery { private generate_tag(entry: MenuEntry) : JQuery {
if(entry.type == contextmenu.MenuEntryType.HR) { if(entry.type == MenuEntryType.HR) {
return $.spawn("hr"); return $.spawn("hr");
} else if(entry.type == contextmenu.MenuEntryType.ENTRY) { } else if(entry.type == MenuEntryType.ENTRY) {
let icon = entry.icon_class; let icon = entry.icon_class;
if(!icon || icon.length == 0) icon = "icon_empty"; if(!icon || icon.length == 0) icon = "icon_empty";
else icon = "icon " + icon; else icon = "icon " + icon;
@ -141,7 +139,7 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
}); });
} }
return tag; return tag;
} else if(entry.type == contextmenu.MenuEntryType.CHECKBOX) { } else if(entry.type == MenuEntryType.CHECKBOX) {
let checkbox = $.spawn("label").addClass("ccheckbox"); let checkbox = $.spawn("label").addClass("ccheckbox");
$.spawn("input").attr("type", "checkbox").prop("checked", !!entry.checkbox_checked).appendTo(checkbox); $.spawn("input").attr("type", "checkbox").prop("checked", !!entry.checkbox_checked).appendTo(checkbox);
$.spawn("span").addClass("checkmark").appendTo(checkbox); $.spawn("span").addClass("checkmark").appendTo(checkbox);
@ -161,7 +159,7 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
}); });
} }
return tag; return tag;
} else if(entry.type == contextmenu.MenuEntryType.SUB_MENU) { } else if(entry.type == MenuEntryType.SUB_MENU) {
let icon = entry.icon_class; let icon = entry.icon_class;
if(!icon || icon.length == 0) icon = "icon_empty"; if(!icon || icon.length == 0) icon = "icon_empty";
else icon = "icon " + icon; else icon = "icon " + icon;
@ -187,7 +185,7 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
return $.spawn("div").text("undefined"); 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; this._visible = true;
let menu_tag = this._context_menu || (this._context_menu = $(".context-menu")); 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) if(typeof(entry.visible) === 'boolean' && !entry.visible)
continue; continue;
if(entry.type == contextmenu.MenuEntryType.CLOSE) { if(entry.type == MenuEntryType.CLOSE) {
if(entry.callback) if(entry.callback)
this._close_callbacks.push(entry.callback); this._close_callbacks.push(entry.callback);
} else } else
@ -225,5 +223,4 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
return true; return true;
} }
} }
set_provider(new HTMLContextMenuProvider());
contextmenu.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, HEADER,
BODY, BODY,
FOOTER FOOTER
} }
type BodyCreator = (() => JQuery | JQuery[] | string) | string | JQuery | JQuery[]; export type BodyCreator = (() => JQuery | JQuery[] | string) | string | JQuery | JQuery[];
const ModalFunctions = { export const ModalFunctions = {
divify: function (val: JQuery) { divify: function (val: JQuery) {
if(val.length > 1) if(val.length > 1)
return $.spawn("div").append(val); return $.spawn("div").append(val);
@ -54,7 +54,7 @@ const ModalFunctions = {
} }
}; };
class ModalProperties { export class ModalProperties {
template?: string; template?: string;
header: BodyCreator = () => "HEADER"; header: BodyCreator = () => "HEADER";
body: BodyCreator = () => "BODY"; body: BodyCreator = () => "BODY";
@ -184,7 +184,7 @@ let _global_modal_count = 0;
let _global_modal_last: HTMLElement; let _global_modal_last: HTMLElement;
let _global_modal_last_time: number; let _global_modal_last_time: number;
class Modal { export class Modal {
private _htmlTag: JQuery; private _htmlTag: JQuery;
properties: ModalProperties; properties: ModalProperties;
shown: boolean; 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)); return new Modal(ModalFunctions.warpProperties(data));
} }
class InputModalProperties extends ModalProperties { export class InputModalProperties extends ModalProperties {
maxLength?: number; maxLength?: number;
field_title?: string; field_title?: string;
@ -310,7 +310,7 @@ class InputModalProperties extends ModalProperties {
error_message?: string; 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 = ModalFunctions.warpProperties(props);
props.template_properties || (props.template_properties = {}); props.template_properties || (props.template_properties = {});
props.template_properties.field_title = props.field_title; props.template_properties.field_title = props.field_title;
@ -370,7 +370,7 @@ function createInputModal(headMessage: BodyCreator, question: BodyCreator, valid
return modal; 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 = ModalFunctions.warpProperties(props);
(props.template_properties || (props.template_properties = {})).header_class = "modal-header-error"; (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; 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 = ModalFunctions.warpProperties(props);
(props.template_properties || (props.template_properties = {})).header_class = "modal-header-info"; (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; 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; min_value?: number;
max_value?: number; max_value?: number;
initial_value?: number; initial_value?: number;
@ -8,11 +10,11 @@ interface SliderOptions {
value_field?: JQuery | JQuery[]; value_field?: JQuery | JQuery[];
} }
interface Slider { export interface Slider {
value(value?: number) : number; value(value?: number) : number;
} }
function sliderfy(slider: JQuery, options?: SliderOptions) : Slider { export function sliderfy(slider: JQuery, options?: SliderOptions) : Slider {
options = Object.assign( { options = Object.assign( {
initial_value: 0, initial_value: 0,
min_value: 0, min_value: 0,
@ -30,7 +32,7 @@ function sliderfy(slider: JQuery, options?: SliderOptions) : Slider {
throw "invalid step size"; 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 filler = slider.find(".filler");
const thumb = slider.find(".thumb"); const thumb = slider.find(".thumb");
const tooltip_text = slider.find(".tooltip a"); const tooltip_text = slider.find(".tooltip a");

View file

@ -1,13 +1,12 @@
/// <reference path="../../i18n/localize.ts" /> declare global {
interface JQuery<TElement = HTMLElement> {
interface JQuery<TElement = HTMLElement> {
asTabWidget(copy?: boolean) : JQuery<TElement>; asTabWidget(copy?: boolean) : JQuery<TElement>;
tabify(copy?: boolean) : this; tabify(copy?: boolean) : this;
changeElementType(type: string) : JQuery<TElement>; changeElementType(type: string) : JQuery<TElement>;
}
} }
if(typeof (customElements) !== "undefined") { if(typeof (customElements) !== "undefined") {
try { try {
class X_Tab extends HTMLElement {} class X_Tab extends HTMLElement {}
@ -26,7 +25,7 @@ if(typeof (customElements) !== "undefined") {
console.warn(tr("Could not defied tab customElements!")); console.warn(tr("Could not defied tab customElements!"));
} }
var TabFunctions = { export const TabFunctions = {
tabify(template: JQuery, copy: boolean = true) : JQuery { tabify(template: JQuery, copy: boolean = true) : JQuery {
console.log("Tabify: copy=" + copy); console.log("Tabify: copy=" + copy);
console.log(template); 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" /> import {ConnectionHandler, DisconnectReason} from "tc-shared/ConnectionHandler";
/// <reference path="../modal/ModalSettings.ts" /> import {createErrorModal, createInfoModal, createInputModal} from "tc-shared/ui/elements/Modal";
/// <reference path="../modal/ModalBanList.ts" /> import {manager, Sound} from "tc-shared/sound/Sounds";
/* import {default_recorder} from "tc-shared/voice/RecorderProfile";
client_output_hardware Value: '1' import {Settings, settings} from "tc-shared/settings";
client_output_muted Value: '0' import {spawnSettingsModal} from "tc-shared/ui/modal/ModalSettings";
client_outputonly_muted Value: '0' 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' export let control_bar: ControlBar; /* global variable to access the control bar */
client_input_muted Value: '0' export function set_control_bar(bar: ControlBar) { control_bar = bar; }
client_away Value: '0' export type MicrophoneState = "disabled" | "muted" | "enabled";
client_away_message Value: '' export type HeadphoneState = "muted" | "enabled";
*/ export type AwayState = "away-global" | "away" | "online";
export class ControlBar {
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 {
private _button_away_active: AwayState; private _button_away_active: AwayState;
private _button_microphone: MicrophoneState; private _button_microphone: MicrophoneState;
private _button_speakers: HeadphoneState; private _button_speakers: HeadphoneState;
@ -363,10 +379,10 @@ class ControlBar {
private on_toggle_microphone() { private on_toggle_microphone() {
if(this._button_microphone === "disabled" || this._button_microphone === "muted") { if(this._button_microphone === "disabled" || this._button_microphone === "muted") {
this.button_microphone = "enabled"; this.button_microphone = "enabled";
sound.manager.play(Sound.MICROPHONE_ACTIVATED); manager.play(Sound.MICROPHONE_ACTIVATED);
} else { } else {
this.button_microphone = "muted"; this.button_microphone = "muted";
sound.manager.play(Sound.MICROPHONE_MUTED); manager.play(Sound.MICROPHONE_MUTED);
} }
if(this.connection_handler) { if(this.connection_handler) {
@ -384,10 +400,10 @@ class ControlBar {
private on_toggle_sound() { private on_toggle_sound() {
if(this._button_speakers === "muted") { if(this._button_speakers === "muted") {
this.button_speaker = "enabled"; this.button_speaker = "enabled";
sound.manager.play(Sound.SOUND_ACTIVATED); manager.play(Sound.SOUND_ACTIVATED);
} else { } else {
this.button_speaker = "muted"; this.button_speaker = "muted";
sound.manager.play(Sound.SOUND_MUTED); manager.play(Sound.SOUND_MUTED);
} }
if(this.connection_handler) { if(this.connection_handler) {
@ -421,20 +437,20 @@ class ControlBar {
} }
private on_open_settings() { private on_open_settings() {
Modals.spawnSettingsModal(); spawnSettingsModal();
} }
private on_open_connect() { private on_open_connect() {
if(this.connection_handler) if(this.connection_handler)
this.connection_handler.cancel_reconnect(true); this.connection_handler.cancel_reconnect(true);
Modals.spawnConnectModal({}, { spawnConnectModal({}, {
url: "ts.TeaSpeak.de", url: "ts.TeaSpeak.de",
enforce: false enforce: false
}); });
} }
private on_open_connect_new_tab() { private on_open_connect_new_tab() {
Modals.spawnConnectModal({ spawnConnectModal({
default_connect_new_tab: true default_connect_new_tab: true
}, { }, {
url: "ts.TeaSpeak.de", url: "ts.TeaSpeak.de",
@ -470,7 +486,7 @@ class ControlBar {
this.connection_handler.handleDisconnect(DisconnectReason.REQUESTED); //TODO message? this.connection_handler.handleDisconnect(DisconnectReason.REQUESTED); //TODO message?
this.update_connection_state(); this.update_connection_state();
this.connection_handler.sound.play(Sound.CONNECTION_DISCONNECTED); 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() { private on_token_use() {
@ -483,7 +499,7 @@ class ControlBar {
createInfoModal(tr("Use token"), tr("Toke successfully used!")).open(); createInfoModal(tr("Use token"), tr("Toke successfully used!")).open();
}).catch(error => { }).catch(error => {
//TODO tr //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(); }).open();
} }
@ -497,7 +513,7 @@ class ControlBar {
button.addClass("activated"); button.addClass("activated");
setTimeout(() => { setTimeout(() => {
if(this.connection_handler) if(this.connection_handler)
Modals.spawnPermissionEdit(this.connection_handler).open(); spawnPermissionEdit(this.connection_handler).open();
else else
createErrorModal(tr("You have to be connected"), tr("You have to be connected!")).open(); createErrorModal(tr("You have to be connected"), tr("You have to be connected!")).open();
button.removeClass("activated"); button.removeClass("activated");
@ -508,7 +524,7 @@ class ControlBar {
if(!this.connection_handler.serverConnection) return; if(!this.connection_handler.serverConnection) return;
if(this.connection_handler.permissions.neededPermission(PermissionType.B_CLIENT_BAN_LIST).granted(1)) { if(this.connection_handler.permissions.neededPermission(PermissionType.B_CLIENT_BAN_LIST).granted(1)) {
Modals.openBanList(this.connection_handler); openBanList(this.connection_handler);
} else { } else {
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to view the ban list")).open(); 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); this.connection_handler.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
@ -516,7 +532,7 @@ class ControlBar {
} }
private on_bookmark_server_add() { private on_bookmark_server_add() {
bookmarks.add_current_server(); add_current_server();
} }
update_bookmark_status() { update_bookmark_status() {
@ -530,13 +546,13 @@ class ControlBar {
let tag_bookmark = this.htmlTag.find(".btn_bookmark > .dropdown"); let tag_bookmark = this.htmlTag.find(".btn_bookmark > .dropdown");
tag_bookmark.find(".bookmark, .directory").remove(); tag_bookmark.find(".bookmark, .directory").remove();
const build_entry = (bookmark: bookmarks.DirectoryBookmark | bookmarks.Bookmark) => { const build_entry = (bookmark: DirectoryBookmark | Bookmark) => {
if(bookmark.type == bookmarks.BookmarkType.ENTRY) { if(bookmark.type == BookmarkType.ENTRY) {
const mark = <bookmarks.Bookmark>bookmark; const mark = <Bookmark>bookmark;
const bookmark_connect = (new_tab: boolean) => { const bookmark_connect = (new_tab: boolean) => {
this.htmlTag.find(".btn_bookmark").find(".dropdown").removeClass("displayed"); //FIXME Not working 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") return $.spawn("div")
@ -580,7 +596,7 @@ class ControlBar {
}) })
) )
} else { } else {
const mark = <bookmarks.DirectoryBookmark>bookmark; const mark = <DirectoryBookmark>bookmark;
const container = $.spawn("div").addClass("sub-menu dropdown"); const container = $.spawn("div").addClass("sub-menu dropdown");
const result = $.spawn("div") 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); const entry = build_entry(bookmark);
tag_bookmark.append(entry); tag_bookmark.append(entry);
} }
} }
private on_bookmark_manage() { private on_bookmark_manage() {
Modals.spawnBookmarkModal(); spawnBookmarkModal();
} }
private on_open_query_create() { private on_open_query_create() {
if(this.connection_handler.permissions.neededPermission(PermissionType.B_CLIENT_CREATE_MODIFY_SERVERQUERY_LOGIN).granted(1)) { 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 { } else {
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to create a server query login")).open(); 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); this.connection_handler.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
@ -630,7 +646,7 @@ class ControlBar {
private on_open_query_manage() { private on_open_query_manage() {
if(this.connection_handler && this.connection_handler.connected) { if(this.connection_handler && this.connection_handler.connected) {
Modals.spawnQueryManage(this.connection_handler); spawnQueryManage(this.connection_handler);
} else { } else {
createErrorModal(tr("You have to be connected"), tr("You have to be connected!")).open(); 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() { private on_open_playlist_manage() {
if(this.connection_handler && this.connection_handler.connected) { if(this.connection_handler && this.connection_handler.connected) {
Modals.spawnPlaylistManage(this.connection_handler); spawnPlaylistManage(this.connection_handler);
} else { } else {
createErrorModal(tr("You have to be connected"), tr("You have to be connected to use this function!")).open(); createErrorModal(tr("You have to be connected"), tr("You have to be connected to use this function!")).open();
} }

View file

@ -1,7 +1,34 @@
namespace top_menu { import {Icon, IconManager} from "tc-shared/FileManager";
export interface HRItem { } 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 MenuItem { export interface HRItem { }
export interface MenuItem {
append_item(label: string): MenuItem; append_item(label: string): MenuItem;
append_hr(): HRItem; append_hr(): HRItem;
delete_item(item: MenuItem | HRItem); delete_item(item: MenuItem | HRItem);
@ -12,9 +39,9 @@ namespace top_menu {
visible(value?: boolean) : boolean; visible(value?: boolean) : boolean;
disabled(value?: boolean) : boolean; disabled(value?: boolean) : boolean;
click(callback: () => any) : this; click(callback: () => any) : this;
} }
export interface MenuBarDriver { export interface MenuBarDriver {
initialize(); initialize();
append_item(label: string) : MenuItem; append_item(label: string) : MenuItem;
@ -22,18 +49,18 @@ namespace top_menu {
items() : MenuItem[]; items() : MenuItem[];
flush_changes(); flush_changes();
} }
let _driver: MenuBarDriver; let _driver: MenuBarDriver;
export function driver() : MenuBarDriver { export function driver() : MenuBarDriver {
return _driver; return _driver;
} }
export function set_driver(driver: MenuBarDriver) { export function set_driver(driver: MenuBarDriver) {
_driver = driver; _driver = driver;
} }
export interface NativeActions { export interface NativeActions {
open_dev_tools(); open_dev_tools();
reload_page(); reload_page();
@ -43,11 +70,11 @@ namespace top_menu {
quit(); quit();
show_dev_tools(): boolean; show_dev_tools(): boolean;
} }
export let native_actions: NativeActions; export let native_actions: NativeActions;
namespace html { namespace html {
class HTMLHrItem implements top_menu.HRItem { class HTMLHrItem implements HRItem {
readonly html_tag: JQuery; readonly html_tag: JQuery;
constructor() { constructor() {
@ -55,7 +82,7 @@ namespace top_menu {
} }
} }
class HTMLMenuItem implements top_menu.MenuItem { class HTMLMenuItem implements MenuItem {
readonly html_tag: JQuery; readonly html_tag: JQuery;
readonly _label_tag: JQuery; readonly _label_tag: JQuery;
readonly _label_icon_tag: JQuery; readonly _label_icon_tag: JQuery;
@ -99,7 +126,7 @@ namespace top_menu {
this.html_tag.append(this._submenu_tag); 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"); const item = new HTMLMenuItem(label, "side");
this._items.push(item); this._items.push(item);
this._submenu_tag.append(item.html_tag); this._submenu_tag.append(item.html_tag);
@ -114,7 +141,7 @@ namespace top_menu {
return item; return item;
} }
delete_item(item: top_menu.MenuItem | top_menu.HRItem) { delete_item(item: MenuItem | HRItem) {
this._items.remove(item); this._items.remove(item);
(item as any).html_tag.detach(); (item as any).html_tag.detach();
this.html_tag.toggleClass('sub-entries', this._items.length > 0); this.html_tag.toggleClass('sub-entries', this._items.length > 0);
@ -128,7 +155,7 @@ namespace top_menu {
return value; return value;
} }
items(): (top_menu.MenuItem | top_menu.HRItem)[] { items(): (MenuItem | HRItem)[] {
return this._items; return this._items;
} }
@ -178,7 +205,7 @@ namespace top_menu {
this.html_tag = $.spawn("div").addClass("top-menu-bar"); 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"); const item = new HTMLMenuItem(label, "down");
this._items.push(item); this._items.push(item);
@ -209,7 +236,7 @@ namespace top_menu {
return undefined; return undefined;
} }
items(): top_menu.MenuItem[] { items(): MenuItem[] {
return this._items; return this._items;
} }
@ -219,15 +246,15 @@ namespace top_menu {
$("#top-menu-bar").replaceWith(this.html_tag); $("#top-menu-bar").replaceWith(this.html_tag);
} }
} }
} }
let _items_bookmark: { let _items_bookmark: {
root: MenuItem, root: MenuItem,
manage: MenuItem, manage: MenuItem,
add_current: MenuItem add_current: MenuItem
}; };
export function rebuild_bookmarks() { export function rebuild_bookmarks() {
if(!_items_bookmark) { if(!_items_bookmark) {
_items_bookmark = { _items_bookmark = {
root: driver().append_item(tr("Favorites")), root: driver().append_item(tr("Favorites")),
@ -237,11 +264,11 @@ namespace top_menu {
}; };
_items_bookmark.manage = _items_bookmark.root.append_item(tr("Manage bookmarks")); _items_bookmark.manage = _items_bookmark.root.append_item(tr("Manage bookmarks"));
_items_bookmark.manage.icon("client-bookmark_manager"); _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 = _items_bookmark.root.append_item(tr("Add current server to bookmarks"));
_items_bookmark.add_current.icon('client-bookmark_add'); _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]}; _state_updater["bookmarks.ac"] = { item: _items_bookmark.add_current, conditions: [condition_connected]};
} }
@ -250,9 +277,9 @@ namespace top_menu {
}); });
_items_bookmark.root.append_hr(); _items_bookmark.root.append_hr();
const build_bookmark = (root: MenuItem, entry: bookmarks.DirectoryBookmark | bookmarks.Bookmark) => { const build_bookmark = (root: MenuItem, entry: DirectoryBookmark | Bookmark) => {
if(entry.type == bookmarks.BookmarkType.DIRECTORY) { if(entry.type == BookmarkType.DIRECTORY) {
const directory = entry as bookmarks.DirectoryBookmark; const directory = entry as DirectoryBookmark;
const item = root.append_item(directory.display_name); const item = root.append_item(directory.display_name);
item.icon('client-folder'); item.icon('client-folder');
for(const entry of directory.content) for(const entry of directory.content)
@ -260,21 +287,21 @@ namespace top_menu {
if(directory.content.length == 0) if(directory.content.length == 0)
item.disabled(true); item.disabled(true);
} else { } else {
const bookmark = entry as bookmarks.Bookmark; const bookmark = entry as Bookmark;
const item = root.append_item(bookmark.display_name); const item = root.append_item(bookmark.display_name);
item.icon(IconManager.load_cached_icon(bookmark.last_icon_id || 0)); 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); build_bookmark(_items_bookmark.root, entry);
driver().flush_changes(); driver().flush_changes();
} }
/* will be called on connection handler change or on client connect state or mic state change etc... */ /* will be called on connection handler change or on client connect state or mic state change etc... */
let _state_updater: {[key: string]:{ item: MenuItem; conditions: (() => boolean)[], update_handler?: (item: MenuItem) => any }} = {}; let _state_updater: {[key: string]:{ item: MenuItem; conditions: (() => boolean)[], update_handler?: (item: MenuItem) => any }} = {};
export function update_state() { export function update_state() {
for(const _key of Object.keys(_state_updater)) { for(const _key of Object.keys(_state_updater)) {
const item = _state_updater[_key]; const item = _state_updater[_key];
if(item.update_handler) { if(item.update_handler) {
@ -290,28 +317,28 @@ namespace top_menu {
item.item.disabled(!enabled); item.item.disabled(!enabled);
} }
driver().flush_changes(); driver().flush_changes();
} }
const condition_connected = () => { const condition_connected = () => {
const scon = server_connections ? server_connections.active_connection_handler() : undefined; const scon = server_connections ? server_connections.active_connection_handler() : undefined;
return scon && scon.connected; return scon && scon.connected;
}; };
declare namespace native { declare namespace native {
export function initialize(); export function initialize();
} }
export function initialize() { export function initialize() {
const driver = top_menu.driver(); const driver_ = driver();
driver.initialize(); driver_.initialize();
/* build connection */ /* build connection */
let item: MenuItem; 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 = menu.append_item(tr("Connect to a server"));
item.icon('client-connect'); item.icon('client-connect');
item.click(() => Modals.spawnConnectModal({})); item.click(() => spawnConnectModal({}));
const do_disconnect = (handlers: ConnectionHandler[]) => { const do_disconnect = (handlers: ConnectionHandler[]) => {
for(const handler of handlers) { for(const handler of handlers) {
@ -319,7 +346,7 @@ namespace top_menu {
handler.handleDisconnect(DisconnectReason.REQUESTED); //TODO message? handler.handleDisconnect(DisconnectReason.REQUESTED); //TODO message?
server_connections.active_connection_handler().serverConnection.disconnect(); server_connections.active_connection_handler().serverConnection.disconnect();
handler.sound.play(Sound.CONNECTION_DISCONNECTED); handler.sound.play(Sound.CONNECTION_DISCONNECTED);
handler.log.log(log.server.Type.DISCONNECTED, {}); handler.log.log(slog.Type.DISCONNECTED, {});
} }
control_bar.update_connection_state(); control_bar.update_connection_state();
update_state(); update_state();
@ -343,7 +370,7 @@ namespace top_menu {
return true; return true;
}}; }};
if(!app.is_web()) { if(loader.version().type !== "web") {
menu.append_hr(); menu.append_hr();
item = menu.append_item(tr("Quit")); item = menu.append_item(tr("Quit"));
@ -356,45 +383,45 @@ namespace top_menu {
} }
if(false) { if(false) {
const menu = driver.append_item(tr("Self")); const menu = driver_.append_item(tr("Self"));
/* Microphone | Sound | Away */ /* 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 = menu.append_item(tr("Server Groups"));
item.icon("client-permission_server_groups"); item.icon("client-permission_server_groups");
item.click(() => { 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]}; _state_updater["permission.sg"] = { item: item, conditions: [condition_connected]};
item = menu.append_item(tr("Client Permissions")); item = menu.append_item(tr("Client Permissions"));
item.icon("client-permission_client"); item.icon("client-permission_client");
item.click(() => { 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]}; _state_updater["permission.clp"] = { item: item, conditions: [condition_connected]};
item = menu.append_item(tr("Channel Client Permissions")); item = menu.append_item(tr("Channel Client Permissions"));
item.icon("client-permission_client"); item.icon("client-permission_client");
item.click(() => { 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]}; _state_updater["permission.chclp"] = { item: item, conditions: [condition_connected]};
item = menu.append_item(tr("Channel Groups")); item = menu.append_item(tr("Channel Groups"));
item.icon("client-permission_channel"); item.icon("client-permission_channel");
item.click(() => { 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]}; _state_updater["permission.cg"] = { item: item, conditions: [condition_connected]};
item = menu.append_item(tr("Channel Permissions")); item = menu.append_item(tr("Channel Permissions"));
item.icon("client-permission_channel"); item.icon("client-permission_channel");
item.click(() => { 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]}; _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(); createInfoModal(tr("Use token"), tr("Toke successfully used!")).open();
}).catch(error => { }).catch(error => {
//TODO tr //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(); }).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")); item = menu.append_item(tr("Manage Playlists"));
@ -451,7 +478,7 @@ namespace top_menu {
const scon = server_connections.active_connection_handler(); const scon = server_connections.active_connection_handler();
if(scon && scon.connected) { if(scon && scon.connected) {
if(scon.permissions.neededPermission(PermissionType.B_CLIENT_BAN_LIST).granted(1)) { if(scon.permissions.neededPermission(PermissionType.B_CLIENT_BAN_LIST).granted(1)) {
Modals.openBanList(scon); openBanList(scon);
} else { } else {
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to view the ban list")).open(); 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); scon.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
@ -468,7 +495,7 @@ namespace top_menu {
const scon = server_connections.active_connection_handler(); const scon = server_connections.active_connection_handler();
if(scon && scon.connected) { 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)) { 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 { } else {
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to view the server query list")).open(); 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); scon.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
@ -485,7 +512,7 @@ namespace top_menu {
const scon = server_connections.active_connection_handler(); const scon = server_connections.active_connection_handler();
if(scon && scon.connected) { 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)) { 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 { } else {
createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to create a server query login")).open(); 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); scon.sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
@ -499,13 +526,13 @@ namespace top_menu {
item = menu.append_item(tr("Settings")); item = menu.append_item(tr("Settings"));
item.icon("client-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 = menu.append_item(tr("Check for updates"));
item.click(() => native_actions.check_native_update()); item.click(() => native_actions.check_native_update());
@ -519,7 +546,7 @@ namespace top_menu {
item = menu.append_item(tr("Visit TeaSpeak forum")); item = menu.append_item(tr("Visit TeaSpeak forum"));
item.click(() => window.open('https://forum.teaspeak.de/', '_blank')); 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(); menu.append_hr();
item = menu.append_item(tr("Open developer tools")); item = menu.append_item(tr("Open developer tools"));
item.click(() => native_actions.open_dev_tools()); item.click(() => native_actions.open_dev_tools());
@ -529,13 +556,19 @@ namespace top_menu {
} }
menu.append_hr(); menu.append_hr();
item = menu.append_item(app.is_web() ? tr("About TeaWeb") : tr("About TeaClient")); item = menu.append_item(loader.version().type === "web" ? tr("About TeaWeb") : tr("About TeaClient"));
item.click(() => Modals.spawnAbout()) item.click(() => spawnAbout())
} }
update_state(); update_state();
}
/* default is HTML, the client will override this */
set_driver(html.HTMLMenuBarDriver.instance());
} }
/* 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, GENERAL,
SERVER, SERVER,
CHANNEL, CHANNEL,
@ -6,15 +12,14 @@ enum ChatType {
} }
declare const xbbcode: any; declare const xbbcode: any;
namespace MessageHelper { export function htmlEscape(message: string) : string[] {
export function htmlEscape(message: string) : string[] {
const div = document.createElement('div'); const div = document.createElement('div');
div.innerText = message; div.innerText = message;
message = div.innerHTML; message = div.innerHTML;
return message.replace(/ /g, '&nbsp;').split(/<br>/); return message.replace(/ /g, '&nbsp;').split(/<br>/);
} }
export function formatElement(object: any, escape_html: boolean = true) : JQuery[] { export function formatElement(object: any, escape_html: boolean = true) : JQuery[] {
if($.isArray(object)) { if($.isArray(object)) {
let result = []; let result = [];
for(let element of object) for(let element of object)
@ -34,9 +39,9 @@ namespace MessageHelper {
else if(typeof(object) === "undefined") return formatElement("<undefined>"); else if(typeof(object) === "undefined") return formatElement("<undefined>");
else if(typeof(object) === "number") return [$.spawn("a").text(object)]; else if(typeof(object) === "number") return [$.spawn("a").text(object)];
return formatElement("<unknown object type " + typeof object + ">"); return formatElement("<unknown object type " + typeof object + ">");
} }
export function formatMessage(pattern: string, ...objects: any[]) : JQuery[] { export function formatMessage(pattern: string, ...objects: any[]) : JQuery[] {
let begin = 0, found = 0; let begin = 0, found = 0;
let result: JQuery[] = []; let result: JQuery[] = [];
@ -88,16 +93,16 @@ namespace MessageHelper {
} while(found++); } while(found++);
return result; return result;
} }
//TODO: Remove this (only legacy) //TODO: Remove this (only legacy)
export function bbcode_chat(message: string) : JQuery[] { export function bbcode_chat(message: string) : JQuery[] {
return messages.formatter.bbcode.format(message, { return bbcode.format(message, {
is_chat_message: true is_chat_message: true
}); });
} }
export namespace network { export namespace network {
export const KB = 1024; export const KB = 1024;
export const MB = 1024 * KB; export const MB = 1024 * KB;
export const GB = 1024 * MB; export const GB = 1024 * MB;
@ -150,16 +155,16 @@ namespace MessageHelper {
} }
return result; return result;
} }
} }
export const K = 1000; export const K = 1000;
export const M = 1000 * K; export const M = 1000 * K;
export const G = 1000 * M; export const G = 1000 * M;
export const T = 1000 * G; export const T = 1000 * G;
export function format_number(value: number, options?: { export function format_number(value: number, options?: {
time?: string, time?: string,
unit?: string unit?: string
}) { }) {
options = Object.assign(options || {}, {}); options = Object.assign(options || {}, {});
let points = value.toFixed(0).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'); let points = value.toFixed(0).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
@ -184,15 +189,15 @@ namespace MessageHelper {
if(unit && options.time) if(unit && options.time)
unit = unit + "/" + options.time; unit = unit + "/" + options.time;
return points + " " + (options.unit || "") + (unit ? (" / " + v.toFixed(2) + " " + unit) : ""); return points + " " + (options.unit || "") + (unit ? (" / " + v.toFixed(2) + " " + unit) : "");
} }
export const TIME_SECOND = 1000; export const TIME_SECOND = 1000;
export const TIME_MINUTE = 60 * TIME_SECOND; export const TIME_MINUTE = 60 * TIME_SECOND;
export const TIME_HOUR = 60 * TIME_MINUTE; export const TIME_HOUR = 60 * TIME_MINUTE;
export const TIME_DAY = 24 * TIME_HOUR; export const TIME_DAY = 24 * TIME_HOUR;
export const TIME_WEEK = 7 * TIME_DAY; export const TIME_WEEK = 7 * TIME_DAY;
export function format_time(time: number, default_value: string) { export function format_time(time: number, default_value: string) {
let result = ""; let result = "";
if(time > TIME_WEEK) { if(time > TIME_WEEK) {
const amount = Math.floor(time / TIME_WEEK); const amount = Math.floor(time / TIME_WEEK);
@ -225,10 +230,10 @@ namespace MessageHelper {
} }
return result.length > 0 ? result.substring(1) : default_value; return result.length > 0 ? result.substring(1) : default_value;
} }
let _icon_size_style: JQuery<HTMLStyleElement>; let _icon_size_style: JQuery<HTMLStyleElement>;
export function set_icon_size(size: string) { export function set_icon_size(size: string) {
if(!_icon_size_style) if(!_icon_size_style)
_icon_size_style = $.spawn("style").appendTo($("#style")); _icon_size_style = $.spawn("style").appendTo($("#style"));
@ -238,13 +243,12 @@ namespace MessageHelper {
" width: " + size + "!important;\n" + " width: " + size + "!important;\n" +
"}\n" "}\n"
); );
} }
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "icon size init", name: "icon size init",
function: async () => { 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 priority: 10
}); });
}

View file

@ -1,16 +1,26 @@
/* the bar on the right with the chats (Channel & Client) */ /* the bar on the right with the chats (Channel & Client) */
namespace chat { import {ClientEntry, MusicClientEntry} from "tc-shared/ui/client";
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; import {ConnectionHandler} from "tc-shared/ConnectionHandler";
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; 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";
export enum InfoFrameMode { declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
export enum InfoFrameMode {
NONE = "none", NONE = "none",
CHANNEL_CHAT = "channel_chat", CHANNEL_CHAT = "channel_chat",
PRIVATE_CHAT = "private_chat", PRIVATE_CHAT = "private_chat",
CLIENT_INFO = "client_info", CLIENT_INFO = "client_info",
MUSIC_BOT = "music_bot" MUSIC_BOT = "music_bot"
} }
export class InfoFrame { export class InfoFrame {
private readonly handle: Frame; private readonly handle: Frame;
private _html_tag: JQuery; private _html_tag: JQuery;
private _mode: InfoFrameMode; private _mode: InfoFrameMode;
@ -69,7 +79,7 @@ namespace chat {
const bot = this.handle.music_info().current_bot(); const bot = this.handle.music_info().current_bot();
if(!bot) return; 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._button_song_add = this._html_tag.find(".bot-add-song").on('click', event => {
this.handle.music_info().events.fire("action_song_add"); this.handle.music_info().events.fire("action_song_add");
@ -157,7 +167,7 @@ namespace chat {
this.update_channel_limit(channel, html_limit_tag); this.update_channel_limit(channel, html_limit_tag);
} else if(channel_tree && current_channel_id > 0) { } 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) { } else if(channel_tree && current_channel_id == 0) {
const server = this.handle.handle.channelTree.server; const server = this.handle.handle.channelTree.server;
if(server.properties.virtualserver_icon_id != 0) if(server.properties.virtualserver_icon_id != 0)
@ -253,17 +263,17 @@ namespace chat {
//TODO? //TODO?
} }
} }
} }
export enum FrameContent { export enum FrameContent {
NONE, NONE,
PRIVATE_CHAT, PRIVATE_CHAT,
CHANNEL_CHAT, CHANNEL_CHAT,
CLIENT_INFO, CLIENT_INFO,
MUSIC_BOT MUSIC_BOT
} }
export class Frame { export class Frame {
readonly handle: ConnectionHandler; readonly handle: ConnectionHandler;
private _info_frame: InfoFrame; private _info_frame: InfoFrame;
private _html_tag: JQuery; private _html_tag: JQuery;
@ -274,7 +284,7 @@ namespace chat {
private _conversations: PrivateConverations; private _conversations: PrivateConverations;
private _client_info: ClientInfo; private _client_info: ClientInfo;
private _music_info: MusicInfo; private _music_info: MusicInfo;
private _channel_conversations: channel.ConversationManager; private _channel_conversations: ConversationManager;
constructor(handle: ConnectionHandler) { constructor(handle: ConnectionHandler) {
this.handle = handle; this.handle = handle;
@ -282,7 +292,7 @@ namespace chat {
this._content_type = FrameContent.NONE; this._content_type = FrameContent.NONE;
this._info_frame = new InfoFrame(this); this._info_frame = new InfoFrame(this);
this._conversations = new PrivateConverations(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._client_info = new ClientInfo(this);
this._music_info = new MusicInfo(this); this._music_info = new MusicInfo(this);
@ -335,7 +345,7 @@ namespace chat {
return this._conversations; return this._conversations;
} }
channel_conversations() : channel.ConversationManager { channel_conversations() : ConversationManager {
return this._channel_conversations; return this._channel_conversations;
} }
@ -413,5 +423,4 @@ namespace chat {
this._info_frame.set_mode(InfoFrameMode.NONE); this._info_frame.set_mode(InfoFrameMode.NONE);
} }
} }
}
} }

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; export let server_connections: ServerConnectionManager;
export function initialize(manager: ServerConnectionManager) {
class ServerConnectionManager { server_connections = manager;
}
export class ServerConnectionManager {
private connection_handlers: ConnectionHandler[] = []; private connection_handlers: ConnectionHandler[] = [];
private active_handler: ConnectionHandler | undefined; 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 html_tag: JQuery<HTMLElement>;
readonly client: ConnectionHandler; readonly client: ConnectionHandler;

View file

@ -1,9 +1,10 @@
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;
export function preview_image(url: string, original_url: string) { let preview_overlay: JQuery<HTMLDivElement>;
let container_image: JQuery<HTMLDivElement>;
let button_open_in_browser: JQuery;
export function preview_image(url: string, original_url: string) {
if(!preview_overlay) return; if(!preview_overlay) return;
container_image.empty(); container_image.empty();
@ -15,9 +16,9 @@ namespace image_preview {
preview_overlay.removeClass("hidden"); preview_overlay.removeClass("hidden");
button_open_in_browser.show(); button_open_in_browser.show();
} }
export function preview_image_tag(tag: JQuery) { export function preview_image_tag(tag: JQuery) {
if(!preview_overlay) return; if(!preview_overlay) return;
container_image.empty(); container_image.empty();
@ -25,18 +26,18 @@ namespace image_preview {
preview_overlay.removeClass("hidden"); preview_overlay.removeClass("hidden");
button_open_in_browser.hide(); button_open_in_browser.hide();
} }
export function current_url() { export function current_url() {
const image_tag = container_image.find("img"); const image_tag = container_image.find("img");
return image_tag.attr("x-original-src") || image_tag.attr("src") || ""; return image_tag.attr("x-original-src") || image_tag.attr("src") || "";
} }
export function close_preview() { export function close_preview() {
preview_overlay.addClass("hidden"); preview_overlay.addClass("hidden");
} }
loader.register_task(loader.Stage.LOADED, { loader.register_task(loader.Stage.LOADED, {
priority: 0, priority: 0,
name: "image preview init", name: "image preview init",
function: async () => { function: async () => {
@ -77,5 +78,4 @@ namespace image_preview {
win.focus(); win.focus();
}); });
} }
}); });
}

View file

@ -1,6 +1,11 @@
namespace log { import {tra} from "tc-shared/i18n/localize";
export namespace server { import {PermissionInfo} from "tc-shared/permission/PermissionManager";
export enum Type { 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_BEGIN = "connection_begin",
CONNECTION_HOSTNAME_RESOLVE = "connection_hostname_resolve", CONNECTION_HOSTNAME_RESOLVE = "connection_hostname_resolve",
CONNECTION_HOSTNAME_RESOLVE_ERROR = "connection_hostname_resolve_error", CONNECTION_HOSTNAME_RESOLVE_ERROR = "connection_hostname_resolve_error",
@ -44,9 +49,9 @@ namespace log {
RECONNECT_SCHEDULED = "reconnect_scheduled", RECONNECT_SCHEDULED = "reconnect_scheduled",
RECONNECT_EXECUTE = "reconnect_execute", RECONNECT_EXECUTE = "reconnect_execute",
RECONNECT_CANCELED = "reconnect_canceled" RECONNECT_CANCELED = "reconnect_canceled"
} }
export namespace base { export namespace base {
export type Client = { export type Client = {
client_unique_id: string; client_unique_id: string;
client_name: string; client_name: string;
@ -64,9 +69,9 @@ namespace log {
server_hostname: string; server_hostname: string;
server_port: number; server_port: number;
} }
} }
export namespace event { export namespace event {
export type GlobalMessage = { export type GlobalMessage = {
sender: base.Client; sender: base.Client;
message: string; message: string;
@ -203,15 +208,15 @@ namespace log {
invoker: base.Client; invoker: base.Client;
} }
} }
export type LogMessage = { export type LogMessage = {
type: Type; type: Type;
timestamp: number; timestamp: number;
data: any; data: any;
} }
export interface TypeInfo { export interface TypeInfo {
"connection_begin" : event.ConnectBegin; "connection_begin" : event.ConnectBegin;
"global_message": event.GlobalMessage; "global_message": event.GlobalMessage;
@ -250,23 +255,22 @@ namespace log {
"channel_delete": event.ChannelDelete; "channel_delete": event.ChannelDelete;
"disconnected": any; "disconnected": any;
} }
export type MessageBuilderOptions = {}; 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>} = { export const MessageBuilders: {[key: string]: MessageBuilder<any>} = {
"error_custom": (data: event.ErrorCustom, options) => { "error_custom": (data: event.ErrorCustom, options) => {
return [$.spawn("div").addClass("log-error").text(data.message)] return [$.spawn("div").addClass("log-error").text(data.message)]
} }
}; };
}
export class ServerLog { export class ServerLog {
private readonly handle: ConnectionHandler; private readonly handle: ConnectionHandler;
private history_length: number = 100; private history_length: number = 100;
private _log: server.LogMessage[] = []; private _log: LogMessage[] = [];
private _html_tag: JQuery; private _html_tag: JQuery;
private _log_container: JQuery; private _log_container: JQuery;
private auto_follow: boolean; /* automatic scroll to bottom */ 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 = { const event = {
data: data, data: data,
timestamp: Date.now(), timestamp: Date.now(),
@ -318,7 +322,7 @@ namespace log {
private _scroll_task: number; private _scroll_task: number;
private append_log(message: server.LogMessage) { private append_log(message: LogMessage) {
let container = $.spawn("div").addClass("log-message"); let container = $.spawn("div").addClass("log-message");
/* build timestamp */ /* build timestamp */
@ -333,9 +337,9 @@ namespace log {
/* build message data */ /* build message data */
{ {
const builder = server.MessageBuilders[message.type]; const builder = MessageBuilders[message.type];
if(!builder) { 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 { } else {
const elements = builder(message.data, {}); const elements = builder(message.data, {});
if(!elements || elements.length == 0) if(!elements || elements.length == 0)
@ -364,66 +368,62 @@ namespace log {
}, 5) as any; }, 5) as any;
} }
} }
}
} }
/* impl of the parsers */ /* impl of the parsers */
namespace log { const client_tag = (client: base.Client, braces?: boolean) => htmltags.generate_client_object({
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_unique_id: client.client_unique_id,
client_id: client.client_id, client_id: client.client_id,
client_name: client.client_name, client_name: client.client_name,
add_braces: braces add_braces: braces
}); });
const channel_tag = (channel: base.Channel, braces?: boolean) => htmltags.generate_channel_object({ const channel_tag = (channel: base.Channel, braces?: boolean) => htmltags.generate_channel_object({
channel_display_name: channel.channel_name, channel_display_name: channel.channel_name,
channel_name: channel.channel_name, channel_name: channel.channel_name,
channel_id: channel.channel_id, channel_id: channel.channel_id,
add_braces: braces add_braces: braces
}); });
MessageBuilders["connection_begin"] = (data: event.ConnectBegin, options) => { 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_resolve"] = (data: event.ConnectionHostnameResolve, options) => 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_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) => MessageHelper.formatMessage(tr("Failed to resolve hostname. Connecting to given hostname. Error: {0}"), data.message); 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_login"] = (data: event.ConnectionLogin, options) => formatMessage(tr("Logging in..."));
MessageBuilders["connection_failed"] = (data: event.ConnectionFailed, options) => MessageHelper.formatMessage(tr("Connect failed.")); MessageBuilders["connection_failed"] = (data: event.ConnectionFailed, options) => 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_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) => { 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) => { 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) => { MessageBuilders["client_view_enter"] = (data: event.ClientEnter, options) => {
if(data.reason == ViewReasonId.VREASON_SYSTEM) { if(data.reason == ViewReasonId.VREASON_SYSTEM) {
return undefined; return undefined;
} if(data.reason == ViewReasonId.VREASON_USER_ACTION) { } if(data.reason == ViewReasonId.VREASON_USER_ACTION) {
/* client appeared */ /* client appeared */
if(data.channel_from) { 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 { } 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) { } else if(data.reason == ViewReasonId.VREASON_MOVED) {
if(data.channel_from) { 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), client_tag(data.client),
channel_tag(data.channel_from), channel_tag(data.channel_from),
channel_tag(data.channel_to), channel_tag(data.channel_to),
client_tag(data.invoker) client_tag(data.invoker)
); );
} else { } 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), client_tag(data.client),
channel_tag(data.channel_to), channel_tag(data.channel_to),
client_tag(data.invoker) client_tag(data.invoker)
@ -431,7 +431,7 @@ namespace log {
} }
} else if(data.reason == ViewReasonId.VREASON_CHANNEL_KICK) { } else if(data.reason == ViewReasonId.VREASON_CHANNEL_KICK) {
if(data.channel_from) { 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), client_tag(data.client),
channel_tag(data.channel_from), channel_tag(data.channel_from),
channel_tag(data.channel_to), channel_tag(data.channel_to),
@ -439,7 +439,7 @@ namespace log {
data.message ? (" (" + data.message + ")") : "" data.message ? (" (" + data.message + ")") : ""
); );
} else { } 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), client_tag(data.client),
channel_tag(data.channel_to), channel_tag(data.channel_to),
client_tag(data.invoker), client_tag(data.invoker),
@ -448,24 +448,24 @@ namespace log {
} }
} }
return [$.spawn("div").addClass("log-error").text("Invalid view enter reason id (" + data.message + ")")]; return [$.spawn("div").addClass("log-error").text("Invalid view enter reason id (" + data.message + ")")];
}; };
MessageBuilders["client_view_move"] = (data: event.ClientMove, options) => { MessageBuilders["client_view_move"] = (data: event.ClientMove, options) => {
if(data.reason == ViewReasonId.VREASON_MOVED) { 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), client_tag(data.client),
channel_tag(data.channel_from), channel_tag(data.channel_from),
channel_tag(data.channel_to), channel_tag(data.channel_to),
client_tag(data.invoker) client_tag(data.invoker)
); );
} else if(data.reason == ViewReasonId.VREASON_USER_ACTION) { } 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), client_tag(data.client),
channel_tag(data.channel_from), channel_tag(data.channel_from),
channel_tag(data.channel_to) channel_tag(data.channel_to)
); );
} else if(data.reason == ViewReasonId.VREASON_CHANNEL_KICK) { } 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), client_tag(data.client),
channel_tag(data.channel_from), channel_tag(data.channel_from),
channel_tag(data.channel_to), channel_tag(data.channel_to),
@ -474,17 +474,17 @@ namespace log {
); );
} }
return [$.spawn("div").addClass("log-error").text("Invalid view move reason id (" + data.reason + ")")]; return [$.spawn("div").addClass("log-error").text("Invalid view move reason id (" + data.reason + ")")];
}; };
MessageBuilders["client_view_leave"] = (data: event.ClientLeave, options) => { MessageBuilders["client_view_leave"] = (data: event.ClientLeave, options) => {
if(data.reason == ViewReasonId.VREASON_USER_ACTION) { 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) { } 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) { } 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) { } 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), client_tag(data.client),
channel_tag(data.channel_from), channel_tag(data.channel_from),
client_tag(data.invoker), client_tag(data.invoker),
@ -494,59 +494,59 @@ namespace log {
let duration = "permanently"; let duration = "permanently";
if(data.ban_time) if(data.ban_time)
duration = "for " + formatDate(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), client_tag(data.client),
duration, duration,
client_tag(data.invoker), client_tag(data.invoker),
data.message ? (" (" + data.message + ")") : "" data.message ? (" (" + data.message + ")") : ""
); );
} else if(data.reason == ViewReasonId.VREASON_TIMEOUT) { } 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) { } 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 + ")")]; return [$.spawn("div").addClass("log-error").text("Invalid view leave reason id (" + data.reason + ")")];
}; };
MessageBuilders["server_welcome_message"] = (data: event.WelcomeMessage, options) => { 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) => { 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) => { MessageBuilders["client_nickname_changed"] = (data: event.ClientNicknameChanged, options) => {
if(data.own_client) { if(data.own_client) {
return MessageHelper.formatMessage(tr("Nickname successfully changed.")); return formatMessage(tr("Nickname successfully changed."));
} else { } 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);
} }
}; };
MessageBuilders["global_message"] = (data: event.GlobalMessage, options) => { MessageBuilders["global_message"] = (data: event.GlobalMessage, options) => {
return []; /* we do not show global messages within 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) => { 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) => { MessageBuilders["reconnect_canceled"] = (data: event.ReconnectCanceled, options) => {
return tra("Canceled reconnect.") return tra("Canceled reconnect.")
}; };
MessageBuilders["reconnect_execute"] = (data: event.ReconnectExecute, options) => { MessageBuilders["reconnect_execute"] = (data: event.ReconnectExecute, options) => {
return tra("Reconnecting...") return tra("Reconnecting...")
}; };
MessageBuilders["server_banned"] = (data: event.ServerBanned, options) => { MessageBuilders["server_banned"] = (data: event.ServerBanned, options) => {
let result: JQuery[]; 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.invoker.client_id > 0) {
if(data.message) 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); result = tra("You've been banned from the server by {0} for {1}. Reason: {2}", client_tag(data.invoker), time, data.message);
@ -560,7 +560,4 @@ namespace log {
} }
return result.map(e => e.addClass("log-error")); return result.map(e => e.addClass("log-error"));
}; };
}
}
}

View file

@ -1,8 +1,10 @@
namespace chat { import {Settings, settings} from "tc-shared/settings";
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; import {helpers} from "tc-shared/ui/frames/side/chat_helper";
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
export class ChatBox { declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
export class ChatBox {
private _html_tag: JQuery; private _html_tag: JQuery;
private _html_input: JQuery<HTMLDivElement>; private _html_input: JQuery<HTMLDivElement>;
private _enabled: boolean; private _enabled: boolean;
@ -263,5 +265,4 @@ namespace chat {
focus_input() { focus_input() {
this._html_input.focus(); this._html_input.focus();
} }
}
} }

View file

@ -1,5 +1,9 @@
namespace chat { import * as log from "tc-shared/log";
export namespace helpers { 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 //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; //static readonly URL_REGEX = /^(?<hostname>([a-zA-Z0-9-]+\.)+[a-zA-Z0-9-]{2,63})(?:\/(?<path>(?:[^\s?]+)?)(?:\?(?<query>\S+))?)?$/gm;
const URL_REGEX = /^(([a-zA-Z0-9-]+\.)+[a-zA-Z0-9-]{2,63})(?:\/((?:[^\s?]+)?)(?:\?(\S+))?)?$/gm; const URL_REGEX = /^(([a-zA-Z0-9-]+\.)+[a-zA-Z0-9-]{2,63})(?:\/((?:[^\s?]+)?)(?:\?(\S+))?)?$/gm;
@ -300,9 +304,9 @@ test
return a.getFullYear() === b.getFullYear(); return a.getFullYear() === b.getFullYear();
} }
} }
} }
export namespace format { export namespace format {
export namespace date { export namespace date {
export enum ColloquialFormat { export enum ColloquialFormat {
YESTERDAY, YESTERDAY,
@ -419,5 +423,4 @@ test
return result.substr(0, result.length - 1); return result.substr(0, result.length - 1);
} }
} }
}
} }

View file

@ -1,8 +1,16 @@
namespace chat { import {GroupManager} from "tc-shared/permission/GroupManager";
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; import {Frame, FrameContent} from "tc-shared/ui/frames/chat_frame";
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; 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";
export class ClientInfo { declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
export class ClientInfo {
readonly handle: Frame; readonly handle: Frame;
private _html_tag: JQuery; private _html_tag: JQuery;
private _current_client: ClientEntry | undefined; private _current_client: ClientEntry | undefined;
@ -40,7 +48,7 @@ namespace chat {
if(!this._current_client) if(!this._current_client)
return; return;
Modals.openClientInfo(this._current_client); openClientInfo(this._current_client);
}); });
this._html_tag.find('.container-avatar-edit').on('click', () => this.handle.handle.update_avatar()); this._html_tag.find('.container-avatar-edit').on('click', () => this.handle.handle.update_avatar());
} }
@ -116,7 +124,7 @@ namespace chat {
country.children().detach(); country.children().detach();
const country_code = (client ? client.properties.client_country : undefined) || "xx"; const country_code = (client ? client.properties.client_country : undefined) || "xx";
$.spawn("div").addClass("country flag-" + country_code.toLowerCase()).appendTo(country); $.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"); const version = this._html_tag.find(".client-version");
@ -271,5 +279,4 @@ namespace chat {
} }
} }
} }
}
} }

View file

@ -1,13 +1,23 @@
namespace chat { import {settings, Settings} from "tc-shared/settings";
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; import {format} from "tc-shared/ui/frames/side/chat_helper";
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; 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";
export namespace channel { declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
export type ViewEntry = { declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
export type ViewEntry = {
html_element: JQuery; html_element: JQuery;
update_timer?: number; update_timer?: number;
} }
export type MessageData = { export type MessageData = {
timestamp: number; timestamp: number;
message: string; message: string;
@ -15,10 +25,10 @@ namespace chat {
sender_name: string; sender_name: string;
sender_unique_id: string; sender_unique_id: string;
sender_database_id: number; sender_database_id: number;
} }
export type Message = MessageData & ViewEntry; export type Message = MessageData & ViewEntry;
export class Conversation { export class Conversation {
readonly handle: ConversationManager; readonly handle: ConversationManager;
readonly channel_id: number; readonly channel_id: number;
@ -151,7 +161,7 @@ namespace chat {
client_unique_id: data.sender_unique_id, client_unique_id: data.sender_unique_id,
client_id: 0 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) 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); log.error(LogCategory.CHAT, tr("Failed to delete conversation message for conversation %o: %o"), this.channel_id, error);
if(error instanceof CommandResult) if(error instanceof CommandResult)
error = error.extra_message || error.message; 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); log.debug(LogCategory.CLIENT, tr("Deleting text message %o"), message);
} }
@ -471,9 +481,9 @@ namespace chat {
this._last_messages.remove(message); this._last_messages.remove(message);
} }
} }
export class ConversationManager { export class ConversationManager {
readonly handle: Frame; readonly handle: Frame;
private _html_tag: JQuery; private _html_tag: JQuery;
@ -610,6 +620,4 @@ namespace chat {
tag.addClass("hidden"); tag.addClass("hidden");
} }
} }
}
}
} }

View file

@ -1,10 +1,18 @@
namespace chat { import {Frame, FrameContent} from "tc-shared/ui/frames/chat_frame";
import PlayerState = connection.voice.PlayerState; 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 setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
interface LoadedSongData { interface LoadedSongData {
description: string; description: string;
title: string; title: string;
url: string; url: string;
@ -13,9 +21,9 @@ namespace chat {
thumbnail?: string; thumbnail?: string;
metadata: {[key: string]: string}; metadata: {[key: string]: string};
} }
export class MusicInfo { export class MusicInfo {
readonly events: events.Registry<events.sidebar.music>; readonly events: events.Registry<events.sidebar.music>;
readonly handle: Frame; readonly handle: Frame;
@ -845,5 +853,4 @@ namespace chat {
} }
return tag; return tag;
} }
}
} }

View file

@ -1,13 +1,22 @@
/* the bar on the right with the chats (Channel & Client) */ /* the bar on the right with the chats (Channel & Client) */
namespace chat { import {settings, Settings} from "tc-shared/settings";
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; import {LogCategory} from "tc-shared/log";
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; 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";
export type PrivateConversationViewEntry = { declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
export type PrivateConversationViewEntry = {
html_tag: JQuery; html_tag: JQuery;
} }
export type PrivateConversationMessageData = { export type PrivateConversationMessageData = {
message_id: string; message_id: string;
message: string; message: string;
sender: "self" | "partner"; sender: "self" | "partner";
@ -17,21 +26,21 @@ namespace chat {
sender_client_id: number; sender_client_id: number;
timestamp: number; timestamp: number;
}; };
export type PrivateConversationViewMessage = PrivateConversationMessageData & PrivateConversationViewEntry & { export type PrivateConversationViewMessage = PrivateConversationMessageData & PrivateConversationViewEntry & {
time_update_id: number; time_update_id: number;
}; };
export type PrivateConversationViewSpacer = PrivateConversationViewEntry; export type PrivateConversationViewSpacer = PrivateConversationViewEntry;
export enum PrivateConversationState { export enum PrivateConversationState {
OPEN, OPEN,
CLOSED, CLOSED,
DISCONNECTED, DISCONNECTED,
DISCONNECTED_SELF, DISCONNECTED_SELF,
} }
export type DisplayedMessage = { export type DisplayedMessage = {
timestamp: number; timestamp: number;
message: PrivateConversationViewMessage | PrivateConversationViewEntry; message: PrivateConversationViewMessage | PrivateConversationViewEntry;
@ -45,9 +54,9 @@ namespace chat {
tag_message: JQuery; tag_message: JQuery;
tag_unread: PrivateConversationViewSpacer | undefined; tag_unread: PrivateConversationViewSpacer | undefined;
tag_timepointer: PrivateConversationViewSpacer | undefined; tag_timepointer: PrivateConversationViewSpacer | undefined;
} }
export class PrivateConveration { export class PrivateConveration {
readonly handle: PrivateConverations; readonly handle: PrivateConverations;
private _html_entry_tag: JQuery; private _html_entry_tag: JQuery;
private _message_history: PrivateConversationMessageData[] = []; private _message_history: PrivateConversationMessageData[] = [];
@ -341,7 +350,7 @@ namespace chat {
client_unique_id: message.sender_unique_id, client_unique_id: message.sender_unique_id,
client_id: message.sender_client_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) avatar: this.handle.handle.handle.fileManager.avatars.generate_chat_tag({id: message.sender_client_id}, message.sender_unique_id)
}); });
if(time.next_update > 0) { if(time.next_update > 0) {
@ -653,9 +662,9 @@ namespace chat {
typing_active() { typing_active() {
return Date.now() - this._last_typing < this._typing_timeout; return Date.now() - this._last_typing < this._typing_timeout;
} }
} }
export class PrivateConverations { export class PrivateConverations {
readonly handle: Frame; readonly handle: Frame;
private _chat_box: ChatBox; private _chat_box: ChatBox;
private _html_tag: JQuery; private _html_tag: JQuery;
@ -792,7 +801,7 @@ namespace chat {
} }
} else { } else {
conv.append_error(tr("Failed to send message. Lookup the console for more details")); 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);
} }
}); });
}); });
@ -892,5 +901,4 @@ namespace chat {
tag.addClass("hidden"); tag.addClass("hidden");
} }
} }
}
} }

View file

@ -1,31 +1,37 @@
namespace htmltags { import * as log from "tc-shared/log";
let mouse_coordinates: {x: number, y: number} = {x: 0, y: 0}; 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";
function initialize() { let mouse_coordinates: {x: number, y: number} = {x: 0, y: 0};
function initialize() {
document.addEventListener('mousemove', event => { document.addEventListener('mousemove', event => {
mouse_coordinates.x = event.pageX; mouse_coordinates.x = event.pageX;
mouse_coordinates.y = event.pageY; mouse_coordinates.y = event.pageY;
}); });
} }
initialize(); initialize();
export interface ClientProperties { export interface ClientProperties {
client_id: number, client_id: number,
client_unique_id: string, client_unique_id: string,
client_name: string, client_name: string,
add_braces?: boolean, add_braces?: boolean,
client_database_id?: number; /* not yet used */ client_database_id?: number; /* not yet used */
} }
export interface ChannelProperties { export interface ChannelProperties {
channel_id: number, channel_id: number,
channel_name: string, channel_name: string,
channel_display_name?: string, channel_display_name?: string,
add_braces?: boolean add_braces?: boolean
} }
/* required for the bbcodes */ /* required for the bbcodes */
function generate_client_open(properties: ClientProperties) : string { function generate_client_open(properties: ClientProperties) : string {
let result = ""; let result = "";
/* build the opening tag: <div ...> */ /* build the opening tag: <div ...> */
@ -55,16 +61,16 @@ namespace htmltags {
result = result + ">"; result = result + ">";
return result; return result;
} }
export function generate_client(properties: ClientProperties) : string { export function generate_client(properties: ClientProperties) : string {
let result = generate_client_open(properties); let result = generate_client_open(properties);
/* content */ /* content */
{ {
if(properties.add_braces) if(properties.add_braces)
result = result + "\""; result = result + "\"";
result = result + MessageHelper.htmlEscape(properties.client_name || "undefined").join(" "); result = result + htmlEscape(properties.client_name || "undefined").join(" ");
if(properties.add_braces) if(properties.add_braces)
result = result + "\""; result = result + "\"";
} }
@ -74,14 +80,14 @@ namespace htmltags {
result += "</div>"; result += "</div>";
} }
return result; return result;
} }
export function generate_client_object(properties: ClientProperties) : JQuery { export function generate_client_object(properties: ClientProperties) : JQuery {
return $(this.generate_client(properties)); return $(this.generate_client(properties));
} }
/* required for the bbcodes */ /* required for the bbcodes */
function generate_channel_open(properties: ChannelProperties) : string { function generate_channel_open(properties: ChannelProperties) : string {
let result = ""; let result = "";
/* build the opening tag: <div ...> */ /* build the opening tag: <div ...> */
@ -98,15 +104,15 @@ namespace htmltags {
result = result + ">"; result = result + ">";
return result; return result;
} }
export function generate_channel(properties: ChannelProperties) : string { export function generate_channel(properties: ChannelProperties) : string {
let result = generate_channel_open(properties); let result = generate_channel_open(properties);
/* content */ /* content */
{ {
if(properties.add_braces) if(properties.add_braces)
result = result + "\""; 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) if(properties.add_braces)
result = result + "\""; result = result + "\"";
} }
@ -116,14 +122,14 @@ namespace htmltags {
result += "</div>"; result += "</div>";
} }
return result; return result;
} }
export function generate_channel_object(properties: ChannelProperties) : JQuery { export function generate_channel_object(properties: ChannelProperties) : JQuery {
return $(this.generate_channel(properties)); return $(this.generate_channel(properties));
} }
export namespace callbacks { export namespace callbacks {
export function callback_context_client(element: JQuery) { export function callback_context_client(element: JQuery) {
const client_id = parseInt(element.attr("client-id") || "0"); const client_id = parseInt(element.attr("client-id") || "0");
const client_unique_id = decodeURIComponent(element.attr("client-unique-id") || ""); const client_unique_id = decodeURIComponent(element.attr("client-unique-id") || "");
@ -179,9 +185,10 @@ namespace htmltags {
channel.showContextMenu(mouse_coordinates.x, mouse_coordinates.y); channel.showContextMenu(mouse_coordinates.x, mouse_coordinates.y);
return false; return false;
} }
} }
namespace bbcodes { declare const xbbcode;
namespace bbcodes {
/* the = because we sometimes get that */ /* 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; //const url_client_regex = /?client:\/\/(?<client_id>[0-9]+)\/(?<client_unique_id>[a-zA-Z0-9+=#]+)~(?<client_name>(?:[^%]|%[0-9A-Fa-f]{2})+)$/g;
const url_client_regex = /client:\/\/([0-9]+)\/([a-zA-Z0-9+=/#]+)~((?:[^%]|%[0-9A-Fa-f]{2})+)$/g; /* IDK which browsers already support group naming */ const url_client_regex = /client:\/\/([0-9]+)\/([a-zA-Z0-9+=/#]+)~((?:[^%]|%[0-9A-Fa-f]{2})+)$/g; /* IDK which browsers already support group naming */
@ -223,31 +230,7 @@ namespace htmltags {
} }
return origin_url.build_html_tag_close(layer); 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(); initialize();
}
} }

View file

@ -1,15 +1,15 @@
/// <reference path="../../ui/elements/modal.ts" /> import {createModal} from "tc-shared/ui/elements/Modal";
/// <reference path="../../ConnectionHandler.ts" /> import * as loader from "tc-loader";
/// <reference path="../../proto.ts" /> import {LogCategory} from "tc-shared/log";
import * as log from "tc-shared/log";
namespace Modals { function format_date(date: number) {
function format_date(date: number) {
const d = new Date(date); const d = new Date(date);
return ('00' + d.getDay()).substr(-2) + "." + ('00' + d.getMonth()).substr(-2) + "." + d.getFullYear() + " - " + ('00' + d.getHours()).substr(-2) + ":" + ('00' + d.getMinutes()).substr(-2); return ('00' + d.getDay()).substr(-2) + "." + ('00' + d.getMonth()).substr(-2) + "." + d.getFullYear() + " - " + ('00' + d.getHours()).substr(-2) + ":" + ('00' + d.getMinutes()).substr(-2);
} }
export function spawnAbout() { export function spawnAbout() {
const app_version = (() => { const app_version = (() => {
const version_node = document.getElementById("app_version"); const version_node = document.getElementById("app_version");
if(!version_node) return undefined; if(!version_node) return undefined;
@ -27,9 +27,9 @@ namespace Modals {
header: tr("About"), header: tr("About"),
body: () => { body: () => {
let tag = $("#tmpl_about").renderTag({ 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_ui: app_version || "in-dev",
version_timestamp: !!app_version ? format_date(Date.now()) : "--" version_timestamp: !!app_version ? format_date(Date.now()) : "--"
@ -43,7 +43,7 @@ namespace Modals {
connectModal.htmlTag.find(".modal-body").addClass("modal-about"); connectModal.htmlTag.find(".modal-body").addClass("modal-about");
connectModal.open(); connectModal.open();
if(!app.is_web()) { if(loader.version().type !== "web") {
(window as any).native.client_version().then(version => { (window as any).native.client_version().then(version => {
connectModal.htmlTag.find(".version-client").text(version); connectModal.htmlTag.find(".version-client").text(version);
}).catch(error => { }).catch(error => {
@ -51,5 +51,4 @@ namespace Modals {
connectModal.htmlTag.find(".version-client").text("unknown"); connectModal.htmlTag.find(".version-client").text("unknown");
}); });
} }
}
} }

View file

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

View file

@ -1,9 +1,13 @@
/// <reference path="../../ui/elements/modal.ts" /> import {createErrorModal, createModal} from "tc-shared/ui/elements/Modal";
/// <reference path="../../ConnectionHandler.ts" /> import {LogCategory} from "tc-shared/log";
/// <reference path="../../proto.ts" /> 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 avatar_to_uid = (id: string) => {
const buffer = new Uint8Array(id.length / 2); const buffer = new Uint8Array(id.length / 2);
for(let index = 0; index < id.length; index += 2) { for(let index = 0; index < id.length; index += 2) {
const upper_nibble = id.charCodeAt(index) - 97; const upper_nibble = id.charCodeAt(index) - 97;
@ -11,16 +15,17 @@ namespace Modals {
buffer[index / 2] = (upper_nibble << 4) | lower_nibble; buffer[index / 2] = (upper_nibble << 4) | lower_nibble;
} }
return base64_encode_ab(buffer); return base64_encode_ab(buffer);
}; };
export const human_file_size = (size: number) => { export const human_file_size = (size: number) => {
if(size < 1000) if(size < 1000)
return size + "B"; return size + "B";
const exp = Math.floor(Math.log2(size) / 10); const exp = Math.floor(Math.log2(size) / 10);
return (size / Math.pow(1024, exp)).toFixed(2) + 'KMGTPE'.charAt(exp - 1) + "iB"; return (size / Math.pow(1024, exp)).toFixed(2) + 'KMGTPE'.charAt(exp - 1) + "iB";
}; };
export function spawnAvatarList(client: ConnectionHandler) { declare const moment;
export function spawnAvatarList(client: ConnectionHandler) {
const modal = createModal({ const modal = createModal({
header: tr("Avatars"), header: tr("Avatars"),
footer: undefined, footer: undefined,
@ -158,5 +163,4 @@ namespace Modals {
button_delete.on('click', () => (callback_delete || (() => {}))()); button_delete.on('click', () => (callback_delete || (() => {}))());
setTimeout(() => update_avatar_list(), 250); setTimeout(() => update_avatar_list(), 250);
modal.open(); modal.open();
}
} }

View file

@ -2,18 +2,23 @@
/// <reference path="../../ConnectionHandler.ts" /> /// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" /> /// <reference path="../../proto.ts" />
namespace Modals { import PermissionType from "tc-shared/permission/PermissionType";
export type BanEntry = { 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; name?: string;
unique_id: string; unique_id: string;
} }
export function spawnBanClient(client: ConnectionHandler, entries: BanEntry | BanEntry[], callback: (data: { export function spawnBanClient(client: ConnectionHandler, entries: BanEntry | BanEntry[], callback: (data: {
length: number, length: number,
reason: string, reason: string,
no_name: boolean, no_name: boolean,
no_ip: boolean, no_ip: boolean,
no_hwid: boolean no_hwid: boolean
}) => void) { }) => void) {
const max_ban_time = client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).value; const max_ban_time = client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).value;
const permission_criteria_hwid = client.permissions.neededPermission(PermissionType.B_CLIENT_BAN_HWID).granted(1); const permission_criteria_hwid = client.permissions.neededPermission(PermissionType.B_CLIENT_BAN_HWID).granted(1);
@ -165,7 +170,7 @@ namespace Modals {
update_button_ok(); update_button_ok();
} }
tooltip(template); tooltip.initialize(template);
return template.children(); return template.children();
}, },
footer: null, footer: null,
@ -176,5 +181,4 @@ namespace Modals {
modal.open(); modal.open();
modal.htmlTag.find(".modal-body").addClass("modal-ban-client"); modal.htmlTag.find(".modal-body").addClass("modal-ban-client");
}
} }

View file

@ -1,15 +1,20 @@
/// <reference path="../../ConnectionHandler.ts" /> import {ConnectionHandler} from "tc-shared/ConnectionHandler";
/// <reference path="../../ui/elements/modal.ts" /> import {createErrorModal, createInfoModal, createModal, Modal} from "tc-shared/ui/elements/Modal";
/// <reference path="../../i18n/localize.ts" /> import {SingleCommandHandler} from "tc-shared/connection/ConnectionBase";
/// <reference path="../../proto.ts" /> 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) {
export function openBanList(client: ConnectionHandler) {
let modal: Modal; let modal: Modal;
let _callback_bans; let _callback_bans;
let _callback_triggers; let _callback_triggers;
const single_ban_handler: connection.SingleCommandHandler = { const single_ban_handler: SingleCommandHandler = {
command: "notifybanlist", command: "notifybanlist",
function: command => { function: command => {
const json = command.arguments; const json = command.arguments;
@ -41,7 +46,7 @@ namespace Modals {
return false; /* do not remove me */ return false; /* do not remove me */
} }
}; };
const single_trigger_handler: connection.SingleCommandHandler = { const single_trigger_handler: SingleCommandHandler = {
command: "notifybantriggerlist", command: "notifybantriggerlist",
function: command => { function: command => {
//TODO: Test the server id in the response? //TODO: Test the server id in the response?
@ -189,9 +194,9 @@ namespace Modals {
modal.htmlTag.dividerfy(); modal.htmlTag.dividerfy();
modal.htmlTag.find(".modal-body").addClass("modal-ban-list"); modal.htmlTag.find(".modal-body").addClass("modal-ban-list");
modal.open(); modal.open();
} }
interface BanEntry { interface BanEntry {
server_id: number; server_id: number;
banid: number; banid: number;
@ -213,18 +218,18 @@ namespace Modals {
enforcements: number; enforcements: number;
flag_own?: boolean; flag_own?: boolean;
} }
interface TriggerEntry { interface TriggerEntry {
unique_id?: string; unique_id?: string;
client_nickname?: string; client_nickname?: string;
hardware_id?: string; hardware_id?: string;
connection_ip: string; connection_ip: string;
timestamp: number; timestamp: number;
} }
interface BanListController { interface BanListController {
request_list(callback_bans: (entries: BanEntry[]) => any) : Promise<void>; request_list(callback_bans: (entries: BanEntry[]) => any) : Promise<void>;
request_trigger_list(ban: {ban_id: number, server_id: number | undefined}, callback_triggers: (entries: TriggerEntry[]) => any) : Promise<void>; request_trigger_list(ban: {ban_id: number, server_id: number | undefined}, callback_triggers: (entries: TriggerEntry[]) => any) : Promise<void>;
@ -235,10 +240,10 @@ namespace Modals {
add_ban(entry: BanEntry) : Promise<void>; add_ban(entry: BanEntry) : Promise<void>;
edit_ban(data: any) : Promise<void>; edit_ban(data: any) : Promise<void>;
delete_ban(entry_id: number, server_id: number | undefined) : Promise<void>; delete_ban(entry_id: number, server_id: number | undefined) : Promise<void>;
} }
//Note: This object must be sorted (from shortest to longest)! //Note: This object must be sorted (from shortest to longest)!
export const duration_data = { export const duration_data = {
"sec": { "sec": {
"text": tr("Seconds"), "text": tr("Seconds"),
"1-text": tr("Second"), "1-text": tr("Second"),
@ -263,9 +268,10 @@ namespace Modals {
scale: 86400 scale: 86400
}, },
}; };
function generate_dom(controller: BanListController) : JQuery { declare const moment;
function generate_dom(controller: BanListController) : JQuery {
const template = $("#tmpl_ban_list").renderTag(); const template = $("#tmpl_ban_list").renderTag();
let callback_ban_filter: ((text: string, flag_own: boolean, highlight_own: boolean) => boolean)[] = []; let callback_ban_filter: ((text: string, flag_own: boolean, highlight_own: boolean) => boolean)[] = [];
@ -392,7 +398,7 @@ namespace Modals {
log.error(LogCategory.CLIENT, tr("Failed to delete ban: %o"), error); log.error(LogCategory.CLIENT, tr("Failed to delete ban: %o"), error);
if(error instanceof CommandResult) if(error instanceof CommandResult)
error = error.id === ErrorID.PERMISSION_ERROR ? "no permissions" : error.extra_message || error.message; 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--; if(index > 0) index--;
input_duration_type.find("option[value='" + periods[index] + "']").prop("selected", true); input_duration_type.find("option[value='" + periods[index] + "']").prop("selected", true);
input_duration_value.val(Math.ceil(duration / duration_data[periods[index]].scale)); 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 { } else {
tooltip_duration_detailed.text(tr("The ban is forever.")); tooltip_duration_detailed.text(tr("The ban is forever."));
input_duration_value.attr("placeholder", tr("for ever")).val(null).prop('disabled', true); 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); log.error(LogCategory.CLIENT, tr("Failed to edited ban: %o"), error);
if(error instanceof CommandResult) if(error instanceof CommandResult)
error = error.id === ErrorID.PERMISSION_ERROR ? "no permissions" : error.extra_message || error.message; 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); log.error(LogCategory.CLIENT, tr("Failed to add ban: %o"), error);
if(error instanceof CommandResult) if(error instanceof CommandResult)
error = error.id === ErrorID.PERMISSION_ERROR ? "no permissions" : error.extra_message || error.message; 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_edit_window(false);
update_banlist(); update_banlist();
tooltip(template); tooltip.initialize(template);
return template.children(); return template.children();
}
} }
//container-triggerlist

View file

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

View file

@ -1,10 +1,12 @@
/// <reference path="../../ui/elements/modal.ts" /> import {createModal, Modal} from "tc-shared/ui/elements/Modal";
/// <reference path="../../ConnectionHandler.ts" /> import {ClientEntry} from "tc-shared/ui/client";
/// <reference path="../../proto.ts" /> 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;
let modal: Modal; export function spawnChangeLatency(client: ClientEntry, current: LatencySettings, reset: () => LatencySettings, apply: (settings: LatencySettings) => any, callback_flush?: () => any) {
export function spawnChangeLatency(client: ClientEntry, current: connection.voice.LatencySettings, reset: () => connection.voice.LatencySettings, apply: (settings: connection.voice.LatencySettings) => any, callback_flush?: () => any) {
if(modal) modal.close(); if(modal) modal.close();
const begin = Object.assign({}, current); const begin = Object.assign({}, current);
@ -110,5 +112,4 @@ namespace Modals {
modal.close_listener.push(() => modal = undefined); modal.close_listener.push(() => modal = undefined);
modal.open(); modal.open();
modal.htmlTag.find(".modal-body").addClass("modal-latency"); modal.htmlTag.find(".modal-body").addClass("modal-latency");
}
} }

View file

@ -1,12 +1,12 @@
/// <reference path="../../ui/elements/modal.ts" /> //TODO: Use the max limit!
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals { import {sliderfy} from "tc-shared/ui/elements/Slider";
//TODO: Use the max limit! 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; let modal: Modal;
export function spawnChangeVolume(client: ClientEntry, local: boolean, current: number, max: number | undefined, callback: (number) => void) { export function spawnChangeVolume(client: ClientEntry, local: boolean, current: number, max: number | undefined, callback: (number) => void) {
if(modal) modal.close(); if(modal) modal.close();
let new_value: number; let new_value: number;
@ -73,5 +73,4 @@ namespace Modals {
modal.close_listener.push(() => modal = undefined); modal.close_listener.push(() => modal = undefined);
modal.open(); modal.open();
modal.htmlTag.find(".modal-body").addClass("modal-volume"); modal.htmlTag.find(".modal-body").addClass("modal-volume");
}
} }

View file

@ -1,5 +1,10 @@
namespace Modals { import {createInfoModal, createModal, Modal} from "tc-shared/ui/elements/Modal";
export function openChannelInfo(channel: ChannelEntry) { 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; let modal: Modal;
modal = createModal({ modal = createModal({
@ -22,7 +27,7 @@ namespace Modals {
button_update.on('click', event => update_values(modal.htmlTag)); button_update.on('click', event => update_values(modal.htmlTag));
update_values(template); update_values(template);
tooltip(template); tooltip.initialize(template);
return template.children(); return template.children();
}, },
footer: null, footer: null,
@ -31,9 +36,10 @@ namespace Modals {
modal.htmlTag.find(".button-close").on('click', event => modal.close()); modal.htmlTag.find(".button-close").on('click', event => modal.close());
modal.htmlTag.find(".modal-body").addClass("modal-channel-info"); modal.htmlTag.find(".modal-body").addClass("modal-channel-info");
modal.open(); modal.open();
} }
function apply_channel_description(container: JQuery, channel: ChannelEntry) { declare const xbbcode;
function apply_channel_description(container: JQuery, channel: ChannelEntry) {
const container_value = container.find(".value"); const container_value = container.find(".value");
const container_no_value = container.find(".no-value"); const container_no_value = container.find(".no-value");
@ -50,18 +56,18 @@ namespace Modals {
container_value.hide(); container_value.hide();
container_no_value.text(tr("loading...")).show(); container_no_value.text(tr("loading...")).show();
} }
const codec_names = [ const codec_names = [
tr("Speex Narrowband"), tr("Speex Narrowband"),
tr("Speex Wideband"), tr("Speex Wideband"),
tr("Speex Ultra-Wideband"), tr("Speex Ultra-Wideband"),
tr("CELT Mono"), tr("CELT Mono"),
tr("Opus Voice"), tr("Opus Voice"),
tr("Opus Music") tr("Opus Music")
]; ];
function apply_general(container: JQuery, channel: ChannelEntry) { function apply_general(container: JQuery, channel: ChannelEntry) {
/* channel type */ /* channel type */
{ {
const tag = container.find(".channel-type .value").empty(); const tag = container.find(".channel-type .value").empty();
@ -85,7 +91,7 @@ namespace Modals {
else if(channel.properties.channel_conversation_history_length == 0) else if(channel.properties.channel_conversation_history_length == 0)
tag.text(tr("Public; Permanent message saving")); tag.text(tr("Public; Permanent message saving"));
else 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));
} }
} }
@ -148,5 +154,4 @@ namespace Modals {
container_tag.hide(); container_tag.hide();
} }
} }
}
} }

View file

@ -1,6 +1,13 @@
namespace Modals { import {ClientConnectionInfo, ClientEntry} from "tc-shared/ui/client";
type InfoUpdateCallback = (info: ClientConnectionInfo) => any; import PermissionType from "tc-shared/permission/PermissionType";
export function openClientInfo(client: ClientEntry) { 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; let modal: Modal;
let update_callbacks: InfoUpdateCallback[] = []; let update_callbacks: InfoUpdateCallback[] = [];
@ -31,7 +38,7 @@ namespace Modals {
apply_groups(client, template.find(".container-groups"), modal, update_callbacks); apply_groups(client, template.find(".container-groups"), modal, update_callbacks);
apply_packets(client, template.find(".container-packets"), modal, update_callbacks); apply_packets(client, template.find(".container-packets"), modal, update_callbacks);
tooltip(template); tooltip.initialize(template);
return template.children(); return template.children();
}, },
footer: null, footer: null,
@ -46,15 +53,15 @@ namespace Modals {
modal.htmlTag.find(".modal-body").addClass("modal-client-info"); modal.htmlTag.find(".modal-body").addClass("modal-client-info");
modal.open(); modal.open();
modal.close_listener.push(() => clearInterval(updater)); modal.close_listener.push(() => clearInterval(updater));
} }
const TIME_SECOND = 1000; const TIME_SECOND = 1000;
const TIME_MINUTE = 60 * TIME_SECOND; const TIME_MINUTE = 60 * TIME_SECOND;
const TIME_HOUR = 60 * TIME_MINUTE; const TIME_HOUR = 60 * TIME_MINUTE;
const TIME_DAY = 24 * TIME_HOUR; const TIME_DAY = 24 * TIME_HOUR;
const TIME_WEEK = 7 * TIME_DAY; const TIME_WEEK = 7 * TIME_DAY;
function format_time(time: number, default_value: string) { function format_time(time: number, default_value: string) {
let result = ""; let result = "";
if(time > TIME_WEEK) { if(time > TIME_WEEK) {
const amount = Math.floor(time / TIME_WEEK); const amount = Math.floor(time / TIME_WEEK);
@ -87,9 +94,9 @@ namespace Modals {
} }
return result.length > 0 ? result.substring(1) : default_value; return result.length > 0 ? result.substring(1) : default_value;
} }
function apply_static_info(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) { function apply_static_info(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) {
tag.find(".container-avatar").append( tag.find(".container-avatar").append(
client.channelTree.client.fileManager.avatars.generate_chat_tag({database_id: client.properties.client_database_id, id: client.clientId()}, client.properties.client_unique_identifier) client.channelTree.client.fileManager.avatars.generate_chat_tag({database_id: client.properties.client_database_id, id: client.clientId()}, client.properties.client_unique_identifier)
); );
@ -101,9 +108,9 @@ namespace Modals {
tag.find(".client-description").text( tag.find(".client-description").text(
client.properties.client_description client.properties.client_description
); );
} }
function apply_client_status(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) { function apply_client_status(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) {
tag.find(".status-output-disabled").toggle(!client.properties.client_output_hardware); tag.find(".status-output-disabled").toggle(!client.properties.client_output_hardware);
tag.find(".status-input-disabled").toggle(!client.properties.client_input_hardware); tag.find(".status-input-disabled").toggle(!client.properties.client_input_hardware);
@ -117,9 +124,10 @@ namespace Modals {
} else { } else {
tag.find(".container-away-message").hide(); tag.find(".container-away-message").hide();
} }
} }
function apply_basic_info(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) { declare const moment;
function apply_basic_info(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) {
/* Unique ID */ /* Unique ID */
{ {
const container = tag.find(".property-unique-id"); const container = tag.find(".property-unique-id");
@ -191,7 +199,7 @@ namespace Modals {
const container = tag.find(".property-country"); const container = tag.find(".property-country");
container.find(".value").empty().append( container.find(".value").empty().append(
$.spawn("div").addClass("country flag-" + client.properties.client_country.toLowerCase()), $.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")))
); );
} }
@ -279,9 +287,9 @@ namespace Modals {
node.innerText = tr("loading..."); node.innerText = tr("loading...");
} }
} }
} }
function apply_groups(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) { function apply_groups(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) {
/* server groups */ /* server groups */
{ {
const container_entries = tag.find(".entries"); const container_entries = tag.find(".entries");
@ -321,9 +329,9 @@ namespace Modals {
update_groups(); update_groups();
} }
} }
function apply_packets(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) { function apply_packets(client: ClientEntry, tag: JQuery, modal: Modal, callbacks: InfoUpdateCallback[]) {
/* Packet Loss */ /* Packet Loss */
{ {
@ -361,7 +369,7 @@ namespace Modals {
if(packets == 0 && info.connection_packets_received_keepalive == -1) if(packets == 0 && info.connection_packets_received_keepalive == -1)
node_downstream.innerText = tr("Not calculated"); node_downstream.innerText = tr("Not calculated");
else else
node_downstream.innerText = MessageHelper.format_number(packets, {unit: "Packets"}); node_downstream.innerText = format_number(packets, {unit: "Packets"});
}); });
node_downstream.innerText = tr("loading..."); node_downstream.innerText = tr("loading...");
} }
@ -375,7 +383,7 @@ namespace Modals {
if(packets == 0 && info.connection_packets_sent_keepalive == -1) if(packets == 0 && info.connection_packets_sent_keepalive == -1)
node_upstream.innerText = tr("Not calculated"); node_upstream.innerText = tr("Not calculated");
else else
node_upstream.innerText = MessageHelper.format_number(packets, {unit: "Packets"}); node_upstream.innerText = format_number(packets, {unit: "Packets"});
}); });
node_upstream.innerText = tr("loading..."); node_upstream.innerText = tr("loading...");
} }
@ -396,7 +404,7 @@ namespace Modals {
if(bytes == 0 && info.connection_bytes_received_keepalive == -1) if(bytes == 0 && info.connection_bytes_received_keepalive == -1)
node_downstream.innerText = tr("Not calculated"); node_downstream.innerText = tr("Not calculated");
else else
node_downstream.innerText = MessageHelper.network.format_bytes(bytes); node_downstream.innerText = network.format_bytes(bytes);
}); });
node_downstream.innerText = tr("loading..."); node_downstream.innerText = tr("loading...");
} }
@ -410,7 +418,7 @@ namespace Modals {
if(bytes == 0 && info.connection_bytes_sent_keepalive == -1) if(bytes == 0 && info.connection_bytes_sent_keepalive == -1)
node_upstream.innerText = tr("Not calculated"); node_upstream.innerText = tr("Not calculated");
else else
node_upstream.innerText = MessageHelper.network.format_bytes(bytes); node_upstream.innerText = network.format_bytes(bytes);
}); });
node_upstream.innerText = tr("loading..."); node_upstream.innerText = tr("loading...");
} }
@ -431,7 +439,7 @@ namespace Modals {
if(bytes == 0 && info.connection_bandwidth_received_last_second_keepalive == -1) if(bytes == 0 && info.connection_bandwidth_received_last_second_keepalive == -1)
node_downstream.innerText = tr("Not calculated"); node_downstream.innerText = tr("Not calculated");
else 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..."); node_downstream.innerText = tr("loading...");
} }
@ -445,7 +453,7 @@ namespace Modals {
if(bytes == 0 && info.connection_bandwidth_sent_last_second_keepalive == -1) if(bytes == 0 && info.connection_bandwidth_sent_last_second_keepalive == -1)
node_upstream.innerText = tr("Not calculated"); node_upstream.innerText = tr("Not calculated");
else 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..."); node_upstream.innerText = tr("loading...");
} }
@ -466,7 +474,7 @@ namespace Modals {
if(bytes == 0 && info.connection_bandwidth_received_last_minute_keepalive == -1) if(bytes == 0 && info.connection_bandwidth_received_last_minute_keepalive == -1)
node_downstream.innerText = tr("Not calculated"); node_downstream.innerText = tr("Not calculated");
else 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..."); node_downstream.innerText = tr("loading...");
} }
@ -480,7 +488,7 @@ namespace Modals {
if(bytes == 0 && info.connection_bandwidth_sent_last_minute_keepalive == -1) if(bytes == 0 && info.connection_bandwidth_sent_last_minute_keepalive == -1)
node_upstream.innerText = tr("Not calculated"); node_upstream.innerText = tr("Not calculated");
else 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..."); node_upstream.innerText = tr("loading...");
} }
@ -495,7 +503,7 @@ namespace Modals {
if(node_downstream) { if(node_downstream) {
client.updateClientVariables().then(info => { client.updateClientVariables().then(info => {
//TODO: Test for own client info and if so then show the max quota (needed permission) //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..."); node_downstream.innerText = tr("loading...");
} }
@ -503,10 +511,9 @@ namespace Modals {
if(node_upstream) { if(node_upstream) {
client.updateClientVariables().then(info => { client.updateClientVariables().then(info => {
//TODO: Test for own client info and if so then show the max quota (needed permission) //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..."); 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! //FIXME: Move this shit out of this file!
namespace connection_log { export namespace connection_log {
//TODO: Save password data //TODO: Save password data
export type ConnectionData = { export type ConnectionData = {
name: string; name: string;
@ -91,11 +101,11 @@ namespace connection_log {
}); });
} }
namespace Modals { declare const native_client;
export function spawnConnectModal(options: { export function spawnConnectModal(options: {
default_connect_new_tab?: boolean /* default false */ 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}) { }, defaultHost: { url: string, enforce: boolean} = { url: "ts.TeaSpeak.de", enforce: false}, connect_profile?: { profile: ConnectionProfile, enforce: boolean}) {
let selected_profile: profiles.ConnectionProfile; let selected_profile: ConnectionProfile;
const random_id = (() => { const random_id = (() => {
const array = new Uint32Array(10); const array = new Uint32Array(10);
@ -180,7 +190,7 @@ namespace Modals {
button_connect.trigger('click'); button_connect.trigger('click');
}); });
button_manage.on('click', event => { button_manage.on('click', event => {
const modal = Modals.spawnSettingsModal("identity-profiles"); const modal = spawnSettingsModal("identity-profiles");
modal.close_listener.push(() => { modal.close_listener.push(() => {
input_profile.trigger('change'); input_profile.trigger('change');
}); });
@ -189,14 +199,14 @@ namespace Modals {
/* Connect Profiles */ /* Connect Profiles */
{ {
for(const profile of profiles.profiles()) { for(const profile of profiles()) {
input_profile.append( input_profile.append(
$.spawn("option").text(profile.profile_name).val(profile.id) $.spawn("option").text(profile.profile_name).val(profile.id)
); );
} }
input_profile.on('change', event => { 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); settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, undefined);
input_nickname input_nickname
@ -289,7 +299,7 @@ namespace Modals {
).append( ).append(
$.spawn("div").addClass("column country-name").append([ $.spawn("div").addClass("column country-name").append([
$.spawn("div").addClass("country flag-" + entry.country.toLowerCase()), $.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( ).append(
$.spawn("div").addClass("column clients").text(entry.clients_online + "/" + entry.clients_total) $.spawn("div").addClass("column clients").text(entry.clients_online + "/" + entry.clients_total)
@ -318,14 +328,13 @@ namespace Modals {
modal.open(); modal.open();
return; return;
} }
export const Regex = { export const Regex = {
//DOMAIN<:port> //DOMAIN<:port>
DOMAIN: /^(localhost|((([a-zA-Z0-9_-]{0,63}\.){0,253})?[a-zA-Z0-9_-]{0,63}\.[a-zA-Z]{2,64}))(|:(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[0-5]?[0-9]{1,46}))$/, DOMAIN: /^(localhost|((([a-zA-Z0-9_-]{0,63}\.){0,253})?[a-zA-Z0-9_-]{0,63}\.[a-zA-Z]{2,64}))(|:(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[0-5]?[0-9]{1,46}))$/,
//IP<:port> //IP<:port>
IP_V4: /(^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(|:(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[0-5]?[0-9]{1,4}))$/, IP_V4: /(^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(|:(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[0-5]?[0-9]{1,4}))$/,
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_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*$/, 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,7 +1,17 @@
/// <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) {
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 let properties: ChannelProperties = { } as ChannelProperties; //The changes properties
const modal = createModal({ const modal = createModal({
header: channel ? tr("Edit channel") : tr("Create channel"), header: channel ? tr("Edit channel") : tr("Create channel"),
@ -92,7 +102,7 @@ namespace Modals {
callback(properties, updated); //First may create the channel callback(properties, updated); //First may create the channel
}); });
tooltip(modal.htmlTag); tooltip.initialize(modal.htmlTag);
modal.htmlTag.find(".button_cancel").click(() => { modal.htmlTag.find(".button_cancel").click(() => {
modal.close(); modal.close();
callback(); callback();
@ -101,9 +111,9 @@ namespace Modals {
modal.open(); modal.open();
if(!channel) if(!channel)
modal.htmlTag.find(".channel_name").focus(); modal.htmlTag.find(".channel_name").focus();
} }
function applyGeneralListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, button: JQuery, channel: ChannelEntry | undefined) { function applyGeneralListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, button: JQuery, channel: ChannelEntry | undefined) {
let updateButton = () => { let updateButton = () => {
const status = tag.find(".input_error").length != 0; const status = tag.find(".input_error").length != 0;
console.log("Disabled: %o", status); console.log("Disabled: %o", status);
@ -121,7 +131,7 @@ namespace Modals {
} }
tag.find(".button-select-icon").on('click', event => { tag.find(".button-select-icon").on('click', event => {
Modals.spawnIconSelect(connection, id => { spawnIconSelect(connection, id => {
const icon_node = tag.find(".icon-preview"); const icon_node = tag.find(".icon-preview");
icon_node.children().remove(); icon_node.children().remove();
icon_node.append(connection.fileManager.icons.generateTag(id)); icon_node.append(connection.fileManager.icons.generateTag(id));
@ -145,7 +155,7 @@ namespace Modals {
tag.find(".channel_password").change(function (this: HTMLInputElement) { tag.find(".channel_password").change(function (this: HTMLInputElement) {
properties.channel_flag_password = this.value.length != 0; properties.channel_flag_password = this.value.length != 0;
if(properties.channel_flag_password) 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"); channel_password.removeClass("input_error");
if(!properties.channel_flag_password) if(!properties.channel_flag_password)
@ -205,9 +215,9 @@ namespace Modals {
tag.find(".channel_password").trigger('change'); tag.find(".channel_password").trigger('change');
}, 0); }, 0);
} }
} }
function applyStandardListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, simple: JQuery, parent: ChannelEntry, channel: ChannelEntry) { function applyStandardListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, simple: JQuery, parent: ChannelEntry, channel: ChannelEntry) {
/* Channel type */ /* Channel type */
{ {
const input_advanced_type = tag.find("input[name='channel_type']"); const input_advanced_type = tag.find("input[name='channel_type']");
@ -483,9 +493,9 @@ namespace Modals {
setTimeout(() => container_max_users.find("input:checked").trigger('change'), 100); setTimeout(() => container_max_users.find("input:checked").trigger('change'), 100);
} }
} }
} }
function applyPermissionListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, button: JQuery, permissions: PermissionManager, channel?: ChannelEntry) { function applyPermissionListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, button: JQuery, permissions: PermissionManager, channel?: ChannelEntry) {
let apply_permissions = (channel_permissions: PermissionValue[]) => { let apply_permissions = (channel_permissions: PermissionValue[]) => {
log.trace(LogCategory.CHANNEL, tr("Received channel permissions: %o"), channel_permissions); log.trace(LogCategory.CHANNEL, tr("Received channel permissions: %o"), channel_permissions);
@ -526,9 +536,9 @@ namespace Modals {
console.log("Failed to receive channel permissions (%o)", error); console.log("Failed to receive channel permissions (%o)", error);
}); });
} else apply_permissions([]); } else apply_permissions([]);
} }
function applyAudioListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, simple: JQuery, channel?: ChannelEntry) { function applyAudioListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, simple: JQuery, channel?: ChannelEntry) {
const bandwidth_mapping = [ const bandwidth_mapping = [
/* SPEEX narrow */ [2.49, 2.69, 2.93, 3.17, 3.17, 3.56, 3.56, 4.05, 4.05, 4.44, 5.22], /* SPEEX narrow */ [2.49, 2.69, 2.93, 3.17, 3.17, 3.56, 3.56, 4.05, 4.05, 4.44, 5.22],
/* SPEEX wide */ [2.69, 2.93, 3.17, 3.42, 3.76, 4.25, 4.74, 5.13, 5.62, 6.40, 7.37], /* SPEEX wide */ [2.69, 2.93, 3.17, 3.42, 3.76, 4.25, 4.74, 5.13, 5.62, 6.40, 7.37],
@ -679,9 +689,9 @@ namespace Modals {
change_quality(channel.properties.channel_codec_quality); change_quality(channel.properties.channel_codec_quality);
} }
update_template(); update_template();
} }
function applyAdvancedListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, button: JQuery, channel?: ChannelEntry) { function applyAdvancedListener(connection: ConnectionHandler, properties: ChannelProperties, tag: JQuery, button: JQuery, channel?: ChannelEntry) {
tag.find(".channel_name_phonetic").change(function (this: HTMLInputElement) { tag.find(".channel_name_phonetic").change(function (this: HTMLInputElement) {
properties.channel_topic = this.value; properties.channel_topic = this.value;
}); });
@ -707,5 +717,4 @@ namespace Modals {
properties.channel_codec_is_unencrypted = parseInt(this.value) == 0; properties.channel_codec_is_unencrypted = parseInt(this.value) == 0;
}).prop("disabled", !permission).parent(".input-boxed").toggleClass("disabled", !permission); }).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