Adding support for poput able windows and watch 2 gather

This commit is contained in:
WolverinDEV 2020-08-08 01:03:54 +02:00
parent bbcef83443
commit bb41c6e2a0
23 changed files with 260 additions and 76 deletions

2
github

@ -1 +1 @@
Subproject commit 08b8d258af3fb887707511625542376e2f222067 Subproject commit 605c418ac7e5b158426cee96712e7bbc2033e0b6

View File

@ -73,6 +73,10 @@ function spawn_main_window(entry_point: string) {
}); });
main_window.webContents.on('new-window', (event, url_str, frameName, disposition, options, additionalFeatures) => { main_window.webContents.on('new-window', (event, url_str, frameName, disposition, options, additionalFeatures) => {
if(frameName.startsWith("__modal_external__")) {
return;
}
event.preventDefault(); event.preventDefault();
try { try {
let url: URL; let url: URL;

View File

@ -112,8 +112,9 @@ export namespace ui {
gui.setMenu(null); gui.setMenu(null);
gui.loadURL(url.pathToFileURL(path.join(path.dirname(module.filename), "ui", "loading_screen.html")).toString()) gui.loadURL(url.pathToFileURL(path.join(path.dirname(module.filename), "ui", "loading_screen.html")).toString())
gui.on('closed', () => { gui.on('closed', () => {
if(resolve) if(resolve) {
resolve(); resolve();
}
gui = undefined; gui = undefined;
cleanup(); cleanup();
}); });

View File

@ -383,7 +383,7 @@ async function load_files_from_dev_server(channel: string, stats_update: (messag
const max_simultaneously_downloads = 8; const max_simultaneously_downloads = 8;
let pending_files: VersionedFile[] = files.slice(0); let pending_files: VersionedFile[] = files.slice(0);
let current_downloads: {[key: string]:Promise<void>} = {}; let current_downloads: {[key: string]: Promise<void>} = {};
const update_download_status = () => { const update_download_status = () => {
const indicator = (pending_files.length + Object.keys(current_downloads).length) / files.length; const indicator = (pending_files.length + Object.keys(current_downloads).length) / files.length;
@ -426,6 +426,11 @@ async function load_files_from_dev_server(channel: string, stats_update: (messag
/* generate_tmp has already been called an its the file destination */ /* generate_tmp has already been called an its the file destination */
return path.join(await generate_tmp(), "index.html"); /* entry point */ return path.join(await generate_tmp(), "index.html"); /* entry point */
} }
async function stream_files_from_dev_server(channel: string, stats_update: (message: string, index: number) => any) : Promise<string> {
return remote_url() + "index.html";
}
async function load_bundles_ui_pack(channel: string, stats_update: (message: string, index: number) => any) : Promise<String> { async function load_bundles_ui_pack(channel: string, stats_update: (message: string, index: number) => any) : Promise<String> {
stats_update("Query local UI pack info", .33); stats_update("Query local UI pack info", .33);
const bundles_ui = await client_shipped_ui(); const bundles_ui = await client_shipped_ui();
@ -564,7 +569,8 @@ async function load_cached_or_remote_ui_pack(channel: string, stats_update: (mes
enum UILoaderMethod { enum UILoaderMethod {
PACK, PACK,
BUNDLED_PACK, BUNDLED_PACK,
RAW_FILES RAW_FILES,
DEVELOP_SERVER
} }
export async function load_files(channel: string, stats_update: (message: string, index: number) => any) : Promise<String> { export async function load_files(channel: string, stats_update: (message: string, index: number) => any) : Promise<String> {
@ -580,6 +586,9 @@ export async function load_files(channel: string, stats_update: (message: string
case UILoaderMethod.RAW_FILES: case UILoaderMethod.RAW_FILES:
return await load_files_from_dev_server(channel, stats_update); return await load_files_from_dev_server(channel, stats_update);
case UILoaderMethod.DEVELOP_SERVER:
return await stream_files_from_dev_server(channel, stats_update);
} }
} }

View File

@ -1,5 +1,4 @@
import * as path from "path"; import * as path from "path";
import * as util from "util";
import * as fs from "fs-extra"; import * as fs from "fs-extra";
import * as electron from "electron"; import * as electron from "electron";
@ -81,22 +80,24 @@ async function load_() : Promise<CacheFile> {
const file = path.join(cache_path(), "data.json"); const file = path.join(cache_path(), "data.json");
try { try {
if(!(await fs.pathExists(file))) if(!(await fs.pathExists(file))) {
return ui_cache_; return ui_cache_;
}
const data = await fs.readJSON(file) as CacheFile; const data = await fs.readJSON(file) as CacheFile;
if(!data) if(!data) {
throw "invalid data object"; throw "invalid data object";
else if(typeof data["version"] !== "number") } else if(typeof data["version"] !== "number") {
throw "invalid versions tag"; throw "invalid versions tag";
else if(data["version"] !== 2) { } else if(data["version"] !== 2) {
console.warn("UI cache file contains an old version. Ignoring file and may override with newer version."); console.warn("UI cache file contains an old version. Ignoring file and may override with newer version.");
return ui_cache_; return ui_cache_;
} }
/* validating data */ /* validating data */
if(!Array.isArray(data.cached_ui_packs)) if(!Array.isArray(data.cached_ui_packs)) {
throw "Invalid 'cached_ui_packs' entry within the UI cache file"; throw "Invalid 'cached_ui_packs' entry within the UI cache file";
}
return (ui_cache_ = data as CacheFile); return (ui_cache_ = data as CacheFile);
} catch(error) { } catch(error) {

View File

@ -27,7 +27,6 @@
justify-content: center; justify-content: center;
-ms-overflow-style: none; -ms-overflow-style: none;
-webkit-app-region: drag;
} }
img { img {
@ -75,7 +74,7 @@
background: whitesmoke; background: whitesmoke;
border: none; border: none;
width: 0%; width: 0;
height: 100%; height: 100%;
} }
@ -98,8 +97,8 @@
</head> </head>
<body> <body>
<div class="container-logo"> <div class="container-logo">
<img class="logo" src="img/logo.svg"> <img class="logo" src="img/logo.svg" alt="logo">
<img class="smoke" src="img/smoke.png"> <img class="smoke" src="img/smoke.png" alt="">
</div> </div>
<div class="container-info"> <div class="container-info">
<a id="loading-text">Loading... Please wait!</a> <a id="loading-text">Loading... Please wait!</a>

View File

@ -8,7 +8,7 @@
const target_file = remote.getGlobal("browser-root"); const target_file = remote.getGlobal("browser-root");
console.log("Navigate to %s", target_file); console.log("Navigate to %s", target_file);
if(fs.existsSync(target_file)) if(fs.existsSync(target_file) || target_file.startsWith("http://") || target_file.startsWith("https://"))
window.location.href = target_file; window.location.href = target_file;
else { else {
console.error("Failed to find target file!"); console.error("Failed to find target file!");

View File

@ -0,0 +1,60 @@
/* --------------- bootstrap --------------- */
import * as RequireProxy from "../renderer/RequireProxy";
import * as path from "path";
RequireProxy.initialize(path.join(__dirname, "backend-impl"));
/* --------------- entry point --------------- */
import * as loader from "tc-loader";
import {Stage} from "tc-loader";
import {Arguments, process_args} from "../shared/process-arguments";
import {remote} from "electron";
export function initialize(manifestTarget: string) {
console.log("Initializing native client for manifest target %s", manifestTarget);
const _impl = message => {
if(!process_args.has_flag(Arguments.DEBUG)) {
console.error("Displaying critical error: %o", message);
message = message.replace(/<br>/i, "\n");
const win = remote.getCurrentWindow();
win.webContents.openDevTools();
remote.dialog.showMessageBox({
type: "error",
buttons: ["exit"],
title: "A critical error happened!",
message: message
});
} else {
console.error("Received critical error: %o", message);
console.error("Ignoring error due to the debug mode");
}
};
if(window.impl_display_critical_error)
window.impl_display_critical_error = _impl;
else
window.displayCriticalError = _impl;
loader.register_task(loader.Stage.JAVASCRIPT, {
name: "teaclient jquery",
function: async () => {
window.$ = require("jquery");
window.jQuery = window.$;
Object.assign(window.$, window.jsrender = require('jsrender'));
},
priority: 80
});
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
name: "handler initialize",
priority: 100,
function: async () => {
await import("../renderer/Logger");
await import("../renderer/PersistentLocalStorage");
}
})
}

View File

@ -0,0 +1,84 @@
import {AbstractExternalModalController} from "tc-shared/ui/react-elements/external-modal/Controller";
import {setExternalModalControllerFactory} from "tc-shared/ui/react-elements/external-modal";
import * as ipc from "tc-shared/ipc/BrowserIPC";
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import * as loader from "tc-loader";
import {Stage} from "tc-loader";
import {BrowserWindow, remote} from "electron";
import {tr} from "tc-shared/i18n/localize";
import * as path from "path";
class ExternalModalController extends AbstractExternalModalController {
private window: BrowserWindow;
constructor(a, b, c) {
super(a, b, c);
}
protected async spawnWindow(): Promise<boolean> {
if(this.window) {
return true;
}
this.window = new remote.BrowserWindow({
parent: remote.getCurrentWindow(),
autoHideMenuBar: true,
webPreferences: {
nodeIntegration: true
},
icon: path.join(__dirname, "..", "..", "resources", "logo.ico"),
minWidth: 600,
minHeight: 300
});
const parameters = {
"loader-target": "manifest",
"chunk": "modal-external",
"modal-target": this.modalType,
"ipc-channel": this.ipcChannel.channelId,
"ipc-address": ipc.getInstance().getLocalAddress(),
//"disableGlobalContextMenu": is_debug ? 1 : 0,
//"loader-abort": is_debug ? 1 : 0,
};
const baseUrl = location.origin + location.pathname + "?";
const url = baseUrl + Object.keys(parameters).map(e => e + "=" + encodeURIComponent(parameters[e])).join("&");
try {
await this.window.loadURL(url);
} catch (error) {
log.warn(LogCategory.GENERAL, tr("Failed to load external modal main page: %o"), error);
this.window.close();
this.window = undefined;
return false;
}
this.window.show();
this.window.on("closed", () => {
this.window = undefined;
this.handleWindowClosed();
});
return true;
}
protected destroyWindow(): void {
if(this.window) {
this.window.close();
this.window = undefined;
}
}
protected focusWindow(): void {
this.window?.focus();
}
}
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
priority: 50,
name: "external modal controller factory setup",
function: async () => {
setExternalModalControllerFactory((modal, events, userData) => new ExternalModalController(modal, events, userData));
}
});

View File

@ -1,4 +1,6 @@
import * as electron from "electron"; import * as electron from "electron";
import * as loader from "tc-loader";
import {Stage} from "tc-loader";
import NativeImage = electron.NativeImage; import NativeImage = electron.NativeImage;
let _div: JQuery; let _div: JQuery;
@ -31,30 +33,34 @@ export function class_to_image(klass: string) : NativeImage {
}); });
} }
export async function initialize() { loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
if(!_div) { priority: 100,
_div = $(document.createElement("div")); name: "native icon sprite loader",
_div.css('display', 'none'); function: async () => {
_div.appendTo(document.body); if(!_div) {
_div = $(document.createElement("div"));
_div.css('display', 'none');
_div.appendTo(document.body);
}
const image = new Image();
image.src = 'img/client_icon_sprite.svg';
await new Promise((resolve, reject) => {
image.onload = resolve;
image.onerror = reject;
});
/* TODO: Get a size! */
const canvas = document.createElement("canvas");
canvas.width = 1024;
canvas.height = 1024;
canvas.getContext("2d").drawImage(image, 0, 0);
_cache_klass_map = {};
_icon_mash_url = canvas.toDataURL();
_icon_mask_img = electron.remote.nativeImage.createFromDataURL(_icon_mash_url);
} }
})
const image = new Image();
image.src = 'img/client_icon_sprite.svg';
await new Promise((resolve, reject) => {
image.onload = resolve;
image.onerror = reject;
});
/* TODO: Get a size! */
const canvas = document.createElement("canvas");
canvas.width = 1024;
canvas.height = 1024;
canvas.getContext("2d").drawImage(image, 0, 0);
_cache_klass_map = {};
_icon_mash_url = canvas.toDataURL();
_icon_mask_img = electron.remote.nativeImage.createFromDataURL(_icon_mash_url);
}
export function finalize() { export function finalize() {
_icon_mask_img = undefined; _icon_mask_img = undefined;

View File

@ -1,4 +1,4 @@
import {class_to_image} from "./icon-helper"; import {class_to_image} from "./IconHelper";
import * as electron from "electron"; import * as electron from "electron";
import * as mbar from "tc-shared/ui/frames/MenuBar"; import * as mbar from "tc-shared/ui/frames/MenuBar";
import {Arguments, process_args} from "../shared/process-arguments"; import {Arguments, process_args} from "../shared/process-arguments";

View File

@ -19,9 +19,7 @@ export async function initialize() {
try { try {
const data = await fs.readFile(path.join(SETTINGS_DIR, file)); const data = await fs.readFile(path.join(SETTINGS_DIR, file));
const decoded = JSON.parse(data.toString() || "{}"); _local_storage[key] = JSON.parse(data.toString() || "{}");
_local_storage[key] = decoded;
} catch(error) { } catch(error) {
const target_file = path.join(SETTINGS_DIR, file + "." + Date.now() + ".broken"); const target_file = path.join(SETTINGS_DIR, file + "." + Date.now() + ".broken");
console.error("Failed to load settings for %s: %o. Moving settings so the file does not get overridden. Target file: %s", key, error, target_file); console.error("Failed to load settings for %s: %o. Moving settings so the file does not get overridden. Target file: %s", key, error, target_file);

View File

@ -34,9 +34,13 @@ function proxied_load(request: string, parent?: NodeJS.Module) {
} }
function shared_backend_loader(request: string) { function shared_backend_loader(request: string) {
if(!request.startsWith("tc-backend/")) throw "invalid target"; if(!request.startsWith("tc-backend/"))
const target = request.substr(11); throw "invalid target";
if(!backend_root)
throw "backend is not available in this context";
const target = request.substr(11);
return require(path.join(backend_root, target)); return require(path.join(backend_root, target));
} }
@ -94,18 +98,27 @@ overrides.push({
name: "shared loader", name: "shared loader",
test: /^tc-shared\/.*/, test: /^tc-shared\/.*/,
callback: request => { callback: request => {
if(request.endsWith("/"))
return require(request + "index");
const webpack_path = path.dirname("shared/js/" + request.substr(10)); //FIXME: Get the prefix from a variable! const webpack_path = path.dirname("shared/js/" + request.substr(10)); //FIXME: Get the prefix from a variable!
const loader = require("tc-loader"); const loader = require("tc-loader");
const mapping = loader.module_mapping().find(e => e.application === "client-app"); //FIXME: Variable name! const mapping = loader.module_mapping().find(e => e.application === "client-app"); //FIXME: Variable name!
if(!mapping) throw "missing mapping"; if(!mapping) throw "missing mapping";
const entries = mapping.modules.filter(e => e.context === webpack_path); const entries = mapping.modules.filter(e => e.context.startsWith(webpack_path));
if(!entries.length) throw "unknown target path"; if(!entries.length) throw "unknown target path";
const basename = path.basename(request, path.extname(request)); const basename = path.basename(request, path.extname(request));
const entry = entries.find(e => path.basename(e.resource, path.extname(e.resource)) === basename); const entry = entries.find(e => path.basename(e.resource, path.extname(e.resource)) === basename);
if(!entry) throw "unknown import"; if(!entry) {
if(basename.indexOf(".") === -1 && !request.endsWith("/"))
return require(request + "/index");
debugger;
throw "unknown import (" + request + ")";
}
return window["shared-require"](entry.id); return window["shared-require"](entry.id);
} }

View File

@ -1,4 +1,4 @@
import * as handler from "../ppt"; import * as handler from "../PushToTalkHandler";
export const initialize = handler.initialize; export const initialize = handler.initialize;
export const finalize = handler.finalize; export const finalize = handler.finalize;

View File

@ -1,4 +1,4 @@
import {class_to_image} from "./icon-helper"; import {class_to_image} from "./IconHelper";
import * as contextmenu from "tc-shared/ui/elements/ContextMenu"; import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
import * as electron from "electron"; import * as electron from "electron";
const remote = electron.remote; const remote = electron.remote;

View File

@ -1,5 +1,5 @@
/* --------------- bootstrap --------------- */ /* --------------- bootstrap --------------- */
import * as rh from "./require-handler"; import * as RequireProxy from "./RequireProxy";
import * as crash_handler from "../crash_handler"; import * as crash_handler from "../crash_handler";
import * as path from "path"; import * as path from "path";
@ -9,6 +9,15 @@ import * as path from "path";
crash_handler.initialize_handler("renderer", is_electron_run); crash_handler.initialize_handler("renderer", is_electron_run);
} }
RequireProxy.initialize(path.join(__dirname, "backend-impl"));
/* --------------- main initialize --------------- */
import {Arguments, parse_arguments, process_args} from "../shared/process-arguments";
import * as electron from "electron";
import {remote} from "electron";
import * as loader from "tc-loader";
import ipcRenderer = electron.ipcRenderer;
/* some decls */ /* some decls */
declare global { declare global {
interface Window { interface Window {
@ -23,14 +32,6 @@ declare global {
open_connected_question: () => Promise<boolean>; open_connected_question: () => Promise<boolean>;
} }
} }
rh.initialize(path.join(__dirname, "backend-impl"));
/* --------------- main initialize --------------- */
import {Arguments, parse_arguments, process_args} from "../shared/process-arguments";
import * as electron from "electron";
import {remote} from "electron";
import * as loader from "tc-loader";
import ipcRenderer = electron.ipcRenderer;
/* we use out own jquery resource */ /* we use out own jquery resource */
loader.register_task(loader.Stage.JAVASCRIPT, { loader.register_task(loader.Stage.JAVASCRIPT, {
@ -153,12 +154,13 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
function: async () => { function: async () => {
/* all files which replaces a native driver */ /* all files which replaces a native driver */
try { try {
require("./version"); await import("./version");
require("./menu"); await import("./MenuBarHandler");
require("./context-menu"); await import("./context-menu");
require("./app_backend"); await import("./SingleInstanceHandler");
require("./icon-helper").initialize(); await import("./IconHelper");
require("./connection/FileTransfer"); await import("./connection/FileTransfer");
await import("./ExternalModalHandler");
} catch (error) { } catch (error) {
console.log(error); console.log(error);
window.displayCriticalError("Failed to load native extensions: " + error); window.displayCriticalError("Failed to load native extensions: " + error);

View File

@ -20,3 +20,5 @@ namespace forum {
} }
//window["forum"] = forum; //window["forum"] = forum;
export = {};

View File

@ -70,6 +70,8 @@ bool PortAudioRecord::impl_start(std::string &error) {
log_critical(category::audio, tr("Failed to close opened pa stream. This will cause memory leaks. Error: {}/{}"), err, Pa_GetErrorText(err)); log_critical(category::audio, tr("Failed to close opened pa stream. This will cause memory leaks. Error: {}/{}"), err, Pa_GetErrorText(err));
return false; return false;
} }
log_debug(category::audio, tr("Opened audio record stream for {} ({})"), this->info->name, Pa_GetHostApiInfo(this->info->hostApi)->name);
return true; return true;
} }
@ -80,6 +82,8 @@ void PortAudioRecord::impl_stop() {
auto error = Pa_CloseStream(this->stream); auto error = Pa_CloseStream(this->stream);
if(error != paNoError) if(error != paNoError)
log_error(category::audio, tr("Failed to close PA stream: {}"), error); log_error(category::audio, tr("Failed to close PA stream: {}"), error);
else
log_debug(category::audio, tr("Closed audio record stream for {} ({})"), this->info->name, Pa_GetHostApiInfo(this->info->hostApi)->name);
this->stream = nullptr; this->stream = nullptr;
} }

View File

@ -1,22 +1,18 @@
#include <v8.h> #include <v8.h>
#include <nan.h> #include <nan.h>
#include <node.h> #include <node.h>
#include <iostream>
#include <mutex> #include <mutex>
#include <event2/thread.h> #include <event2/thread.h>
#include <misc/digest.h> #include <misc/digest.h>
#include <NanStrings.h> #include <NanStrings.h>
#include "logger.h" #include "logger.h"
#include "include/NanException.h"
#include "include/NanEventCallback.h" #include "include/NanEventCallback.h"
#include "connection/ServerConnection.h" #include "connection/ServerConnection.h"
#include "connection/audio/VoiceConnection.h" #include "connection/audio/VoiceConnection.h"
#include "connection/audio/VoiceClient.h" #include "connection/audio/VoiceClient.h"
#include "connection/ft/FileTransferManager.h" #include "connection/ft/FileTransferManager.h"
#include "connection/ft/FileTransferObject.h" #include "connection/ft/FileTransferObject.h"
#include "audio/AudioOutput.h"
#include "audio/driver/AudioDriver.h"
#include "audio/js/AudioOutputStream.h" #include "audio/js/AudioOutputStream.h"
#include "audio/js/AudioPlayer.h" #include "audio/js/AudioPlayer.h"
#include "audio/js/AudioRecorder.h" #include "audio/js/AudioRecorder.h"
@ -93,20 +89,29 @@ NAN_MODULE_INIT(init) {
{ {
auto data = (uint8_t*) "Hello World"; auto data = (uint8_t*) "Hello World";
auto hash_result = digest::sha1(std::string("Hello World")); auto hash_result = digest::sha1(std::string("Hello World"));
if(hash_result.length() != 20) if(hash_result.length() != 20) {
Nan::ThrowError("digest::sha1 test failed"); Nan::ThrowError("digest::sha1 test failed");
return;
}
} }
{ {
auto data = (uint8_t*) "Hello World"; auto data = (uint8_t*) "Hello World";
uint8_t result[SHA_DIGEST_LENGTH]; uint8_t result[SHA_DIGEST_LENGTH];
digest::tomcrypt::sha1((char*) data, 11, result); digest::tomcrypt::sha1((char*) data, 11, result);
auto hash_result = std::string((const char*) result, SHA_DIGEST_LENGTH); auto hash_result = std::string((const char*) result, SHA_DIGEST_LENGTH);
log_error(category::connection, tr("Hash result: {}"), hash_result.length()); if(hash_result.length() != SHA_DIGEST_LENGTH) {
Nan::ThrowError("digest::tomcrypt::sha1 test failed");
return;
}
} }
string error; string error;
tc::audio::initialize(); //TODO: Notify JS when initialized? tc::audio::initialize(); //TODO: Notify JS when initialized?
node::AtExit([](auto){
tc::audio::finalize();
}, nullptr);
logger::info(category::general, "Loading crypt modules"); logger::info(category::general, "Loading crypt modules");
std::string descriptors = "LTGE"; std::string descriptors = "LTGE";
@ -238,7 +243,6 @@ NAN_MODULE_INIT(init) {
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferFileTarget::create)).ToLocalChecked() Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferFileTarget::create)).ToLocalChecked()
); );
//spawn_file_connection destroy_file_connection
JSTransfer::Init(ft_namespace); JSTransfer::Init(ft_namespace);
Nan::Set(ft_namespace, Nan::New<v8::String>("spawn_connection").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(JSTransfer::NewInstance)).ToLocalChecked()); Nan::Set(ft_namespace, Nan::New<v8::String>("spawn_connection").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(JSTransfer::NewInstance)).ToLocalChecked());
Nan::Set(ft_namespace, Nan::New<v8::String>("destroy_connection").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(JSTransfer::destory_transfer)).ToLocalChecked()); Nan::Set(ft_namespace, Nan::New<v8::String>("destroy_connection").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(JSTransfer::destory_transfer)).ToLocalChecked());

View File

@ -1,19 +1,16 @@
{ {
"name": "TeaClient", "name": "TeaClient",
"version": "1.4.8", "version": "1.4.9",
"description": "", "description": "",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"crash_handler": "electron . crash-handler", "crash_handler": "electron . crash-handler",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"start": "electron --js-flags='--expose-gc' --debug --dev-tools --disable-hardware-acceleration .",
"start-d": "electron . --disable-hardware-acceleration --debug -t -u http://clientapi.teaspeak.dev/",
"start-wd": "electron . --disable-hardware-acceleration --debug -t -s -u http://localhost/TeaWeb/client-api/environment/",
"start-d1": "electron . --disable-hardware-acceleration --debug -t --gdb -s -u=http://clientapi.teaspeak.dev/ --updater-ui-loader_type=0", "start-d1": "electron . --disable-hardware-acceleration --debug -t --gdb -s -u=http://clientapi.teaspeak.dev/ --updater-ui-loader_type=0",
"start-n": "electron . -t --disable-hardware-acceleration --no-single-instance -u=https://clientapi.teaspeak.de/ -d", "start-n": "electron . -t --disable-hardware-acceleration --no-single-instance -u=https://clientapi.teaspeak.de/ -d",
"start-nd": "electron . -t --disable-hardware-acceleration --no-single-instance -u=http://clientapi.teaspeak.dev/ -d", "start-nd": "electron . -t --disable-hardware-acceleration --no-single-instance -u=http://clientapi.teaspeak.dev/ -d",
"start-01": "electron . --updater-channel=test -u=http://dev.clientapi.teaspeak.de/ -d --updater-ui-loader_type=0 --updater-local-version=1.0.1", "start-01": "electron . --updater-channel=test -u=http://dev.clientapi.teaspeak.de/ -d --updater-ui-loader_type=0 --updater-local-version=1.0.1",
"start-s": "electron . --disable-hardware-acceleration --gdb --debug --updater-ui-loader_type=2 --updater-ui-ignore-version -t -u http://localhost:8081/", "start-s": "electron . --disable-hardware-acceleration --gdb --debug --updater-ui-loader_type=3 --updater-ui-ignore-version -t -u http://localhost:8081/",
"dtest": "electron . dtest", "dtest": "electron . dtest",
"compile-sass": "sass --update .:.", "compile-sass": "sass --update .:.",
"compile-tsc": "tsc", "compile-tsc": "tsc",