Merge remote-tracking branch 'remotes/origin/canary' into develop

# Conflicts:
#	ChangeLog.md
canary
WolverinDEV 2020-08-05 19:12:56 +02:00
commit 6eed79ad61
92 changed files with 62034 additions and 26700 deletions

View File

@ -1,4 +1,15 @@
# Changelog:
* **05.08.20**
- Putting the CSS files within the assets. No extra load needed any more
- Revoked the async file loading limit
- Improved chunk splitting for webpack
- Using webpack for the opus codec generated code as well
- Improved the web audio context handler
* **01.08.20**
- Cleaning up the channel trees selection on reset
- Updated the translations to the newest standard
* **25.07.20**
- Fixed bug where icons could not be loaded due to cros policy

8
client/.gitignore vendored
View File

@ -1,8 +1,8 @@
css/**/*.css
css/**/*.css.map
app/**/*.css
app/**/*.css.map
js/**/*.js
js/**/*.js.map
app/**/*.js
app/**/*.js.map
declarations/
generated/

31
client/app/index.scss Normal file
View File

@ -0,0 +1,31 @@
:global {
html, body {
border: 0;
margin: 0;
}
.app-container {
right: 0;
left: 0;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
position: absolute;
display: flex;
justify-content: stretch;
.app {
width: 100%;
height: 100%;
margin: 0;
display: flex; flex-direction: column; resize: both;
}
}
footer {
display: none!important;
}
}

View File

@ -1,4 +1,7 @@
declare const __webpack_require__;
window["shared-require"] = __webpack_require__;
import "./index.scss";
/* firstly assign the shared-require */
setTimeout(() => require("tc-shared/main"), 0);

View File

@ -1,29 +0,0 @@
html, body {
border: 0;
margin: 0;
}
.app-container {
right: 0;
left: 0;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
position: absolute;
display: flex;
justify-content: stretch;
.app {
width: 100%;
height: 100%;
margin: 0;
display: flex; flex-direction: column; resize: both;
}
}
footer {
display: none!important;
}

277
file.ts
View File

@ -62,47 +62,6 @@ const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
"path": "css/",
"local-path": "./loader/css/"
},
{ /* 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$/,
@ -126,70 +85,23 @@ const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
"path": "img/",
"local-path": "./shared/img/"
}
];
const APP_FILE_LIST_SHARED_VENDORS: ProjectResource[] = [
{
"type": "js",
"search-pattern": /.*(\.min)?\.js$/,
"build-target": "dev|rel",
"search-exclude": /.*xbbcode.*/g,
"path": "vendor/",
"local-path": "./vendor/"
}
];
const APP_FILE_LIST_CLIENT_SOURCE: ProjectResource[] = [
{ /* client css files */
"client-only": true,
"type": "css",
"search-pattern": /.*\.css$/,
"build-target": "dev|rel",
"path": "css/",
"local-path": "./client/css/"
},
{ /* client js files */
"client-only": true,
"type": "js",
"search-pattern": /.*\.js/,
"build-target": "dev",
"path": "js/",
"local-path": "./client/js/"
}
];
const APP_FILE_LIST_WEB_SOURCE: ProjectResource[] = [
{ /* generated assembly files */
{ /* assembly files */
"web-only": true,
"type": "wasm",
"search-pattern": /.*\.(wasm)/,
"build-target": "dev|rel",
"path": "wasm/",
"local-path": "./web/native-codec/generated/"
},
{ /* web css files */
"web-only": true,
"type": "css",
"search-pattern": /.*\.css$/,
"build-target": "dev|rel",
"path": "js/",
"local-path": "./dist/"
}
];
"path": "css/",
"local-path": "./web/css/"
},
{ /* web html files */
"web-only": true,
"type": "html",
"search-pattern": /.*\.(html)/,
"build-target": "dev|rel",
const APP_FILE_LIST_SHARED_VENDORS: ProjectResource[] = [];
"path": "./",
"local-path": "./web/html/"
},
const APP_FILE_LIST_CLIENT_SOURCE: ProjectResource[] = [];
const APP_FILE_LIST_WEB_SOURCE: ProjectResource[] = [
{ /* translations */
"web-only": true, /* Only required for the web client */
"type": "i18n",
@ -201,98 +113,6 @@ const APP_FILE_LIST_WEB_SOURCE: ProjectResource[] = [
}
];
//FIXME: This isn't working right now
const CERTACCEPT_FILE_LIST: ProjectResource[] = [
{ /* html files */
"type": "html",
"search-pattern": /^([a-zA-Z]+)\.(html|json)$/,
"build-target": "dev|rel",
"path": "./popup/certaccept/",
"local-path": "./shared/popup/certaccept/html/"
},
{ /* javascript loader (debug) */
"type": "js",
"search-pattern": /(loader|certaccept)\.js$/,
"build-target": "dev",
"path": "./popup/certaccept/loader/",
"local-path": "./shared/loader/"
},
{ /* javascript loader (releases) */
"type": "js",
"search-pattern": /.*loader_certaccept.min.js$/,
"build-target": "rel",
"path": "./popup/certaccept/loader/",
"local-path": "./shared/generated/"
},
{ /* javascript imported from shared for debug */
"type": "js",
"search-pattern": /^(BrowserIPC|log|proto|settings)\.js$/,
"build-target": "dev",
"path": "./popup/certaccept/js/",
"local-path": "./shared/js/"
},
{ /* javascript for debug */
"type": "js",
"search-pattern": /^certaccept\.min\.js$/,
"build-target": "rel",
"path": "./popup/certaccept/js/",
"local-path": "./shared/generated/"
},
{ /* javascript for release */
"type": "js",
"search-pattern": /^.*\.js$/,
"build-target": "dev",
"path": "./popup/certaccept/js/",
"local-path": "./shared/popup/certaccept/js/"
},
{ /* shared css files */
"type": "css",
"search-pattern": /.*\.css$/,
"build-target": "dev|rel",
"path": "./popup/certaccept/css/loader/",
"local-path": "./shared/css/loader/"
},
{ /* shared css files */
"type": "css",
"search-pattern": /.*\.css$/,
"build-target": "dev|rel",
"path": "./popup/certaccept/css/static/",
"local-path": "./shared/popup/certaccept/css/static/"
},
{ /* img files */
"type": "img",
"search-pattern": /^(loading_error.*)\.(svg)$/,
"build-target": "dev|rel",
"path": "./popup/certaccept/img/",
"local-path": "./shared/img/"
},
{ /* jquery vendor */
"type": "js",
"search-pattern": /^jquery\/.*\.js$/,
"build-target": "dev|rel",
"path": "./popup/certaccept/vendor/",
"local-path": "./vendor/"
},
];
const CLIENT_APP_FILE_LIST = [
...APP_FILE_LIST_SHARED_SOURCE,
...APP_FILE_LIST_SHARED_VENDORS,
@ -303,7 +123,6 @@ const WEB_APP_FILE_LIST = [
...APP_FILE_LIST_SHARED_SOURCE,
...APP_FILE_LIST_SHARED_VENDORS,
...APP_FILE_LIST_WEB_SOURCE,
...CERTACCEPT_FILE_LIST,
];
//@ts-ignore
@ -451,8 +270,6 @@ namespace server {
search_options: SearchOptions;
}
const exec: (command: string) => Promise<{ stdout: string, stderr: string }> = util.promisify(cp.exec);
let files: ProjectResource[] = [];
let server: http.Server;
let options: Options;
@ -475,9 +292,9 @@ namespace server {
server = https.createServer({
key: await fs.readFile(key_file),
cert: await fs.readFile(cert_file),
}, handle_request);
}, handleHTTPRequest);
} else {
server = http.createServer(handle_request);
server = http.createServer(handleHTTPRequest);
}
await new Promise((resolve, reject) => {
server.on('error', reject);
@ -495,7 +312,7 @@ namespace server {
}
}
async function serve_file(pathname: string, query: any, response: http.ServerResponse) {
async function serve_file(pathname: string, response: http.ServerResponse) {
const file = await generator.search_http_file(files, pathname, options.search_options);
if(!file) {
console.log("[SERVER] Client requested unknown file %s", pathname);
@ -505,12 +322,12 @@ namespace server {
return;
}
let type = mt.lookup(path.extname(file)) || "text/html";
let type: string = mt.lookup(path.extname(file)) || "text/html";
console.log("[SERVER] Serving file %s", file, type);
const fis = fs.createReadStream(file);
response.writeHead(200, "success", {
"Content-Type": type + "; charset=utf-8"
"Content-Type": type + (type.startsWith("text/") ? "; charset=utf-8" : "")
});
fis.on("end", () => response.end());
@ -520,7 +337,7 @@ namespace server {
fis.on("data", data => response.write(data));
}
async function handle_api_request(request: http.IncomingMessage, response: http.ServerResponse, url: url_utils.UrlWithParsedQuery) {
async function handle_api_request(response: http.ServerResponse, url: url_utils.UrlWithParsedQuery) {
if(url.query["type"] === "files") {
response.writeHead(200, { "info-version": 1 });
response.write("type\thash\tpath\tname\n");
@ -531,7 +348,7 @@ namespace server {
} else if(url.query["type"] === "file") {
let p = path.join(url.query["path"] as string, url.query["name"] as string).replace(/\\/g, "/");
if(!p.startsWith("/")) p = "/" + p;
serve_file(p, url.query, response);
await serve_file(p, response);
return;
}
@ -543,7 +360,7 @@ namespace server {
response.end();
}
function handle_request(request: http.IncomingMessage, response: http.ServerResponse) {
function handleHTTPRequest(request: http.IncomingMessage, response: http.ServerResponse) {
let url: url_utils.UrlWithParsedQuery;
try {
url = url_utils.parse(request.url, true);
@ -557,12 +374,12 @@ namespace server {
if(url.pathname === "/api.php") {
//Client API
handle_api_request(request, response, url);
handle_api_request(response, url);
return;
} else if(url.pathname === "/") {
url.pathname = "/index.html";
}
serve_file(url.pathname, url.query, response);
serve_file(url.pathname, response);
}
}
@ -688,28 +505,6 @@ namespace watcher {
}
}
export class TSCWatcher extends Watcher {
constructor() {
super("TSC");
//this.verbose = true;
}
protected start_command(): string[] {
return ["npm", "run", "tsc", "--", "-w"];
}
}
export class SASSWatcher extends Watcher {
constructor() {
super("SASS");
this.verbose = false;
}
protected start_command(): string[] {
return ["npm", "run", "sass", "--", "--watch", "shared/css:shared/css"];
}
}
export class WebPackWatcher extends Watcher {
private readonly target;
@ -738,20 +533,10 @@ async function main_serve(target: "client" | "web", mode: "rel" | "dev", port: n
console.log("Server started on %d", port);
console.log("To stop the server press ^K^C.");
await new Promise(resolve => {});
await new Promise(() => {});
}
async function main_develop(node: boolean, target: "client" | "web", port: number, flags: string[]) {
const tscwatcher = new watcher.TSCWatcher();
try {
if(flags.indexOf("--no-tsc") == -1)
await tscwatcher.start();
const sasswatcher = new watcher.SASSWatcher();
try {
if(flags.indexOf("--no-sass") == -1)
await sasswatcher.start();
const webpackwatcher = new watcher.WebPackWatcher(target);
try {
@ -794,29 +579,11 @@ async function main_develop(node: boolean, target: "client" | "web", port: numbe
console.warn("Failed to stop WebPack watcher: %o", error instanceof Error ? error.message : error);
}
}
} catch(error) {
console.error("Failed to start SASS watcher: %o", error instanceof Error ? error.message : error);
} finally {
try {
await sasswatcher.stop();
} catch(error) {
console.warn("Failed to stop SASS watcher: %o", error instanceof Error ? error.message : error);
}
}
} catch(error) {
console.error("Failed to start TSC watcher: %o", error instanceof Error ? error.message : error);
} finally {
try {
await tscwatcher.stop();
} catch(error) {
console.warn("Failed to stop TSC watcher: %o", error instanceof Error ? error.message : error);
}
}
}
async function git_tag() {
const git_rev = fs.readFileSync(path.join(__dirname, ".git", "HEAD")).toString();
let version;
if(git_rev.indexOf("/") === -1)
return git_rev.substr(0, 7);
else
@ -845,7 +612,7 @@ async function main_generate(target: "client" | "web", mode: "rel" | "dev", dest
const exec = util.promisify(cp.exec);
linker = async (source, target) => {
const command = "ln -s " + source + " " + target;
const { stdout, stderr } = await exec(command);
const { stderr } = await exec(command);
if(stderr)
throw "failed to create link: " + stderr;
}

View File

@ -25,7 +25,6 @@ async function detectAPNGSupport() {
ctx.drawImage(image, 0, 0);
apngSupport = ctx.getImageData(0, 0, 1, 1).data[3] === 0;
console.log("Browser APNG support: %o", apngSupport);
}
function initializeElements() {

View File

@ -9,7 +9,7 @@ window["loader"] = loader;
/* let the loader register himself at the window first */
const target = getUrlParameter("loader-target") || "app";
console.error("Loading app with loader \"%s\"", target);
console.info("Loading app with loader \"%s\"", target);
let appLoader: ApplicationLoader;
if(target === "empty") {

View File

@ -49,9 +49,9 @@ export async function load_parallel<T>(requests: T[], executor: (_: T) => Promis
if(typeof callback === "undefined")
callback = () => {};
options.max_parallel_requests = 1;
const maxParallelRequests = typeof options.max_parallel_requests === "number" && options.max_parallel_requests > 0 ? options.max_parallel_requests : Number.MAX_SAFE_INTEGER;
while (pendingRequests.length > 0) {
while(typeof options.max_parallel_requests !== "number" || options.max_parallel_requests <= 0 || Object.keys(currentRequests).length < options.max_parallel_requests) {
while(Object.keys(currentRequests).length < maxParallelRequests) {
const element = pendingRequests.pop();
const name = stringify(element);

View File

@ -68,76 +68,6 @@ const loader_webassembly = {
}
};
const loader_style = {
load_style: async taskId => {
if(__build.mode === "debug") {
await loader_style.load_style_debug(taskId);
} else {
await loader_style.load_style_release(taskId);
}
},
load_style_debug: async taskId => {
await loader.style.load_multiple([
"css/static/main.css",
"css/static/main-layout.css",
"css/static/scroll.css",
"css/static/channel-tree.css",
"css/static/ts/tab.css",
"css/static/ts/icons.css",
"css/static/ts/icons_em.css",
"css/static/ts/country.css",
"css/static/general.css",
"css/static/modal.css",
"css/static/modals.css",
"css/static/modal-about.css",
"css/static/modal-avatar.css",
"css/static/modal-icons.css",
"css/static/modal-bookmarks.css",
"css/static/modal-connect.css",
"css/static/modal-channel.css",
"css/static/modal-query.css",
"css/static/modal-latency.css",
"css/static/modal-invite.css",
"css/static/modal-banlist.css",
"css/static/modal-banclient.css",
"css/static/modal-channelinfo.css",
"css/static/modal-clientinfo.css",
"css/static/modal-serverinfo.css",
"css/static/modal-musicmanage.css",
"css/static/modal-serverinfobandwidth.css",
"css/static/modal-identity.css",
"css/static/modal-newcomer.css",
"css/static/modal-settings.css",
"css/static/modal-poke.css",
"css/static/modal-server.css",
"css/static/modal-keyselect.css",
"css/static/modal-group-assignment.css",
"css/static/overlay-image-preview.css",
"css/static/context_menu.css",
"css/static/frame-chat.css",
"css/static/connection_handlers.css",
"css/static/server-log.css",
"css/static/htmltags.css",
"css/static/hostbanner.css",
"css/static/menu-bar.css"
], {
cache_tag: cache_tag(),
max_parallel_requests: -1
}, LoaderTaskCallback(taskId));
},
load_style_release: async taskId => {
await loader.style.load_multiple([
"css/static/base.css",
"css/static/main.css",
], {
cache_tag: cache_tag(),
max_parallel_requests: -1
}, LoaderTaskCallback(taskId));
}
};
loader.register_task(loader.Stage.INITIALIZING, {
name: "secure tester",
function: async () => {
@ -165,12 +95,6 @@ loader.register_task(loader.Stage.JAVASCRIPT, {
priority: 10
});
loader.register_task(loader.Stage.STYLE, {
name: "style",
function: loader_style.load_style,
priority: 10
});
loader.register_task(loader.Stage.TEMPLATES, {
name: "templates",
function: async taskId => {

View File

@ -23,7 +23,6 @@ export default class implements ApplicationLoader {
});
/* required sadly */
loader.register_task(loader.Stage.SETUP, {
name: "page setup",
function: async () => {

35
package-lock.json generated
View File

@ -1,5 +1,5 @@
{
"name": "client",
"name": "teaspeak-web",
"version": "1.2.0",
"lockfileVersion": 1,
"requires": true,
@ -1373,6 +1373,7 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/jsrender/-/jsrender-1.0.5.tgz",
"integrity": "sha512-Fjdp5QACaBMsd5vpx9x27rggFa0nyd8zqWnuTw8Aum4+gM/NiQubb6pweE3sgfHwrjRh7BGjYydpE4WYbsB+Ow==",
"dev": true,
"requires": {
"jsrender": "*"
}
@ -4765,6 +4766,38 @@
"integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==",
"dev": true
},
"file-loader": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.0.0.tgz",
"integrity": "sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ==",
"dev": true,
"requires": {
"loader-utils": "^2.0.0",
"schema-utils": "^2.6.5"
},
"dependencies": {
"json5": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
}
}
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",

View File

@ -1,22 +1,16 @@
{
"name": "client",
"name": "teaspeak-web",
"version": "1.2.0",
"description": "Welcome here! This repository is created with two reasons:\n 1. People can bring their own ideas and follow their implementation\n 2. People can see TeaSpeak Web client progress and avoid creating repetitive issues all the time.",
"main": "main.js",
"directories": {},
"scripts": {
"compile-sass": "sass --update shared/css/:shared/css/ loader/css/:loader/css/ web/css/:web/css/ client/css/:client/css/ vendor/:vendor/",
"compile-project-base": "tsc -p tsbaseconfig.json",
"dtsgen": "node tools/dtsgen/index.js",
"trgen": "node tools/trgen/index.js",
"sass": "sass",
"csso": "csso",
"tsc": "tsc",
"compile-scss": "sass loader/css/index.scss:loader/css/index.css",
"start": "npm run compile-project-base && node file.js ndevelop",
"build-web": "webpack --config webpack-web.config.js",
"develop-web": "npm run compile-project-base && node file.js develop web",
"build-client": "webpack --config webpack-client.config.js",
"develop-client": "npm run compile-project-base && node file.js develop client",
"webpack-web": "webpack --config webpack-web.config.js",
"webpack-client": "webpack --config webpack-client.config.js",
"generate-i18n-gtranslate": "node shared/generate_i18n_gtranslate.js"
@ -35,6 +29,7 @@
"@types/fs-extra": "^8.0.1",
"@types/html-minifier": "^3.5.3",
"@types/jquery": "^3.3.34",
"@types/jsrender": "^1.0.5",
"@types/loader-utils": "^1.1.3",
"@types/lodash": "^4.14.149",
"@types/moment": "^2.13.0",
@ -54,6 +49,7 @@
"csso-cli": "^3.0.0",
"ejs": "^3.0.2",
"exports-loader": "^0.7.0",
"file-loader": "^6.0.0",
"fs-extra": "latest",
"gulp": "^4.0.2",
"html-loader": "^1.0.0",
@ -89,7 +85,6 @@
},
"homepage": "https://www.teaspeak.de",
"dependencies": {
"@types/jsrender": "^1.0.5",
"detect-browser": "^5.1.1",
"dompurify": "^2.0.8",
"emoji-mart": "git+https://github.com/WolverinDEV/emoji-mart.git",

View File

@ -50,35 +50,13 @@ if [[ $_exit_code -ne 0 ]]; then
fi
echo "Generating style files"
npm run compile-sass; _exit_code=$?
npm run compile-scss; _exit_code=$?
if [[ $_exit_code -ne 0 ]]; then
echo "Failed to generate style files"
exit 1
fi
echo "Compile vendor XBBCode"
execute_tsc -p ./vendor/xbbcode/tsconfig.json; _exit_code=$?
if [[ $_exit_code -ne 0 ]]; then
echo "Failed to build the XBBCode vendor"
exit 1
fi
echo "Compile vendor emoji-picker"
execute_tsc ./vendor/emoji-picker/src/jquery.lsxemojipicker.ts
if [[ $_exit_code -ne 0 ]]; then
echo "Failed to build the emoji-picker vendor"
exit 1
fi
if [[ "$build_type" == "release" ]]; then # Compile everything for release mode
echo "Packing generated css files"
chmod +x ./shared/css/generate_packed.sh
./shared/css/generate_packed.sh; _exit_code=$?
if [[ $_exit_code -ne 0 ]]; then
echo "Failed to package generated css files"
exit 1
fi
NODE_ENV=production npm run build-$build_target; _exit_code=$?
if [[ $_exit_code -ne 0 ]]; then
echo "Failed to build the $build_target applcation"

4
shared/.gitignore vendored
View File

@ -5,8 +5,12 @@ declarations/*.d.ts
css/static/**/*.css
css/static/**/*.css.map
css/**/*.js
css/**/*.js.map
js/**/*.js
js/**/*.js.map
js/**/*.css
js/**/*.css.map

43
shared/css/load-css.tsx Normal file
View File

@ -0,0 +1,43 @@
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/properties.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/main-layout.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/general.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/channel-tree.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/connection_handlers.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/context_menu.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/frame-chat.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/server-log.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/scroll.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/hostbanner.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/htmltags.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/menu-bar.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/mixin.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modals.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-about.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-avatar.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-banclient.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-banlist.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-bookmarks.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-channel.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-channelinfo.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-clientinfo.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-connect.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-group-assignment.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-icons.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-identity.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-newcomer.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-invite.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-keyselect.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-poke.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-query.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-server.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-musicmanage.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-serverinfobandwidth.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-serverinfo.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/modal-settings.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/overlay-image-preview.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/ts/tab.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/ts/icons.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/ts/icons_em.scss"
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./static/ts/country.scss"

View File

@ -1,7 +1,7 @@
.context-menu {
overflow: visible;
display: none;
z-index: 2000;
z-index: 120000;
position: absolute;
.context-menu-container {

View File

@ -1890,8 +1890,8 @@
</div>
<div class="container general-updates">{{tr "GU" /}}</div>
<div class="container general-keymap"></div>
<div class="container general-chat"></div>
<div class="container general-notifications">
<div class="container general-notifications"></div>
<div class="container general-chat">
<label>
<div class="checkbox">
<input type="checkbox" class="option-fixed-timestamps">

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -35,6 +35,7 @@ import {copy_to_clipboard} from "tc-shared/utils/helpers";
import ContextMenuEvent = JQuery.ContextMenuEvent;
/* required import for init */
import "../css/load-css"
import "./proto";
import "./ui/elements/ContextDivider";
import "./ui/elements/Tab";
@ -126,7 +127,7 @@ function setup_jsrender() : boolean {
if(!$.templates(_entry.id, _entry.innerHTML)) {
log.error(LogCategory.GENERAL, tr("Failed to setup cache for js renderer template %s!"), _entry.id);
} else
log.info(LogCategory.GENERAL, tr("Successfully loaded jsrender template %s"), _entry.id);
log.trace(LogCategory.GENERAL, tr("Successfully loaded jsrender template %s"), _entry.id);
});
return true;
}
@ -594,6 +595,9 @@ const task_connect_handler: loader.Task = {
const task_certificate_callback: loader.Task = {
name: "certificate accept tester",
function: async () => {
/*
This is not needed any more. If we would use the certificate accept stuff, we would have an extra loader target.
I'm just keeping this, so later I've not to to any work, writing this, again.
const certificate_accept = settings.static_global(Settings.KEY_CERTIFICATE_CALLBACK, undefined);
if(certificate_accept) {
log.info(LogCategory.IPC, tr("Using this instance as certificate callback. ID: %s"), certificate_accept);
@ -619,12 +623,9 @@ const task_certificate_callback: loader.Task = {
}
}, 1000);
const message =
"You've successfully accepted the certificate.{:br:}" +
"This page will close in {0} seconds.";
createInfoModal(
tr("Certificate acccepted successfully"),
formatMessage(/* @tr-ignore */ tr(message), seconds_tag),
formatMessage(tr("You've successfully accepted the certificate.{:br:}This page will close in {0} seconds."), seconds_tag),
{
closeable: false,
footer: undefined
@ -637,7 +638,7 @@ const task_certificate_callback: loader.Task = {
} else {
log.info(LogCategory.IPC, tr("We're not used to accept certificated. Booting app."));
}
*/
loader.register_task(loader.Stage.LOADED, task_connect_handler);
},
priority: 10

View File

@ -120,7 +120,7 @@ namespace connection {
if(connection_copy !== connection) return;
if(current_config.verbose)
log.info(LogCategory.STATISTICS, tr("Successfully connected to server. Initializing session."));
log.debug(LogCategory.STATISTICS, tr("Successfully connected to server. Initializing session."));
connection_state = ConnectionState.INITIALIZING;
initialize_session();
@ -141,7 +141,7 @@ namespace connection {
if(typeof(event.data) !== 'string') {
if(current_config.verbose)
log.info(LogCategory.STATISTICS, tr("Received an message which isn't a string. Event object: %o"), event);
log.warn(LogCategory.STATISTICS, tr("Received an message which isn't a string. Event object: %o"), event);
return;
}
@ -212,7 +212,7 @@ namespace connection {
if(typeof(handler[type]) === 'function') {
if(current_config.verbose)
log.debug(LogCategory.STATISTICS, tr("Handling message of type %s"), type);
log.trace(LogCategory.STATISTICS, tr("Handling message of type %s"), type);
handler[type](data);
} else if(current_config.verbose) {
log.warn(LogCategory.STATISTICS, tr("Received message with an unknown type (%s). Dropping message. Full message: %o"), type, data_object);

View File

@ -44,23 +44,23 @@ class ConnectButton extends ReactComponentBase<{ multiSession: boolean; event_re
if(this.props.multiSession) {
if(!this.state.connected) {
subentries.push(
<DropdownEntry key={"connect-server"} icon={"client-connect"} text={<Translatable message={"Connect to a server"} />}
<DropdownEntry key={"connect-server"} icon={"client-connect"} text={<Translatable>Connect to a server</Translatable>}
onClick={ () => global_client_actions.fire("action_open_window_connect", {new_tab: false }) } />
);
} else {
subentries.push(
<DropdownEntry key={"disconnect-current-a"} icon={"client-disconnect"} text={<Translatable message={"Disconnect from current server"} />}
<DropdownEntry key={"disconnect-current-a"} icon={"client-disconnect"} text={<Translatable>Disconnect from current server</Translatable>}
onClick={ () => this.props.event_registry.fire("action_disconnect", { globally: false }) }/>
);
}
if(this.state.connectedAnywhere) {
subentries.push(
<DropdownEntry key={"disconnect-current-b"} icon={"client-disconnect"} text={<Translatable message={"Disconnect from all servers"} />}
<DropdownEntry key={"disconnect-current-b"} icon={"client-disconnect"} text={<Translatable>Disconnect from all servers</Translatable>}
onClick={ () => this.props.event_registry.fire("action_disconnect", { globally: true }) }/>
);
}
subentries.push(
<DropdownEntry key={"connect-new-tab"} icon={"client-connect"} text={<Translatable message={"Connect to a server in another tab"} />}
<DropdownEntry key={"connect-new-tab"} icon={"client-connect"} text={<Translatable>Connect to a server in another tab</Translatable>}
onClick={ () => global_client_actions.fire("action_open_window_connect", { new_tab: true }) } />
);
}
@ -106,9 +106,9 @@ class BookmarkButton extends ReactComponentBase<{ event_registry: Registry<Inter
marks.splice(0, 0, <hr key={"hr"} />);
return (
<Button ref={this.button_ref} dropdownButtonExtraClass={cssButtonStyle.buttonBookmarks} autoSwitch={false} iconNormal={"client-bookmark_manager"}>
<DropdownEntry icon={"client-bookmark_manager"} text={<Translatable message={"Manage bookmarks"} />}
<DropdownEntry icon={"client-bookmark_manager"} text={<Translatable>Manage bookmarks</Translatable>}
onClick={() => this.props.event_registry.fire("action_open_window", { window: "bookmark-manage" })} />
<DropdownEntry icon={"client-bookmark_add"} text={<Translatable message={"Add current server to bookmarks"} />}
<DropdownEntry icon={"client-bookmark_add"} text={<Translatable>Add current server to bookmarks</Translatable>}
onClick={() => this.props.event_registry.fire("action_add_current_server_to_bookmarks")} />
{marks}
</Button>
@ -188,25 +188,25 @@ class AwayButton extends ReactComponentBase<{ event_registry: Registry<InternalC
render() {
let dropdowns = [];
if(this.state.away) {
dropdowns.push(<DropdownEntry key={"cgo"} icon={"client-present"} text={<Translatable message={"Go online"} />}
dropdowns.push(<DropdownEntry key={"cgo"} icon={"client-present"} text={<Translatable>Go online</Translatable>}
onClick={() => this.props.event_registry.fire("action_disable_away", { globally: false })} />);
} else {
dropdowns.push(<DropdownEntry key={"sas"} icon={"client-away"} text={<Translatable message={"Set away on this server"} />}
dropdowns.push(<DropdownEntry key={"sas"} icon={"client-away"} text={<Translatable>Set away on this server</Translatable>}
onClick={() => this.props.event_registry.fire("action_set_away", { globally: false, prompt_reason: false })} />);
}
dropdowns.push(<DropdownEntry key={"sam"} icon={"client-away"} text={<Translatable message={"Set away message on this server"} />}
dropdowns.push(<DropdownEntry key={"sam"} icon={"client-away"} text={<Translatable>Set away message on this server</Translatable>}
onClick={() => this.props.event_registry.fire("action_set_away", { globally: false, prompt_reason: true })} />);
dropdowns.push(<hr key={"-hr"} />);
if(this.state.awayAnywhere) {
dropdowns.push(<DropdownEntry key={"goa"} icon={"client-present"} text={<Translatable message={"Go online for all servers"} />}
dropdowns.push(<DropdownEntry key={"goa"} icon={"client-present"} text={<Translatable>Go online for all servers</Translatable>}
onClick={() => this.props.event_registry.fire("action_disable_away", { globally: true })} />);
}
if(!this.state.awayAll) {
dropdowns.push(<DropdownEntry key={"saa"} icon={"client-away"} text={<Translatable message={"Set away on all servers"} />}
dropdowns.push(<DropdownEntry key={"saa"} icon={"client-away"} text={<Translatable>Set away on all servers</Translatable>}
onClick={() => this.props.event_registry.fire("action_set_away", { globally: true, prompt_reason: false })} />);
}
dropdowns.push(<DropdownEntry key={"sama"} icon={"client-away"} text={<Translatable message={"Set away message for all servers"} />}
dropdowns.push(<DropdownEntry key={"sama"} icon={"client-away"} text={<Translatable>Set away message for all servers</Translatable>}
onClick={() => this.props.event_registry.fire("action_set_away", { globally: true, prompt_reason: true })} />);
/* switchable because we're switching it manually */
@ -323,16 +323,16 @@ class QueryButton extends ReactComponentBase<{ event_registry: Registry<Internal
render() {
let toggle;
if(this.state.queryShown)
toggle = <DropdownEntry key={"query-show"} icon={"client-toggle_server_query_clients"} text={<Translatable message={"Hide server queries"} />}
toggle = <DropdownEntry key={"query-show"} icon={"client-toggle_server_query_clients"} text={<Translatable>Hide server queries</Translatable>}
onClick={() => this.props.event_registry.fire("action_toggle_query", { shown: false })}/>;
else
toggle = <DropdownEntry key={"query-hide"} icon={"client-toggle_server_query_clients"} text={<Translatable message={"Show server queries"} />}
toggle = <DropdownEntry key={"query-hide"} icon={"client-toggle_server_query_clients"} text={<Translatable>Show server queries</Translatable>}
onClick={() => this.props.event_registry.fire("action_toggle_query", { shown: true })}/>;
return (
<Button switched={this.state.queryShown} autoSwitch={false} iconNormal={"client-server_query"}
onToggle={flag => this.props.event_registry.fire("action_toggle_query", { shown: flag })}>
{toggle}
<DropdownEntry icon={"client-server_query"} text={<Translatable message={"Manage server queries"} />}
<DropdownEntry icon={"client-server_query"} text={<Translatable>Manage server queries</Translatable>}
onClick={() => this.props.event_registry.fire("action_open_window", { window: "query-manage" })}/>
</Button>
)

View File

@ -743,6 +743,7 @@ export class ConversationManager extends AbstractChatManager<ConversationUIEvent
}
setSelectedConversation(id: number) {
if(id >= 0)
this.findOrCreateConversation(id);
this.uiEvents.fire("notify_selected_chat", { chatId: id.toString() });
@ -798,7 +799,6 @@ export class ConversationManager extends AbstractChatManager<ConversationUIEvent
return;
}
for(const entry of command.arguments) {
conversation.historyQueryResponse.push({
timestamp: parseInt(entry["timestamp"]),

View File

@ -481,7 +481,7 @@ class ConversationMessages extends React.PureComponent<ConversationMessagesPrope
case "no-permission":
contents.push(<div key={"ol-permission"} className={cssStyle.overlay}><a>
<Translatable>You don't have permissions to participate in this conversation!</Translatable><br />
<Translatable>{this.state.failedPermission}</Translatable></a>
>{this.state.failedPermission}</a>
</div>);
break;

View File

@ -230,8 +230,13 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
throw tr("Missing Indexed DB support");
}
try {
await doOpenDatabase(false);
log.debug(LogCategory.CHAT, tr("Successfully initialized private conversation history database"));
} catch (error) {
log.error(LogCategory.CHAT, tr("Failed to initialize private conversation history database: %o"), error);
log.error(LogCategory.CHAT, tr("Do not saving the private conversation chat."));
}
}
});

View File

@ -44,7 +44,7 @@ export interface GroupPermissionCopyModalEvents {
notify_destroy: {}
}
const GroupSelector = (props: { events: Registry<GroupPermissionCopyModalEvents>, defaultGroup: number, updateEvent: "action_set_source" | "action_set_target", label: string, className: string}) => {
const GroupSelector = (props: { events: Registry<GroupPermissionCopyModalEvents>, defaultGroup: number, updateEvent: "action_set_source" | "action_set_target", label: React.ReactElement, className: string}) => {
const [ selectedGroup, setSelectedGroup ] = useState(undefined);
const [ permissions, setPermissions ] = useState<"loading" | { createTemplate, createQuery }>("loading");
const [ exitingGroups, setExitingGroups ] = useState<"loading" | GroupInfo[]>("loading");
@ -72,7 +72,7 @@ const GroupSelector = (props: { events: Registry<GroupPermissionCopyModalEvents>
return (
<FlatSelect
ref={refSelect}
label={<Translatable>{props.label}</Translatable>}
label={props.label}
className={props.className}
disabled={isLoading}
value={isLoading || selectedGroup === undefined ? "-1" : selectedGroup.toString()}
@ -146,8 +146,8 @@ class ModalGroupPermissionCopy extends Modal {
renderBody() {
return <div className={cssStyle.container}>
<div className={cssStyle.row}>
<GroupSelector events={this.events} defaultGroup={this.defaultSource} updateEvent={"action_set_source"} label={"Source group"} className={cssStyle.sourceGroup} />
<GroupSelector events={this.events} defaultGroup={this.defaultTarget} updateEvent={"action_set_target"} label={"Target group"} className={cssStyle.targetGroup} />
<GroupSelector events={this.events} defaultGroup={this.defaultSource} updateEvent={"action_set_source"} label={<Translatable>Source group</Translatable>} className={cssStyle.sourceGroup} />
<GroupSelector events={this.events} defaultGroup={this.defaultTarget} updateEvent={"action_set_target"} label={<Translatable>Target group</Translatable>} className={cssStyle.targetGroup} />
</div>
<div className={cssStyle.buttons}>
<Button color={"red"} onClick={() => this.events.fire("action_cancel")}><Translatable>Cancel</Translatable></Button>

View File

@ -238,7 +238,7 @@ const ActiveTabInfo = (props: { events: Registry<PermissionModalEvents> }) => {
return <div className={cssStyle.header + " " + cssStyle.activeTabInfo}>
<div className={cssStyle.entry}>
<a title={PermissionTabName[activeTab].translated}>
<Translatable>{PermissionTabName[activeTab].name}</Translatable>
<Translatable trIgnore={true}>{PermissionTabName[activeTab].name}</Translatable>
</a>
</div>
</div>
@ -251,7 +251,7 @@ const TabSelectorEntry = (props: { events: Registry<PermissionModalEvents>, entr
return <div className={cssStyle.entry + " " + (active ? cssStyle.selected : "")} onClick={() => !active && props.events.fire("action_activate_tab", { tab: props.entry })}>
<a title={PermissionTabName[props.entry].translated}>
<Translatable>{PermissionTabName[props.entry].translated}</Translatable>
<Translatable trIgnore={true}>{PermissionTabName[props.entry].translated}</Translatable>
</a>
</div>;
};

View File

@ -204,7 +204,7 @@ const ClientListButton = (props: { events: Registry<PermissionEditorEvents> }) =
className={cssStyle.clients + " " + (visible ? "" : cssStyle.hidden)}
color={"green"}
onClick={() => props.events.fire("action_toggle_client_list", { visible: !toggled })}>
<Translatable>{toggled ? "Hide clients in group" : "Show clients in group"}</Translatable>
{toggled ? <Translatable key={"hide"}>Hide clients in group</Translatable> : <Translatable key={"show"}>Show clients in group</Translatable>}
</Button>
};
@ -643,7 +643,9 @@ const PermissionGroupRow = (props: { events: Registry<PermissionEditorEvents>, g
>
<div className={cssStyle.columnName}>
<div className={"arrow " + (collapsed ? "right" : "down")} onClick={() => props.events.fire("action_toggle_group", { collapsed: !collapsed, groupId: props.group.groupId })} />
<div className={cssStyle.groupName} title={/* @tr-ignore */ tr(props.group.groupName)}><Translatable>{props.group.groupName}</Translatable></div>
<div className={cssStyle.groupName} title={/* @tr-ignore */ tr(props.group.groupName)}>
<Translatable trIgnore={true}>{props.group.groupName}</Translatable>
</div>
</div>
<div className={cssStyle.columnValue} />
<div className={cssStyle.columnSkip} />

View File

@ -77,15 +77,15 @@ class KeyActionEntry extends ReactComponentBase<KeyActionEntryProperties, KeyAct
render() {
let rightItem;
if(this.state.state === "loading") {
rightItem = <div key={"status-loading"} className={cssStyle.status}><Translatable message={"loading..."} /></div>;
rightItem = <div key={"status-loading"} className={cssStyle.status}><Translatable>loading...</Translatable></div>;
} else if(this.state.state === "applying") {
rightItem = <div key={"status-applying"} className={cssStyle.status}><Translatable message={"applying..."} /></div>;
rightItem = <div key={"status-applying"} className={cssStyle.status}><Translatable>applying...</Translatable></div>;
} else if(this.state.state === "loaded") {
rightItem = null;
if(this.state.assignedKey)
rightItem = <div className={cssStyle.key}>{ppt.key_description(this.state.assignedKey)}</div>;
} else {
rightItem = <div key={"status-error"} className={this.classList(cssStyle.status, cssStyle.error)}><Translatable message={this.state.error || "unknown error"} /></div>;
rightItem = <div key={"status-error"} className={this.classList(cssStyle.status, cssStyle.error)}><Translatable trIgnore={true}>{this.state.error || "unknown error"}</Translatable></div>;
}
return (
<div
@ -97,7 +97,7 @@ class KeyActionEntry extends ReactComponentBase<KeyActionEntryProperties, KeyAct
onContextMenu={e => this.onContextMenu(e)}
>
<IconRenderer icon={this.props.icon}/>
<a><Translatable message={this.props.description} /></a>
<a><Translatable trIgnore={true}>{this.props.description}</Translatable></a>
{rightItem}
</div>
);
@ -204,7 +204,7 @@ class KeyActionGroup extends ReactComponentBase<KeyActionGroupProperties, { coll
const result = [];
result.push(<div key={"category-" + this.props.id} className={this.classList(cssStyle.row, cssStyle.category)} onClick={() => this.toggleCollapsed()}>
<div className={this.classList("arrow", this.state.collapsed ? "right" : "down")} />
<a><Translatable message={this.props.name} /></a>
<a><Translatable trIgnore={true}>{this.props.name}</Translatable></a>
</div>);
result.push(...Object.keys(KeyTypes).filter(e => KeyTypes[e].category === this.props.id).map(e => (
@ -264,7 +264,7 @@ class ButtonBar extends ReactComponentBase<{ event_registry: Registry<KeyMapEven
return (
<div className={cssStyle.buttons}>
<Button color={"red"} disabled={!this.state.active_action || this.state.loading || !this.state.has_key} onClick={() => this.onButtonClick()}>
<Translatable message={"Clear Key"} />
<Translatable>Clear Key</Translatable>
</Button>
</div>
);
@ -304,7 +304,7 @@ export const KeyMapSettings = () => {
return (<>
<div key={"header"} className={cssStyle.header}>
<a><Translatable message={"Keymap"} /></a>
<a><Translatable>Keymap</Translatable></a>
</div>
<div key={"body"} className={cssStyle.containerList}>
<KeyActionList eventRegistry={events.current} />

View File

@ -300,7 +300,7 @@ export const NotificationSettings = () => {
return (<>
<div key={"header"} className={cssStyle.header}>
<a><Translatable message={"Notifications"} /></a>
<a><Translatable>Notifications</Translatable></a>
</div>
<div key={"body"} className={cssStyle.body}>
<EventTable events={events.current} />

View File

@ -68,6 +68,8 @@ html:root {
flex-grow: 0; /* we dont want a grow over the limit set within the content, but we want to shrink the content if necessary */
align-self: center;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: stretch;

View File

@ -3,7 +3,7 @@ import {parseMessageWithArguments} from "tc-shared/ui/frames/chat";
import {cloneElement} from "react";
let instances = [];
export class Translatable extends React.Component<{ message: string, children?: never } | { children: string }, { translated: string }> {
export class Translatable extends React.Component<{ children: string, __cacheKey?: string, trIgnore?: boolean }, { translated: string }> {
constructor(props) {
super(props);
@ -31,7 +31,7 @@ export class Translatable extends React.Component<{ message: string, children?:
}
}
export const VariadicTranslatable = (props: { text: string, children?: React.ReactElement[] | React.ReactElement }) => {
export const VariadicTranslatable = (props: { text: string, __cacheKey?: string, children?: React.ReactElement[] | React.ReactElement }) => {
const args = Array.isArray(props.children) ? props.children : [props.children];
const argsUseCount = [...new Array(args.length)].map(() => 0);

View File

@ -842,6 +842,7 @@ export class ChannelTree {
reset() {
batch_updates(BatchUpdateType.CHANNEL_TREE);
this.selection.clear_selection();
try {
this.selection.reset();

View File

@ -1,66 +0,0 @@
body {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: 0;
padding: 0;
background-color: #212529;
}
#container-success {
height: 100%;
display: flex;
flex-direction: column;
justify-content: stretch;
.container {
flex-grow: 0;
flex-shrink: 0;
align-self: center;
color: #999999;
background-color: #19191b;
border-radius: 5px;
overflow: hidden;
margin: 1em;
max-width: 80%;
width: 40em;
.content {
padding: 1em;
border-left: 2px solid #00d400;
a {
display: block;
font-size: 1.2em;
}
}
}
.spacer-top {
flex-shrink: 1;
flex-grow: 1;
height: 10%;
min-height: 0;
}
.spacer-bottom {
flex-shrink: 1;
flex-grow: 4;
height: 10%;
min-height: 0;
}
}

View File

@ -1,112 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Certificate callback</title>
<meta name="app-loader-target" content="certaccept">
<!-- required static style for the critical page and the enable javascript page -->
<style>
.fulloverlay {
z-index: 10000;
display: none;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: gray;
text-align: center;
}
.fulloverlay .container {
position: relative;
display: inline-block;
top: 20%;
}
#critical-load.shown {
display: block;
}
@media (max-height: 750px) {
#critical-load .container {
top: unset;
}
#critical-load {
font-size: .8rem;
flex-direction: column;
justify-content: center;
}
#critical-load.shown {
display: flex;
}
}
</style>
<x-properties id="properties" style="display: none"> </x-properties>
</head>
<body>
<div id="style">
<link rel="stylesheet" href="css/loader/loader.css">
</div>
<div id="scripts">
<script type="application/javascript" src="loader/loader_certaccept.min.js" defer></script>
<script type="application/javascript" src="loader/loader_certaccept.js" defer></script>
<script type="application/javascript" src="loader/loader.js?_<?php echo time() ?>" defer></script>
</div>
<!-- Loading screen -->
<div class="loader" id="loader-overlay">
<div class="half right"></div>
<div class="half left"></div>
<div class="bookshelf_wrapper">
<ul class="books_list">
<li class="book_item first"></li>
<li class="book_item second"></li>
<li class="book_item third"></li>
<li class="book_item fourth"></li>
<li class="book_item fifth"></li>
<li class="book_item sixth"></li>
</ul>
<div class="shelf"></div>
</div>
</div>
<!-- Critical load error -->
<div class="fulloverlay" id="critical-load">
<div class="container">
<img src="img/loading_error_right.svg" style="height: 12em">
<h1 class="error" style="color: red; margin-bottom: 0"></h1>
<h3 class="detail" style="margin-top: .5em"></h3>
</div>
</div>
<!-- success window -->
<div id="container-success">
<div class="spacer-top"></div>
<div class="container">
<div class="content">
<h1>Success!</h1>
<p>
<a>You've successfully accepted the certificate.</a>
<a>You will now connecting to the target server in the original tab.</a>
</p>
<p>
<a>This window will close automatically in <span id="time-left">X</span> seconds!</a>
</p>
</div>
</div>
<div class="spacer-bottom"></div>
</div>
</body>
</html>

View File

@ -1,68 +0,0 @@
import {settings, Settings} from "tc-shared/settings";
import * as loader from "tc-loader";
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import * as bipc from "tc-shared/ipc/BrowserIPC";
const is_debug = false; //TODO: Sync with loader!
function tr(text: string) { return text; }
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "certificate accept tester",
function: async () => {
const certificate_accept = settings.static_global(Settings.KEY_CERTIFICATE_CALLBACK, undefined);
const container_success = $("#container-success").hide();
if(!certificate_accept) {
loader.critical_error(tr("Missing certificate callback data"), tr("Please reconnect manually."));
throw "missing data";
}
log.info(LogCategory.IPC, tr("Using this instance as certificate callback. ID: %s"), certificate_accept);
try {
await bipc.getInstance().post_certificate_accpected(certificate_accept);
log.info(LogCategory.IPC, tr("Other instance has acknowledged out work. Closing this window."));
let seconds = 5;
let interval_id;
interval_id = setInterval(() => {
seconds--;
$("#time-left").text(seconds.toString());
if(seconds <= 0) {
clearTimeout(interval_id);
log.info(LogCategory.GENERAL, tr("Closing window"));
window.close();
return;
}
}, 1000);
container_success.show();
} catch(error) {
log.warn(LogCategory.IPC, tr("Failed to successfully post certificate accept status: %o"), error);
loader.critical_error(tr("Failed to emit success!"), tr("Please reconnect manually."));
}
},
priority: 10
});
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
});

View File

@ -17,7 +17,6 @@ const transformer = <T extends ts.Node>(context: ts.TransformationContext) => (r
function compile(fileNames: string[], options: ts.CompilerOptions): void {
const program: ts.Program = ts.createProgram(fileNames, options);
//(context: TransformationContext) => Transformer<T>;
let emitResult = program.emit(undefined, undefined, undefined, undefined, {
before: [ transformer ]
});

View File

@ -4,4 +4,6 @@ export interface TranslationEntry {
character: number;
message: string;
type: "call" | "jsx-translatable" | "jsx-variadic-translatable" | "js-template";
}

View File

@ -99,6 +99,7 @@ const translations: TranslationEntry[] = [];
config.source_files.forEach(file => {
if(config.verbose)
console.log("iterating over %s (%s)", file, path.resolve(path.normalize(config.base_bath + file)));
glob.sync(config.base_bath + file).forEach(_file => {
_file = path.normalize(_file);
for(const n_file of negate_files) {
@ -109,28 +110,17 @@ config.source_files.forEach(file => {
}
const file_type = path.extname(_file);
if(file_type == ".ts") {
if(file_type == ".ts" || file_type == ".tsx") {
let source = ts.createSourceFile(
_file,
readFileSync(_file).toString(),
ts.ScriptTarget.ES2016,
true
);
console.log(print(source));
console.log("Compile " + _file);
const messages = ts_generator.generate(source, {});
translations.push(...messages);
/*
messages.forEach(message => {
console.log(message);
});
console.log("PRINT!");
console.log(print(source));
*/
} else if(file_type == ".html") {
const messages = jsrender_generator.generate({}, {
content: readFileSync(_file).toString(),

View File

@ -44,7 +44,8 @@ export function generate(config: Configuration, file: File) : TranslationEntry[]
filename: file.name,
character: character + 1,
line: line + 1,
message: message
message: message,
type: "js-template"
});
base_index += match.index + match[0].length;

View File

@ -0,0 +1,8 @@
import {Translatable, VariadicTranslatable} from "../../../shared/js/ui/react-elements/i18n";
import * as React from "react";
function test() {
const element_0 = <Translatable>Hello World</Translatable>;
const element_1 = <Translatable>{"Hello World"}</Translatable>;
const element_2 = <VariadicTranslatable text={"XXX"}><>XXX</></VariadicTranslatable>;
}

View File

@ -1,6 +1,6 @@
import * as ts from "typescript";
import * as sha256 from "sha256";
import {SyntaxKind} from "typescript";
import * as sha256 from "sha256";
import {TranslationEntry} from "./generator";
export function generate(file: ts.SourceFile, config: Configuration) : TranslationEntry[] {
@ -26,23 +26,24 @@ function _generate(config: Configuration, node: ts.Node, result: TranslationEntr
call_analize:
if(ts.isCallExpression(node)) {
const call = <ts.CallExpression>node;
const call = node as ts.CallExpression;
const call_name = call.expression["escapedText"] as string;
if(call_name != "tr") break call_analize;
if(call_name != "tr") {
break call_analize;
}
console.dir(call_name);
console.log("Parameters: %o", call.arguments.length);
if(call.arguments.length > 1) {
report(call, "Invalid argument count");
node.forEachChild(n => _generate(config, n, result));
return;
break call_analize;
}
const object = <ts.StringLiteral>call.arguments[0];
if(object.kind != SyntaxKind.StringLiteral) {
report(call, "Invalid argument: " + SyntaxKind[object.kind]);
node.forEachChild(n => _generate(config, n, result));
return;
break call_analize;
}
console.log("Message: %o", object.text);
@ -58,13 +59,44 @@ function _generate(config: Configuration, node: ts.Node, result: TranslationEntr
filename: node.getSourceFile().fileName,
line: line,
character: character,
message: object.text
message: object.text,
type: "call"
});
} else if(node.kind === SyntaxKind.JsxElement) {
const element = node as ts.JsxElement;
const tag = element.openingElement.tagName as ts.Identifier;
if(tag.kind !== SyntaxKind.Identifier)
break call_analize;
if(tag.escapedText === "Translatable") {
if(element.children.length !== 1) {
report(element, "Invalid child count: " + element.children.length);
break call_analize;
}
const text = element.children[0] as ts.JsxText;
if(text.kind != SyntaxKind.JsxText) {
report(element, "Invalid child type " + SyntaxKind[text.kind]);
break call_analize;
}
const { line, character } = node.getSourceFile().getLineAndCharacterOfPosition(node.getStart());
result.push({
filename: node.getSourceFile().fileName,
line: line,
character: character,
message: text.text,
type: "jsx-translatable"
});
} else if(tag.escapedText === "VariadicTranslatable") {
}
}
node.forEachChild(n => _generate(config, n, result));
}
function create_unique_check(config: Configuration, source_file: ts.SourceFile, variable: ts.Expression, variables: { name: string, node: ts.Node }[]) : ts.Node[] {
function generateUniqueCheck(config: Configuration, source_file: ts.SourceFile, variable: ts.Expression, variables: { name: string, node: ts.Node }[]) : ts.Node[] {
const nodes: ts.Node[] = [], blocked_nodes: ts.Statement[] = [];
const node_path = (node: ts.Node) => {
@ -162,6 +194,7 @@ function create_unique_check(config: Configuration, source_file: ts.SourceFile,
return [...nodes, ts.createLabel(unique_check_label_name, ts.createBlock(blocked_nodes))];
}
let globalIdIndex = 0, globalIdTimestamp = Date.now();
export function transform(config: Configuration, context: ts.TransformationContext, source_file: ts.SourceFile) : TransformResult {
const cache: VolatileTransformConfig = {} as any;
cache.translations = [];
@ -207,32 +240,25 @@ export function transform(config: Configuration, context: ts.TransformationConte
}
}
const used_names = [config.variables.declarations, config.variables.declare_files];
const generated_names: { name: string, node: ts.Node }[] = [];
let generator_base = 0;
cache.name_generator = (config, node, message) => {
const characters = "0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
let name;
do {
name = "";
const generate_unique_name = config => {
if(config.module) {
name = "_" + generator_base++;
return "_" + generator_base++;
} else {
/* Global namespace. We've to generate a random name so no duplicates happen */
while(name.length < 8) {
const char = characters[Math.floor(Math.random() * characters.length)];
name = name + char;
if(name[0] >= '0' && name[0] <= '9')
name = name.substr(1) || "";
return "_" + globalIdTimestamp + "-" + ++globalIdIndex;
}
}
} while(used_names.findIndex(e => e === name) !== -1);
};
cache.name_generator = (config, node, message) => {
const name = generate_unique_name(config);
generated_names.push({name: name, node: node});
return name;
};
cache.tsx_name_generator = generate_unique_name;
function visit(node: ts.Node): ts.Node {
node = ts.visitEachChild(node, visit, context);
return replace_processor(config, cache, node, source_file);
@ -240,7 +266,7 @@ export function transform(config: Configuration, context: ts.TransformationConte
source_file = ts.visitNode(source_file, visit);
if(!config.module) {
/* we don't need a unique check because we're just in our scope */
extra_nodes.push(...create_unique_check(config, source_file, cache.nodes.translation_map, generated_names));
extra_nodes.push(...generateUniqueCheck(config, source_file, cache.nodes.translation_map, generated_names));
}
source_file = ts.updateSourceFileNode(source_file, [...(extra_nodes as any[]), ...source_file.statements], source_file.isDeclarationFile, source_file.referencedFiles, source_file.typeReferenceDirectives, source_file.hasNoDefaultLib, source_file.referencedFiles);
@ -251,14 +277,32 @@ export function transform(config: Configuration, context: ts.TransformationConte
return result;
}
const generate_jsx_cache_key = (cache: VolatileTransformConfig, config: Configuration, element: ts.JsxElement) => ts.updateJsxElement(
element,
ts.updateJsxOpeningElement(
element.openingElement,
element.openingElement.tagName,
element.openingElement.typeArguments,
ts.updateJsxAttributes(element.openingElement.attributes, [
...element.openingElement.attributes.properties,
ts.createJsxAttribute(ts.createIdentifier("__cacheKey"), ts.createStringLiteral(cache.tsx_name_generator(config)))
])
),
element.children,
element.closingElement
);
export function replace_processor(config: Configuration, cache: VolatileTransformConfig, node: ts.Node, source_file: ts.SourceFile) : ts.Node {
if(config.verbose)
console.log("Process %s", SyntaxKind[node.kind]);
if(!node.getSourceFile())
return node;
if(ts.isCallExpression(node)) {
const call = <ts.CallExpression>node;
const call_name = call.expression["escapedText"] as string;
if(call_name != "tr") return node;
if(!node.getSourceFile()) return node;
if(config.verbose) {
console.dir(call_name);
console.log("Parameters: %o", call.arguments.length);
@ -293,11 +337,82 @@ export function replace_processor(config: Configuration, cache: VolatileTransfor
message: object.text || object.getText(source_file),
line: line,
character: character,
filename: (source_file || {fileName: "unknown"}).fileName
filename: (source_file || {fileName: "unknown"}).fileName,
type: "call"
});
return ts.createBinary(variable_init, ts.SyntaxKind.BarBarToken, new_variable);
} else if(node.kind === SyntaxKind.JsxElement) {
const element = node as ts.JsxElement;
const tag = element.openingElement.tagName as ts.Identifier;
if(tag.kind !== SyntaxKind.Identifier)
return node;
const properties = {} as any;
element.openingElement.attributes.properties.forEach((e: ts.JsxAttribute) => {
if(e.kind !== SyntaxKind.JsxAttribute)
throw new Error(source_location(e) + ": Invalid jsx attribute kind " + SyntaxKind[e.kind]);
if(e.name.kind !== SyntaxKind.Identifier)
throw new Error(source_location(e) + ": Key isn't an identifier");
properties[e.name.escapedText as string] = e.initializer;
});
if(tag.escapedText === "Translatable") {
if('trIgnore' in properties && properties.trIgnore.kind === SyntaxKind.JsxExpression) {
const ignoreAttribute = properties.trIgnore as ts.JsxExpression;
if(ignoreAttribute.expression.kind === SyntaxKind.TrueKeyword)
return node;
else if(ignoreAttribute.expression.kind !== SyntaxKind.FalseKeyword)
throw new Error(source_location(ignoreAttribute) + ": Invalid attribute value of type " + SyntaxKind[ignoreAttribute.expression.kind]);
}
if(element.children.length !== 1)
throw new Error(source_location(element) + ": Element has been called with an invalid arguments (" + (element.children.length === 0 ? "too few" : "too many") + ")");
const text = element.children[0] as ts.JsxText;
if(text.kind != SyntaxKind.JsxText)
throw new Error(source_location(element) + ": Element has invalid children. Expected JsxText but got " + SyntaxKind[text.kind]);
let { line, character } = source_file.getLineAndCharacterOfPosition(node.getStart());
cache.translations.push({
message: text.text,
line: line,
character: character,
filename: (source_file || {fileName: "unknown"}).fileName,
type: "jsx-translatable"
});
return generate_jsx_cache_key(cache, config, element);
} else if(tag.escapedText === "VariadicTranslatable") {
if(!('text' in properties))
throw new Error(source_location(element) + ": Missing text to translate");
const textAttribute = properties["text"] as ts.JsxExpression;
if(textAttribute.kind !== SyntaxKind.JsxExpression)
throw new Error(source_location(element) + ": Text attribute has an invalid type. Expected JsxExpression but received " + SyntaxKind[textAttribute.kind]);
if(textAttribute.expression.kind !== SyntaxKind.StringLiteral)
throw new Error(source_location(element) + ": Text attribute value isn't a string literal. Expected StringLiteral but received " + SyntaxKind[textAttribute.expression.kind]);
const literal = textAttribute.expression as ts.StringLiteral;
let { line, character } = source_file.getLineAndCharacterOfPosition(node.getStart());
cache.translations.push({
message: literal.text,
line: line,
character: character,
filename: (source_file || {fileName: "unknown"}).fileName,
type: "jsx-variadic-translatable"
});
return generate_jsx_cache_key(cache, config, element);
}
}
return node;
}
export interface Configuration {
@ -326,5 +441,6 @@ interface VolatileTransformConfig {
};
name_generator: (config: Configuration, node: ts.Node, message: string) => string;
tsx_name_generator: (config: Configuration) => string;
translations: TranslationEntry[];
}

View File

@ -49,7 +49,6 @@ const transformer = (context: ts.TransformationContext) =>
for(const file of bundle.sourceFiles)
result.push(handler(file));
return ts.updateBundle(bundle, result as any, bundle.prepends as any);
} else if(rootNode.kind == ts.SyntaxKind.SourceFile) {
const file = rootNode as ts.SourceFile;

View File

@ -13,7 +13,6 @@
"index.ts",
"compiler.ts",
"jsrender_generator.ts",
"ts_generator.ts",
//"ttsc_transformer.ts"
"ts_generator.ts"
]
}

View File

@ -1,9 +0,0 @@
import transform, {Config} from "./ts_transformer";
import {PluginConfig} from "ttypescript/lib/PluginCreator";
import * as ts from "typescript";
export default function(program: ts.Program, config?: PluginConfig) : (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile {
const process_config: Config = config as any || {};
return transform(program, process_config);
}

View File

@ -17,6 +17,7 @@
"webpack/EJSGenerator.ts",
"webpack/WatLoader.ts",
"webpack/DevelBlocks.ts",
"webpack/EmscriptenLoader.ts",
"loader/IndexGenerator.ts",

View File

@ -12,7 +12,8 @@
"paths": {
"*": ["shared/declarations/*"],
"tc-shared/*": ["shared/js/*"],
"tc-backend/web/*": ["web/js/*"], /* specific web part */
"tc-backend/web/assembly/*": ["web/native-codec/generated/*"], /* specific web part */
"tc-backend/web/*": ["web/app/*"], /* specific web part */
"tc-backend/*": ["shared/backend.d/*"],
"tc-loader": ["loader/exports/loader.d.ts"],

8
web/.gitignore vendored
View File

@ -2,8 +2,8 @@ environment/
generated/
declarations/
css/**/*.css
css/**/*.css.map
app/**/*.css
app/**/*.css.map
js/**/*.js
js/**/*.js.map
app/**/*.js
app/**/*.js.map

139
web/app/audio/player.ts Normal file
View File

@ -0,0 +1,139 @@
/*
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();
*/
import {Device} from "tc-shared/audio/player";
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
const kAvoidAudioContextWarning = true;
let audioContextRequiredGesture = false;
let audioContextInstance: AudioContext;
let globalAudioGainInstance: GainNode;
let audioContextInitializeCallbacks: (() => any)[] = [];
let _master_volume: number = 1;
let _no_device = false;
export function initialize() : boolean {
context();
return true;
}
export function initialized() : boolean {
return !!audioContextInstance && audioContextInstance.state === 'running';
}
function fire_initialized() {
log.info(LogCategory.AUDIO, tr("Fire audio player initialized for %d listeners"), audioContextInitializeCallbacks.length);
while(audioContextInitializeCallbacks.length > 0)
audioContextInitializeCallbacks.pop_front()();
}
function createNewContext() {
audioContextInstance = new (window.webkitAudioContext || window.AudioContext)();
audioContextInitializeCallbacks.unshift(() => {
globalAudioGainInstance = audioContextInstance.createGain();
globalAudioGainInstance.gain.value = _no_device ? 0 : _master_volume;
globalAudioGainInstance.connect(audioContextInstance.destination);
});
if(audioContextInstance.state === "suspended") {
audioContextRequiredGesture = true;
return audioContextInstance;
} else if(audioContextInstance.state === "running") {
fire_initialized();
} else if(audioContextInstance.state === "closed") {
throw tr("Audio context has been closed");
} else {
throw tr("invalid audio context state");
}
}
export function context() : AudioContext {
if(audioContextInstance || kAvoidAudioContextWarning)
return audioContextInstance;
if(!audioContextInstance)
createNewContext();
return audioContextInstance;
}
export function get_master_volume() : number {
return _master_volume;
}
export function set_master_volume(volume: number) {
_master_volume = volume;
if(globalAudioGainInstance)
globalAudioGainInstance.gain.value = _no_device ? 0 : _master_volume;
}
export function destination() : AudioNode {
const ctx = context();
if(!ctx) throw tr("Audio player isn't initialized yet!");
return globalAudioGainInstance;
}
export function on_ready(cb: () => any) {
if(initialized())
cb();
else
audioContextInitializeCallbacks.push(cb);
}
export const WEB_DEVICE: Device = {
device_id: "default",
name: "default playback",
driver: 'Web Audio'
};
export function available_devices() : Promise<Device[]> {
return Promise.resolve([WEB_DEVICE])
}
export function set_device(device_id: string) : Promise<void> {
_no_device = !device_id;
globalAudioGainInstance.gain.value = _no_device ? 0 : _master_volume;
return Promise.resolve();
}
export function current_device() : Device {
return WEB_DEVICE;
}
export function initializeFromGesture() {
if(audioContextInstance) {
if(audioContextInstance.state !== "running") {
audioContextInstance.resume().then(() => {
fire_initialized();
}).catch(error => {
log.error(LogCategory.AUDIO, tr("Failed to initialize audio context instance from gesture: %o"), error);
});
}
} else {
createNewContext();
}
}

39
web/app/index.scss Normal file
View File

@ -0,0 +1,39 @@
:global {
html, body {
overflow-y: hidden;
height: 100%;
width: 100%;
position: fixed;
}
.app-container {
display: flex;
justify-content: stretch;
position: absolute;
top: 1.5em!important;
bottom: 0;
transition: all .5s linear;
.app {
width: 100%;
height: 100%;
margin: 0;
display: flex; flex-direction: column; resize: both;
}
}
$small_device: 650px;
@media only screen and (max-width: $small_device) {
html, body {
padding: 0!important;
}
.app-container {
bottom: 0;
}
}
}

5
web/app/index.ts Normal file
View File

@ -0,0 +1,5 @@
import "webrtc-adapter";
import "./index.scss";
import "./FileTransfer";
export = require("tc-shared/main");

View File

@ -44,7 +44,7 @@ export namespace codec {
const dummy_client_id = 0xFFEF;
this.ownCodec(dummy_client_id, _ => {}).then(codec => {
log.info(LogCategory.VOICE, tr("Release again! (%o)"), codec);
log.trace(LogCategory.VOICE, tr("Releasing codec instance (%o)"), codec);
this.releaseCodec(dummy_client_id);
}).catch(error => {
if(this._supported) {

View File

@ -110,7 +110,7 @@ registerCommandHandler("global-initialize", async () => {
});
registerCommandHandler("initialise", async data => {
console.log(prefix + "Initialize for codec %s", CodecType[data.type as CodecType]);
console.log(prefix + "Initialize codec worker for codec %s", CodecType[data.type as CodecType]);
if(!supported_types[data.type])
throw "type unsupported";

View File

@ -0,0 +1,222 @@
import * as cworker from "./CodecWorker";
import {CodecType} from "tc-backend/web/codec/Codec";
import {CodecWorker} from "./CodecWorker";
const WASM_ERROR_MESSAGES = [
'no native wasm support detected'
];
interface OpusModuleType extends EmscriptenModule {
cwrap: typeof cwrap;
}
let OpusModule = {} as OpusModuleType;
const runtimeInitializedPromise = new Promise((resolve, reject) => {
const cleanup = () => {
OpusModule['onRuntimeInitialized'] = undefined;
OpusModule['onAbort'] = undefined;
};
OpusModule['onRuntimeInitialized'] = () => {
cleanup();
resolve();
};
OpusModule['onAbort'] = error => {
cleanup();
let message;
if(error instanceof DOMException)
message = "DOMException (" + error.name + "): " + error.code + " => " + error.message;
else if(error instanceof Error) {
message = error.message;
} else {
message = error;
}
reject(message);
}
});
OpusModule['print'] = function() {
const message = arguments[0] as string;
if(message.startsWith("CompileError: WebAssembly.instantiate(): ")) {
/* Compile errors also get printed to error stream so no need to log them here */
return;
}
console.log(...arguments);
};
OpusModule['printErr'] = function() {
const message = arguments[0] as string;
if(message.startsWith("wasm streaming compile failed: ")) {
const error_message = message.substr(31);
if(error_message.startsWith("TypeError: Failed to execute 'compile' on 'WebAssembly': ")) {
console.warn("Failed to compile opus native code: %s", error_message.substr(57));
} else {
console.warn("Failed to prepare opus native code asynchronously: %s", error_message);
}
return;
} else if(message === "falling back to ArrayBuffer instantiation") {
/*
We suppress this message, because it comes directly after "wasm streaming compile failed:".
So if we want to print multiple lines we just have to edit the lines above.
*/
return;
} else if(message.startsWith("failed to asynchronously prepare wasm:")) {
/*
Will be handled via abort
*/
return;
} else if(message.startsWith("CompileError: WebAssembly.instantiate():")) {
/*
Will be handled via abort already
*/
return;
}
for(const suppress of WASM_ERROR_MESSAGES)
if((arguments[0] as string).indexOf(suppress) != -1)
return;
console.error(...arguments);
};
self.addEventListener("unhandledrejection", event => {
let message;
if(event.reason instanceof Error) {
if(event.reason.name !== "RuntimeError")
return;
else
message = event.reason.message;
} else if(typeof event.reason === "string") {
message = event.reason;
} else {
return;
}
if(message.startsWith("abort(CompileError: WebAssembly.instantiate():")) {
/*
We already handled that error via the Module['printErr'] callback.
*/
event.preventDefault();
return;
}
});
enum OpusType {
VOIP = 2048,
AUDIO = 2049,
RESTRICTED_LOWDELAY = 2051
}
const OPUS_ERROR_CODES = [
"One or more invalid/out of range arguments", //-1 (OPUS_BAD_ARG)
"Not enough bytes allocated in the target buffer", //-2 (OPUS_BUFFER_TOO_SMALL)
"An internal error was detected", //-3 (OPUS_INTERNAL_ERROR)
"The compressed data passed is corrupted", //-4 (OPUS_INVALID_PACKET)
"Invalid/unsupported request number", //-5 (OPUS_UNIMPLEMENTED)
"An encoder or decoder structure is invalid or already freed", //-6 (OPUS_INVALID_STATE)
"Memory allocation has failed" //-7 (OPUS_ALLOC_FAIL)
];
class OpusWorker implements CodecWorker {
private static readonly kProcessBufferSize = 4096 * 2;
private readonly channelCount: number;
private readonly type: OpusType;
private nativeHandle: any;
private fn_newHandle: any;
private fn_decode: any;
private fn_encode: any;
private fn_reset: any;
private nativeBufferPtr: number;
private processBuffer: Uint8Array;
constructor(channelCount: number, type: OpusType) {
this.channelCount = channelCount;
this.type = type;
}
name(): string {
return "Opus (Type: " + OpusWorker[this.type] + " Channels: " + this.channelCount + ")";
}
initialise?() : string {
this.fn_newHandle = OpusModule.cwrap("codec_opus_createNativeHandle", "number", ["number", "number"]);
this.fn_decode = OpusModule.cwrap("codec_opus_decode", "number", ["number", "number", "number", "number"]);
this.fn_encode = OpusModule.cwrap("codec_opus_encode", "number", ["number", "number", "number", "number"]);
this.fn_reset = OpusModule.cwrap("codec_opus_reset", "number", ["number"]);
this.nativeHandle = this.fn_newHandle(this.channelCount, this.type);
this.nativeBufferPtr = OpusModule._malloc(OpusWorker.kProcessBufferSize);
this.processBuffer = new Uint8Array(OpusModule.HEAPU8.buffer, this.nativeBufferPtr, OpusWorker.kProcessBufferSize);
return undefined;
}
deinitialise() { } //TODO
decode(buffer: Uint8Array, responseBuffer: (length: number) => Uint8Array): number | string {
if (buffer.byteLength > this.processBuffer.byteLength)
return "supplied data exceeds internal buffer";
this.processBuffer.set(buffer);
let result = this.fn_decode(this.nativeHandle, this.processBuffer.byteOffset, buffer.byteLength, this.processBuffer.byteLength);
if (result < 0) return OPUS_ERROR_CODES[-result] || "unknown decode error " + result;
const resultByteLength = result * this.channelCount * 4;
const resultBuffer = responseBuffer(resultByteLength);
resultBuffer.set(this.processBuffer.subarray(0, resultByteLength), 0);
return resultByteLength;
}
encode(buffer: Uint8Array, responseBuffer: (length: number) => Uint8Array): number | string {
if (buffer.byteLength > this.processBuffer.byteLength)
return "supplied data exceeds internal buffer";
this.processBuffer.set(buffer);
let result = this.fn_encode(this.nativeHandle, this.processBuffer.byteOffset, buffer.byteLength, this.processBuffer.byteLength);
if (result < 0) return OPUS_ERROR_CODES[-result] || "unknown encode error " + result;
const resultBuffer = responseBuffer(result);
resultBuffer.set(this.processBuffer.subarray(0, result), 0);
return result;
}
reset() {
this.fn_reset(this.nativeHandle);
}
}
cworker.register_codec(CodecType.OPUS_MUSIC, async () => new OpusWorker(2, OpusType.AUDIO));
cworker.register_codec(CodecType.OPUS_VOICE, async () => new OpusWorker(1, OpusType.VOIP));
cworker.set_initialize_callback(async () => {
try {
/* could be directly required since it's just a file reference */
const [ moduleCreator, wasmFile ] = await Promise.all([
import("tc-backend/web/assembly/TeaWeb-Worker-Codec-Opus.js"),
// @ts-ignore
import("tc-backend/web/assembly/TeaWeb-Worker-Codec-Opus.wasm")
]);
const module = moduleCreator(Object.assign(OpusModule, {
locateFile(file: string) {
return file.endsWith(".wasm") ? wasmFile.default : file;
}
}));
if(module !== OpusModule)
throw "invalid opus module object";
} catch (e) {
OpusModule['onAbort']("Failed to load native scripts");
}
await runtimeInitializedPromise;
return true;
});

View File

@ -1,37 +0,0 @@
html, body {
overflow-y: hidden;
height: 100%;
width: 100%;
position: fixed;
}
.app-container {
display: flex;
justify-content: stretch;
position: absolute;
top: 1.5em!important;
bottom: 0;
transition: all .5s linear;
.app {
width: 100%;
height: 100%;
margin: 0;
display: flex; flex-direction: column; resize: both;
}
}
$small_device: 650px;
@media only screen and (max-width: $small_device) {
html, body {
padding: 0!important;
}
.app-container {
bottom: 0;
}
}

View File

@ -1,37 +0,0 @@
#!/usr/bin/env bash
BASEDIR=$(dirname "$0")
cd "$BASEDIR"
source ../scripts/resolve_commands.sh
if [[ ! -e declarations/imports_shared.d.ts ]]; then
echo "generate the declarations first!"
echo "Execute: /scripts/build_declarations.sh"
exit 1
fi
if [[ ! -e ../shared/generated/shared.js ]]; then
echo "generate the shared packed file first!"
echo "Execute: /shared/generate_packed.sh"
exit 1
fi
execute_tsc -p tsconfig/tsconfig_packed.json
if [[ $? -ne 0 ]]; then
echo "Failed to build file"
exit 1
fi
echo "Merging files"
if [[ -e generated/client.js ]]; then
rm generated/client.js
fi
cat ../shared/generated/shared.js > generated/client.js
cat generated/web.js >> generated/client.js
if [[ -e generated/client.min.js ]]; then
rm generated/client.min.js
fi
npm run minify-web-rel-file `pwd`/generated/client.min.js `pwd`/generated/client.js

View File

View File

@ -1,128 +0,0 @@
/*
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();
*/
import {Device} from "tc-shared/audio/player";
import {LogCategory} from "tc-shared/log";
import * as log from "tc-shared/log";
import * as loader from "tc-loader";
let _globalContext: AudioContext;
let _global_destination: GainNode;
let _globalContextPromise: Promise<void>;
let _initialized_listener: (() => any)[] = [];
let _master_volume: number = 1;
let _no_device = false;
export function initialize() : boolean {
context();
return true;
}
export function initialized() : boolean {
return !!_globalContext && _globalContext.state === 'running';
}
function fire_initialized() {
log.info(LogCategory.AUDIO, tr("File initialized for %d listeners"), _initialized_listener.length);
while(_initialized_listener.length > 0)
_initialized_listener.pop_front()();
}
export function context() : AudioContext {
if(_globalContext && _globalContext.state != "suspended") return _globalContext;
if(!_globalContext)
_globalContext = new (window.webkitAudioContext || window.AudioContext)();
_initialized_listener.unshift(() => {
_global_destination = _globalContext.createGain();
_global_destination.gain.value = _no_device ? 0 : _master_volume;
_global_destination.connect(_globalContext.destination);
});
if(_globalContext.state == "suspended") {
if(!_globalContextPromise) {
(_globalContextPromise = _globalContext.resume()).then(() => {
fire_initialized();
}).catch(error => {
loader.critical_error("Failed to initialize global audio context! (" + error + ")");
});
}
_globalContext.resume(); //We already have our listener
return _globalContext;
}
if(_globalContext.state == "running") {
fire_initialized();
return _globalContext;
}
return _globalContext;
}
export function get_master_volume() : number {
return _master_volume;
}
export function set_master_volume(volume: number) {
_master_volume = volume;
if(_global_destination)
_global_destination.gain.value = _no_device ? 0 : _master_volume;
}
export function destination() : AudioNode {
const ctx = context();
if(!ctx) throw tr("Audio player isn't initialized yet!");
return _global_destination;
}
export function on_ready(cb: () => any) {
if(initialized())
cb();
else
_initialized_listener.push(cb);
}
export const WEB_DEVICE: Device = {
device_id: "default",
name: "default playback",
driver: 'Web Audio'
};
export function available_devices() : Promise<Device[]> {
return Promise.resolve([WEB_DEVICE])
}
export function set_device(device_id: string) : Promise<void> {
_no_device = !device_id;
_global_destination.gain.value = _no_device ? 0 : _master_volume;
return Promise.resolve();
}
export function current_device() : Device {
return WEB_DEVICE;
}
export function initializeFromGesture() {
context();
}

View File

@ -1,6 +0,0 @@
const webrtc_adapter = require("webrtc-adapter");
/* typescript keep alive */ let _x = (webrtc_adapter || "").toString();
const tc = require("tc-shared/main");
export = tc;
require("./FileTransfer");

View File

@ -1,215 +0,0 @@
import * as cworker from "./CodecWorker";
import {CodecType} from "tc-backend/web/codec/Codec";
import {CodecWorker} from "./CodecWorker";
declare global {
interface Window {
__init_em_module: ((Module: any) => void)[];
}
}
self.__init_em_module = self.__init_em_module || [];
const WASM_ERROR_MESSAGES = [
'no native wasm support detected'
];
let Module;
self.__init_em_module.push(m => Module = m);
const runtime_initialize_promise = new Promise((resolve, reject) => {
self.__init_em_module.push(Module => {
const cleanup = () => {
Module['onRuntimeInitialized'] = undefined;
Module['onAbort'] = undefined;
};
Module['onRuntimeInitialized'] = () => {
cleanup();
resolve();
};
Module['onAbort'] = error => {
cleanup();
let message;
if(error instanceof DOMException)
message = "DOMException (" + error.name + "): " + error.code + " => " + error.message;
else if(error instanceof Error) {
message = error.message;
} else {
message = error;
}
reject(message);
}
});
});
self.__init_em_module.push(Module => {
Module['print'] = function() {
const message = arguments[0] as string;
if(message.startsWith("CompileError: WebAssembly.instantiate(): ")) {
/* Compile errors also get printed to error stream so no need to log them here */
return;
}
console.log(...arguments);
};
Module['printErr'] = function() {
const message = arguments[0] as string;
if(message.startsWith("wasm streaming compile failed: ")) {
const error_message = message.substr(31);
if(error_message.startsWith("TypeError: Failed to execute 'compile' on 'WebAssembly': ")) {
console.warn("Failed to compile opus native code: %s", error_message.substr(57));
} else {
console.warn("Failed to prepare opus native code asynchronously: %s", error_message);
}
return;
} else if(message === "falling back to ArrayBuffer instantiation") {
/*
We suppress this message, because it comes directly after "wasm streaming compile failed:".
So if we want to print multiple lines we just have to edit the lines above.
*/
return;
} else if(message.startsWith("failed to asynchronously prepare wasm:")) {
/*
Will be handled via abort
*/
return;
} else if(message.startsWith("CompileError: WebAssembly.instantiate():")) {
/*
Will be handled via abort already
*/
return;
}
for(const suppress of WASM_ERROR_MESSAGES)
if((arguments[0] as string).indexOf(suppress) != -1)
return;
console.error(...arguments);
};
Module['locateFile'] = file => "../../wasm/" + file;
});
self.addEventListener("unhandledrejection", event => {
if(event.reason instanceof Error) {
if(event.reason.name === "RuntimeError" && event.reason.message.startsWith("abort(CompileError: WebAssembly.instantiate():")) {
/*
We already handled that error via the Module['printErr'] callback.
*/
event.preventDefault();
return;
}
}
});
enum OpusType {
VOIP = 2048,
AUDIO = 2049,
RESTRICTED_LOWDELAY = 2051
}
const OPUS_ERROR_CODES = [
"One or more invalid/out of range arguments", //-1 (OPUS_BAD_ARG)
"Not enough bytes allocated in the target buffer", //-2 (OPUS_BUFFER_TOO_SMALL)
"An internal error was detected", //-3 (OPUS_INTERNAL_ERROR)
"The compressed data passed is corrupted", //-4 (OPUS_INVALID_PACKET)
"Invalid/unsupported request number", //-5 (OPUS_UNIMPLEMENTED)
"An encoder or decoder structure is invalid or already freed", //-6 (OPUS_INVALID_STATE)
"Memory allocation has failed" //-7 (OPUS_ALLOC_FAIL)
];
class OpusWorker implements CodecWorker {
private static readonly kProcessBufferSize = 4096 * 2;
private readonly channelCount: number;
private readonly type: OpusType;
private nativeHandle: any;
private fn_newHandle: any;
private fn_decode: any;
private fn_encode: any;
private fn_reset: any;
private nativeBufferPtr: number;
private processBuffer: Uint8Array;
constructor(channelCount: number, type: OpusType) {
this.channelCount = channelCount;
this.type = type;
}
name(): string {
return "Opus (Type: " + OpusWorker[this.type] + " Channels: " + this.channelCount + ")";
}
initialise?() : string {
this.fn_newHandle = Module.cwrap("codec_opus_createNativeHandle", "number", ["number", "number"]);
this.fn_decode = Module.cwrap("codec_opus_decode", "number", ["number", "number", "number", "number"]);
this.fn_encode = Module.cwrap("codec_opus_encode", "number", ["number", "number", "number", "number"]);
this.fn_reset = Module.cwrap("codec_opus_reset", "number", ["number"]);
this.nativeHandle = this.fn_newHandle(this.channelCount, this.type);
this.nativeBufferPtr = Module._malloc(OpusWorker.kProcessBufferSize);
this.processBuffer = new Uint8Array(Module.HEAPU8.buffer, this.nativeBufferPtr, OpusWorker.kProcessBufferSize);
return undefined;
}
deinitialise() { } //TODO
decode(buffer: Uint8Array, responseBuffer: (length: number) => Uint8Array): number | string {
if (buffer.byteLength > this.processBuffer.byteLength)
return "supplied data exceeds internal buffer";
this.processBuffer.set(buffer);
let result = this.fn_decode(this.nativeHandle, this.processBuffer.byteOffset, buffer.byteLength, this.processBuffer.byteLength);
if (result < 0) return OPUS_ERROR_CODES[-result] || "unknown decode error " + result;
const resultByteLength = result * this.channelCount * 4;
const resultBuffer = responseBuffer(resultByteLength);
resultBuffer.set(this.processBuffer.subarray(0, resultByteLength), 0);
return resultByteLength;
}
encode(buffer: Uint8Array, responseBuffer: (length: number) => Uint8Array): number | string {
if (buffer.byteLength > this.processBuffer.byteLength)
return "supplied data exceeds internal buffer";
this.processBuffer.set(buffer);
let result = this.fn_encode(this.nativeHandle, this.processBuffer.byteOffset, buffer.byteLength, this.processBuffer.byteLength);
if (result < 0) return OPUS_ERROR_CODES[-result] || "unknown encode error " + result;
const resultBuffer = responseBuffer(result);
resultBuffer.set(this.processBuffer.subarray(0, result), 0);
return result;
}
reset() {
this.fn_reset(this.nativeHandle);
}
}
cworker.register_codec(CodecType.OPUS_MUSIC, async () => new OpusWorker(2, OpusType.AUDIO));
cworker.register_codec(CodecType.OPUS_VOICE, async () => new OpusWorker(1, OpusType.VOIP));
cworker.set_initialize_callback(async () => {
try {
require("tc-generated/codec/opus");
} catch (e) {
if(Module) {
if(typeof(Module['onAbort']) === "function") {
Module['onAbort']("Failed to load native scripts");
} /* else the error had been already handled because its a WASM error */
} else {
throw e;
}
}
if(!Module)
throw "Missing module handle";
await runtime_initialize_promise;
return true;
});

View File

@ -18,7 +18,7 @@ endfunction()
import_opus()
set(CMAKE_CXX_FLAGS "-O3 --llvm-lto 1 --memory-init-file 0 -s WASM=1 -s ASSERTIONS=1") # -s ALLOW_MEMORY_GROWTH=1 -O3
set(CMAKE_EXE_LINKER_FLAGS "-s EXPORTED_FUNCTIONS='[\"_malloc\", \"_free\"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]' -s ENVIRONMENT='worker' --pre-js ${CMAKE_SOURCE_DIR}/init.js") #
set(CMAKE_EXE_LINKER_FLAGS "-s MODULARIZE=1 -s EXPORTED_FUNCTIONS='[\"_malloc\", \"_free\"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]' -s ENVIRONMENT='worker' --pre-js ${CMAKE_SOURCE_DIR}/init.js") #
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/generated/")
add_executable(TeaWeb-Worker-Codec-Opus src/opus.cpp)

View File

@ -10,6 +10,7 @@ cd build_ || exit 1
emcmake cmake .. || {
echo "emcmake cmake failed for the first time, trying it again" #IDKW but sometimes it does not work the first try
cd . # Sometimes helps
emcmake cmake .. || {
echo "Failed to execute cmake"
exit 1

View File

@ -1,6 +1,6 @@
{
"source_files": [
"../js/**/*.ts"
"../app/**/*.ts"
],
"target_file": "../declarations/exports.d.ts"
}

View File

@ -8,6 +8,6 @@
"include": [
"../types",
"../declarations/imports_*.d.ts",
"../js/**/*.ts"
"../app/**/*.ts"
]
}

View File

@ -1,17 +0,0 @@
/* packed web project config */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "none",
"outFile": "../generated/web.js",
"allowJs": true
},
"exclude": [
"../js/workers"
],
"include": [
"../types",
"../declarations/imports_*.d.ts",
"../js/**/*.ts"
]
}

View File

@ -3,7 +3,7 @@ import * as config_base from "./webpack.config";
export = () => config_base.config("client").then(config => {
Object.assign(config.entry, {
"client-app": "./client/js/index.ts"
"client-app": "./client/app/index.ts"
});
Object.assign(config.resolve.alias, {
@ -12,10 +12,13 @@ export = () => config_base.config("client").then(config => {
"tc-backend": path.resolve(__dirname, "shared/backend.d"),
});
config.externals.push((context, request: string, callback) => {
if(!Array.isArray(config.externals))
throw "invalid config";
config.externals.push((context, request, callback) => {
if (request.startsWith("tc-backend/"))
return callback(null, `window["backend-loader"].require("${request}")`);
callback();
callback(undefined, undefined);
});
config.externals.push({ "jquery": "window.$" });

View File

@ -3,15 +3,18 @@ import * as config_base from "./webpack.config";
export = () => config_base.config("web").then(config => {
Object.assign(config.entry, {
"shared-app": "./web/js/index.ts"
"shared-app": "./web/app/index.ts"
});
Object.assign(config.resolve.alias, {
"tc-shared": path.resolve(__dirname, "shared/js"),
"tc-backend/web": path.resolve(__dirname, "web/js"),
"tc-backend": path.resolve(__dirname, "web/js"),
"tc-generated/codec/opus": path.resolve(__dirname, "web/native-codec/generated/TeaWeb-Worker-Codec-Opus.js"),
"tc-backend/web/assembly": path.resolve(__dirname, "web/native-codec/generated"),
"tc-backend/web": path.resolve(__dirname, "web/app"),
"tc-backend": path.resolve(__dirname, "web/app"),
});
config.node = config.node || {};
config.node["fs"] = "empty";
return Promise.resolve(config);
});

View File

@ -55,7 +55,7 @@ const isLoaderFile = (file: string) => {
return false;
};
export const config = async (target: "web" | "client") => { return {
export const config = async (target: "web" | "client"): Promise<Configuration> => { return {
entry: {
"loader": "./loader/app/index.ts",
"modal-external": "./shared/js/ui/react-elements/external-modal/PopoutEntrypoint.ts",
@ -75,15 +75,7 @@ export const config = async (target: "web" | "client") => { return {
base: __dirname
}),
new WorkerPlugin(),
//new BundleAnalyzerPlugin()
/*
new CircularDependencyPlugin({
//exclude: /a\.js|node_modules/,
failOnError: true,
allowAsyncCycles: false,
cwd: process.cwd(),
})
*/
//new BundleAnalyzerPlugin(),
isDevelopment ? undefined : new webpack.optimize.AggressiveSplittingPlugin({
minSize: 1024 * 8,
maxSize: 1024 * 128
@ -96,6 +88,7 @@ export const config = async (target: "web" | "client") => { return {
isDevelopment: isDevelopment
})
].filter(e => !!e),
module: {
rules: [
{
@ -138,7 +131,7 @@ export const config = async (target: "web" | "client") => { return {
options: {
context: __dirname,
colors: true,
getCustomTransformers(prog: ts.Program) {
getCustomTransformers: (prog: ts.Program) => {
return {
before: [trtransformer(prog, {
optimized: false,
@ -183,6 +176,15 @@ export const config = async (target: "web" | "client") => { return {
"./webpack/WatLoader.js"
]
},
{
test: /\.wasm$/,
type: 'javascript/auto',
loader: 'file-loader',
options: {
/* the public path will already be set by emscripten base path */
publicPath: './'
}
},
{
test: /\.svg$/,
loader: 'svg-inline-loader'
@ -197,7 +199,7 @@ export const config = async (target: "web" | "client") => { return {
},
externals: [
{"tc-loader": "window loader"}
] as any[],
],
output: {
filename: isDevelopment ? "[name].[contenthash].js" : "[contenthash].js",
chunkFilename: isDevelopment ? "[name].[contenthash].js" : "[contenthash].js",
@ -206,7 +208,7 @@ export const config = async (target: "web" | "client") => { return {
},
optimization: {
splitChunks: {
chunks: "all"
},
minimize: !isDevelopment,
minimizer: [new TerserPlugin()]