Merge remote-tracking branch 'remotes/origin/canary' into develop
# Conflicts: # ChangeLog.mdcanary
commit
6eed79ad61
11
ChangeLog.md
11
ChangeLog.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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/
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
339
file.ts
339
file.ts
|
@ -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,85 +533,57 @@ 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();
|
||||
const webpackwatcher = new watcher.WebPackWatcher(target);
|
||||
|
||||
try {
|
||||
if(flags.indexOf("--no-tsc") == -1)
|
||||
await tscwatcher.start();
|
||||
if(flags.indexOf("--no-webpack") == -1)
|
||||
await webpackwatcher.start();
|
||||
|
||||
const sasswatcher = new watcher.SASSWatcher();
|
||||
try {
|
||||
if(flags.indexOf("--no-sass") == -1)
|
||||
await sasswatcher.start();
|
||||
|
||||
const webpackwatcher = new watcher.WebPackWatcher(target);
|
||||
|
||||
try {
|
||||
if(flags.indexOf("--no-webpack") == -1)
|
||||
await webpackwatcher.start();
|
||||
|
||||
try {
|
||||
await server.launch(target === "client" ? CLIENT_APP_FILE_LIST : WEB_APP_FILE_LIST, {
|
||||
port: port,
|
||||
search_options: {
|
||||
source_path: __dirname,
|
||||
parameter: [],
|
||||
target: target,
|
||||
mode: "dev",
|
||||
serving: true
|
||||
}
|
||||
});
|
||||
} catch(error) {
|
||||
console.error("Failed to start server: %o", error instanceof Error ? error.message : error);
|
||||
return;
|
||||
await server.launch(target === "client" ? CLIENT_APP_FILE_LIST : WEB_APP_FILE_LIST, {
|
||||
port: port,
|
||||
search_options: {
|
||||
source_path: __dirname,
|
||||
parameter: [],
|
||||
target: target,
|
||||
mode: "dev",
|
||||
serving: true
|
||||
}
|
||||
|
||||
console.log("Server started on %d", port);
|
||||
console.log("To stop the session press ^K^C.");
|
||||
|
||||
await new Promise(resolve => process.once('SIGINT', resolve));
|
||||
console.log("Stopping session.");
|
||||
|
||||
try {
|
||||
await server.shutdown();
|
||||
} catch(error) {
|
||||
console.warn("Failed to stop web server: %o", error instanceof Error ? error.message : error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to start WebPack watcher: %o", error instanceof Error ? error.message : error);
|
||||
} finally {
|
||||
try {
|
||||
await webpackwatcher.stop();
|
||||
} catch(error) {
|
||||
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);
|
||||
}
|
||||
console.error("Failed to start server: %o", error instanceof Error ? error.message : error);
|
||||
return;
|
||||
}
|
||||
} catch(error) {
|
||||
console.error("Failed to start TSC watcher: %o", error instanceof Error ? error.message : error);
|
||||
|
||||
console.log("Server started on %d", port);
|
||||
console.log("To stop the session press ^K^C.");
|
||||
|
||||
await new Promise(resolve => process.once('SIGINT', resolve));
|
||||
console.log("Stopping session.");
|
||||
|
||||
try {
|
||||
await server.shutdown();
|
||||
} catch(error) {
|
||||
console.warn("Failed to stop web server: %o", error instanceof Error ? error.message : error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to start WebPack watcher: %o", error instanceof Error ? error.message : error);
|
||||
} finally {
|
||||
try {
|
||||
await tscwatcher.stop();
|
||||
await webpackwatcher.stop();
|
||||
} catch(error) {
|
||||
console.warn("Failed to stop TSC watcher: %o", error instanceof Error ? error.message : error);
|
||||
console.warn("Failed to stop WebPack 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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -23,7 +23,6 @@ export default class implements ApplicationLoader {
|
|||
});
|
||||
|
||||
/* required sadly */
|
||||
|
||||
loader.register_task(loader.Stage.SETUP, {
|
||||
name: "page setup",
|
||||
function: async () => {
|
||||
|
|
|
@ -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",
|
||||
|
|
13
package.json
13
package.json
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
|
@ -1,7 +1,7 @@
|
|||
.context-menu {
|
||||
overflow: visible;
|
||||
display: none;
|
||||
z-index: 2000;
|
||||
z-index: 120000;
|
||||
position: absolute;
|
||||
|
||||
.context-menu-container {
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -743,7 +743,8 @@ export class ConversationManager extends AbstractChatManager<ConversationUIEvent
|
|||
}
|
||||
|
||||
setSelectedConversation(id: number) {
|
||||
this.findOrCreateConversation(id);
|
||||
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"]),
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -230,8 +230,13 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
|
|||
throw tr("Missing Indexed DB support");
|
||||
}
|
||||
|
||||
await doOpenDatabase(false);
|
||||
log.debug(LogCategory.CHAT, tr("Successfully initialized private conversation history database"));
|
||||
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."));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>;
|
||||
};
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -842,6 +842,7 @@ export class ChannelTree {
|
|||
reset() {
|
||||
batch_updates(BatchUpdateType.CHANNEL_TREE);
|
||||
|
||||
this.selection.clear_selection();
|
||||
try {
|
||||
this.selection.reset();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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
|
||||
});
|
|
@ -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 ]
|
||||
});
|
||||
|
|
|
@ -4,4 +4,6 @@ export interface TranslationEntry {
|
|||
character: number;
|
||||
|
||||
message: string;
|
||||
|
||||
type: "call" | "jsx-translatable" | "jsx-variadic-translatable" | "js-template";
|
||||
}
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -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;
|
||||
|
||||
const generate_unique_name = config => {
|
||||
if(config.module) {
|
||||
return "_" + generator_base++;
|
||||
} else {
|
||||
return "_" + globalIdTimestamp + "-" + ++globalIdIndex;
|
||||
}
|
||||
};
|
||||
|
||||
cache.name_generator = (config, node, message) => {
|
||||
const characters = "0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
let name;
|
||||
do {
|
||||
name = "";
|
||||
|
||||
if(config.module) {
|
||||
name = "_" + 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) || "";
|
||||
}
|
||||
}
|
||||
} while(used_names.findIndex(e => e === name) !== -1);
|
||||
|
||||
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[];
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
"index.ts",
|
||||
"compiler.ts",
|
||||
"jsrender_generator.ts",
|
||||
"ts_generator.ts",
|
||||
//"ttsc_transformer.ts"
|
||||
"ts_generator.ts"
|
||||
]
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
"webpack/EJSGenerator.ts",
|
||||
"webpack/WatLoader.ts",
|
||||
"webpack/DevelBlocks.ts",
|
||||
"webpack/EmscriptenLoader.ts",
|
||||
|
||||
"loader/IndexGenerator.ts",
|
||||
|
||||
|
|
|
@ -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"],
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import "webrtc-adapter";
|
||||
import "./index.scss";
|
||||
import "./FileTransfer";
|
||||
|
||||
export = require("tc-shared/main");
|
|
@ -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) {
|
|
@ -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";
|
||||
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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");
|
|
@ -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;
|
||||
});
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"source_files": [
|
||||
"../js/**/*.ts"
|
||||
"../app/**/*.ts"
|
||||
],
|
||||
"target_file": "../declarations/exports.d.ts"
|
||||
}
|
|
@ -8,6 +8,6 @@
|
|||
"include": [
|
||||
"../types",
|
||||
"../declarations/imports_*.d.ts",
|
||||
"../js/**/*.ts"
|
||||
"../app/**/*.ts"
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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.$" });
|
||||
|
|
|
@ -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);
|
||||
});
|
|
@ -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()]
|
||||
|
|
Loading…
Reference in New Issue