TeaWeb/shared/js/main.tsx

524 lines
20 KiB
TypeScript
Raw Normal View History

2020-03-30 11:44:18 +00:00
import * as loader from "tc-loader";
2020-09-24 13:51:22 +00:00
import {Stage} from "tc-loader";
2020-03-30 11:44:18 +00:00
import {settings, Settings} from "tc-shared/settings";
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import * as bipc from "./ipc/BrowserIPC";
2020-03-30 11:44:18 +00:00
import * as sound from "./sound/Sounds";
import * as i18n from "./i18n/localize";
import {tra} from "./i18n/localize";
2020-09-24 13:51:22 +00:00
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
2020-03-30 11:44:18 +00:00
import {createInfoModal} from "tc-shared/ui/elements/Modal";
import * as stats from "./stats";
import * as fidentity from "./profiles/identities/TeaForumIdentity";
2020-09-07 10:42:00 +00:00
import {defaultRecorder, RecorderProfile, setDefaultRecorder} from "tc-shared/voice/RecorderProfile";
2020-03-30 11:44:18 +00:00
import {spawnConnectModal} from "tc-shared/ui/modal/ModalConnect";
import * as top_menu from "./ui/frames/MenuBar";
import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
import {formatMessage} from "tc-shared/ui/frames/chat";
import {openModalNewcomer} from "tc-shared/ui/modal/ModalNewcomer";
import * as aplayer from "tc-backend/audio/player";
import * as ppt from "tc-backend/ppt";
2020-04-10 18:57:50 +00:00
import * as keycontrol from "./KeyControl";
2020-04-06 14:28:15 +00:00
import * as React from "react";
import * as ReactDOM from "react-dom";
import * as cbar from "./ui/frames/control-bar";
import * as global_ev_handler from "./events/ClientGlobalControlHandler";
2020-04-21 14:17:21 +00:00
import {global_client_actions} from "tc-shared/events/GlobalEvents";
import {FileTransferState, TransferProvider,} from "tc-shared/file/Transfer";
import {MenuEntryType, spawn_context_menu} from "tc-shared/ui/elements/ContextMenu";
import {copy_to_clipboard} from "tc-shared/utils/helpers";
2020-08-23 08:48:15 +00:00
import {checkForUpdatedApp} from "tc-shared/update";
import {setupJSRender} from "tc-shared/ui/jsrender";
2020-08-09 12:30:17 +00:00
import "svg-sprites/client-icons";
2020-04-06 14:28:15 +00:00
2020-03-30 11:44:18 +00:00
/* required import for init */
import "../css/load-css"
2020-07-19 16:49:00 +00:00
import "./proto";
import "./ui/elements/ContextDivider";
import "./ui/elements/Tab";
import "./connection/CommandHandler";
import "./connection/ConnectionBase";
import {ConnectRequestData} from "tc-shared/ipc/ConnectHandler";
2020-08-08 13:20:32 +00:00
import "./video-viewer/Controller";
import "./profiles/ConnectionProfile";
import "./update/UpdaterWeb";
import {defaultConnectProfile, findConnectProfile} from "tc-shared/profiles/ConnectionProfile";
2020-09-24 13:51:22 +00:00
import {server_connections} from "tc-shared/ConnectionManager";
import {initializeConnectionUIList} from "tc-shared/ui/frames/connection-handler-list/Controller";
import ContextMenuEvent = JQuery.ContextMenuEvent;
2020-09-16 18:37:39 +00:00
let preventWelcomeUI = false;
async function initialize() {
try {
await i18n.initialize();
} catch(error) {
console.error(tr("Failed to initialized the translation system!\nError: %o"), error);
2019-08-30 21:06:39 +00:00
loader.critical_error("Failed to setup the translation system");
return;
}
2019-04-04 19:47:52 +00:00
bipc.setup();
}
async function initialize_app() {
2020-09-24 13:51:22 +00:00
initializeConnectionUIList();
2020-09-24 09:24:31 +00:00
global_ev_handler.initialize(global_client_actions);
2020-04-06 14:28:15 +00:00
{
const bar = (
2020-04-06 14:29:40 +00:00
<cbar.ControlBar ref={cbar.react_reference()} multiSession={true} />
2020-04-06 14:28:15 +00:00
);
ReactDOM.render(bar, $(".container-control-bar")[0]);
}
2020-09-24 13:51:22 +00:00
/*
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "settings init",
priority: 10,
function: async () => global_ev_handler.load_default_states(client_control_events)
});
*/
2019-04-04 19:47:52 +00:00
2020-03-30 11:44:18 +00:00
if(!aplayer.initialize())
2019-04-04 19:47:52 +00:00
console.warn(tr("Failed to initialize audio controller!"));
2020-03-30 11:44:18 +00:00
aplayer.on_ready(() => {
if(aplayer.set_master_volume)
aplayer.on_ready(() => aplayer.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER) / 100));
else
2020-03-30 11:44:18 +00:00
log.warn(LogCategory.GENERAL, tr("Client does not support aplayer.set_master_volume()... May client is too old?"));
});
2019-04-04 19:47:52 +00:00
2020-09-07 10:42:00 +00:00
setDefaultRecorder(new RecorderProfile("default"));
defaultRecorder.initialize().catch(error => {
log.error(LogCategory.AUDIO, tr("Failed to initialize default recorder: %o"), error);
});
2019-04-04 19:47:52 +00:00
sound.initialize().then(() => {
2019-08-30 21:06:39 +00:00
log.info(LogCategory.AUDIO, tr("Sounds initialized"));
2019-04-04 19:47:52 +00:00
});
2019-08-21 08:00:01 +00:00
sound.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER_SOUNDS) / 100);
2019-04-04 19:47:52 +00:00
try {
await ppt.initialize();
} catch(error) {
2019-08-30 21:06:39 +00:00
log.error(LogCategory.GENERAL, tr("Failed to initialize ppt!\nError: %o"), error);
loader.critical_error(tr("Failed to initialize ppt!"));
return;
}
}
export function handle_connect_request(properties: ConnectRequestData, connection: ConnectionHandler) {
const profile_uuid = properties.profile || (defaultConnectProfile() || { id: 'default' }).id;
const profile = findConnectProfile(profile_uuid) || defaultConnectProfile();
const username = properties.username || profile.connectUsername();
const password = properties.password ? properties.password.value : "";
const password_hashed = properties.password ? properties.password.hashed : false;
if(profile && profile.valid()) {
2020-09-16 18:37:39 +00:00
settings.changeGlobal(Settings.KEY_USER_IS_NEW, false);
connection.startConnection(properties.address, profile, true, {
nickname: username,
password: password.length > 0 ? {
password: password,
hashed: password_hashed
} : undefined
});
server_connections.set_active_connection(connection);
} else {
2020-03-30 11:44:18 +00:00
spawnConnectModal({},{
url: properties.address,
enforce: true
}, {
profile: profile,
enforce: true
});
2019-09-18 23:25:57 +00:00
}
}
function main() {
2019-08-21 08:00:01 +00:00
/* initialize font */
{
2020-09-17 21:06:02 +00:00
const font = settings.static_global(Settings.KEY_FONT_SIZE);
2019-08-21 08:00:01 +00:00
$(document.body).css("font-size", font + "px");
2020-09-17 21:06:02 +00:00
settings.globalChangeListener(Settings.KEY_FONT_SIZE, value => {
$(document.body).css("font-size", value + "px");
})
2019-08-21 08:00:01 +00:00
}
/* context menu prevent */
$(document).on('contextmenu', (event: ContextMenuEvent) => {
if(event.isDefaultPrevented()) {
2019-08-21 08:00:01 +00:00
return;
}
2019-08-21 08:00:01 +00:00
if(event.target instanceof HTMLInputElement) {
if((!!event.target.value || __build.target === "client") && !event.target.disabled && !event.target.readOnly && event.target.type !== "number") {
spawn_context_menu(event.pageX, event.pageY, {
type: MenuEntryType.ENTRY,
name: tr("Copy"),
callback: () => {
copy_to_clipboard(event.target.value);
},
icon_class: "client-copy",
visible: !!event.target.value
}, {
type: MenuEntryType.ENTRY,
name: tr("Paste"),
callback: () => {
const { clipboard } = __non_webpack_require__('electron');
event.target.value = clipboard.readText();
},
icon_class: "client-copy",
visible: __build.target === "client",
});
}
event.preventDefault();
return;
}
if(settings.static_global(Settings.KEY_DISABLE_GLOBAL_CONTEXT_MENU)) {
2019-08-21 08:00:01 +00:00
event.preventDefault();
}
2019-08-21 08:00:01 +00:00
});
window.removeLoaderContextMenuHook();
2019-08-21 08:00:01 +00:00
top_menu.initialize();
const initial_handler = server_connections.spawn_server_connection();
initial_handler.acquireInputHardware().then(() => {});
2020-09-24 09:24:31 +00:00
server_connections.set_active_connection(initial_handler);
2020-09-24 13:51:22 +00:00
2018-04-11 15:56:09 +00:00
/** Setup the XF forum identity **/
2020-03-30 11:44:18 +00:00
fidentity.update_forum();
2020-04-10 18:57:50 +00:00
keycontrol.initialize();
2019-02-25 14:59:42 +00:00
stats.initialize({
verbose: true,
anonymize_ip_addresses: true,
volatile_collection_only: false
});
stats.register_user_count_listener(status => {
2019-08-30 21:06:39 +00:00
log.info(LogCategory.STATISTICS, tr("Received user count update: %o"), status);
2019-02-25 14:59:42 +00:00
});
2019-03-25 19:04:04 +00:00
server_connections.set_active_connection(server_connections.all_connections()[0]);
checkForUpdatedApp();
2019-09-18 23:25:57 +00:00
(window as any).test_download = async () => {
const connection = server_connections.active_connection();
const download = connection.fileManager.initializeFileDownload({
targetSupplier: async () => await TransferProvider.provider().createDownloadTarget(),
name: "HomeStudent2019Retail.img",
path: "/",
channel: 4
});
console.log("Download stated");
await download.awaitFinished();
console.log("Download finished (%s)", FileTransferState[download.transferState()]);
//console.log(await (download.target as ResponseTransferTarget).getResponse().blob());
console.log("Have buffer");
};
(window as any).test_upload = async () => {
const connection = server_connections.active_connection();
const download = connection.fileManager.initializeFileUpload({
source: async () => await TransferProvider.provider().createTextSource("Hello my lovely world...."),
name: "test-upload.txt",
path: "/",
channel: 4
});
console.log("Download stated");
await download.awaitFinished();
console.log("Download finished (%s)", FileTransferState[download.transferState()]);
//console.log(await (download.target as ResponseTransferTarget).getResponse().blob());
console.log("Have buffer");
};
2019-04-04 19:47:52 +00:00
2019-09-18 23:25:57 +00:00
/* schedule it a bit later then the main because the main function is still within the loader */
2019-08-21 08:00:01 +00:00
setTimeout(() => {
const connection = server_connections.active_connection();
2019-08-21 08:00:01 +00:00
/*
Modals.createChannelModal(connection, undefined, undefined, connection.permissions, (cb, perms) => {
});
*/
2019-08-30 21:06:39 +00:00
// Modals.openServerInfo(connection.channelTree.server);
2019-08-21 08:00:01 +00:00
//Modals.createServerModal(connection.channelTree.server, properties => Promise.resolve());
2019-09-12 21:59:35 +00:00
//Modals.openClientInfo(connection.getClient());
//Modals.openServerInfoBandwidth(connection.channelTree.server);
2019-09-18 23:25:57 +00:00
//Modals.openBanList(connection);
/*
Modals.spawnBanClient(connection,[
{name: "WolverinDEV", unique_id: "XXXX"},
{name: "WolverinDEV", unique_id: "XXXX"},
{name: "WolverinDEV", unique_id: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
{name: "WolverinDEV", unique_id: "YYY"}
], () => {});
*/
2019-09-12 21:59:35 +00:00
}, 4000);
//spawnSettingsModal("general-keymap");
2019-08-21 08:00:01 +00:00
//Modals.spawnKeySelect(console.log);
2019-09-12 21:59:35 +00:00
//Modals.spawnBookmarkModal();
/*
{
const modal = createModal({
header: tr("Test Net Graph"),
body: () => {
const canvas = $.spawn("canvas")
.css("position", "absolute")
.css({
top: 0,
bottom: 0,
right: 0,
left: 0
});
return $.spawn("div")
.css("height", "5em")
.css("width", "30em")
.css("position", "relative")
.append(canvas);
},
footer: null
});
const graph = new net.graph.Graph(modal.htmlTag.find("canvas")[0] as any);
graph.initialize();
modal.close_listener.push(() => graph.terminate());
modal.open();
}
*/
2020-06-15 14:56:05 +00:00
//setTimeout(() => spawnPermissionEditorModal(server_connections.active_connection()), 3000);
//setTimeout(() => spawnGroupCreate(server_connections.active_connection(), "server"), 3000);
2020-03-27 15:15:15 +00:00
2020-09-16 18:37:39 +00:00
if(settings.static_global(Settings.KEY_USER_IS_NEW) && !preventWelcomeUI) {
const modal = openModalNewcomer();
modal.close_listener.push(() => settings.changeGlobal(Settings.KEY_USER_IS_NEW, false));
2020-03-27 15:15:15 +00:00
}
2020-09-17 21:08:29 +00:00
//spawnGlobalSettingsEditor();
//spawnVideoPopout(server_connections.active_connection(), "https://www.youtube.com/watch?v=9683D18fyvs");
2018-04-19 16:42:34 +00:00
}
2019-04-04 19:47:52 +00:00
const task_teaweb_starter: loader.Task = {
name: "voice app starter",
2018-12-30 00:38:13 +00:00
function: async () => {
try {
2019-04-04 19:47:52 +00:00
await initialize_app();
2018-12-30 00:38:13 +00:00
main();
2020-03-30 11:44:18 +00:00
if(!aplayer.initialized()) {
2018-12-30 00:38:13 +00:00
log.info(LogCategory.VOICE, tr("Initialize audio controller later!"));
2020-03-30 11:44:18 +00:00
if(!aplayer.initializeFromGesture) {
console.error(tr("Missing aplayer.initializeFromGesture"));
2018-12-30 00:38:13 +00:00
} else
$(document).one('click', () => aplayer.initializeFromGesture());
2018-12-30 00:38:13 +00:00
}
} catch (ex) {
console.error(ex.stack);
if(ex instanceof ReferenceError || ex instanceof TypeError)
ex = ex.name + ": " + ex.message;
2019-08-30 21:06:39 +00:00
loader.critical_error("Failed to invoke main function:<br>" + ex);
2018-09-25 15:39:38 +00:00
}
2018-12-30 00:38:13 +00:00
},
priority: 10
2019-04-04 19:47:52 +00:00
};
const task_connect_handler: loader.Task = {
name: "Connect handler",
function: async () => {
const address = settings.static(Settings.KEY_CONNECT_ADDRESS, "");
const chandler = bipc.getInstanceConnectHandler();
if(settings.static(Settings.KEY_FLAG_CONNECT_DEFAULT, false) && address) {
const connect_data = {
address: address,
profile: settings.static(Settings.KEY_CONNECT_PROFILE, ""),
username: settings.static(Settings.KEY_CONNECT_USERNAME, ""),
password: {
value: settings.static(Settings.KEY_CONNECT_PASSWORD, ""),
hashed: settings.static(Settings.KEY_FLAG_CONNECT_PASSWORD, false)
}
};
2020-06-15 14:56:05 +00:00
if(chandler && !settings.static(Settings.KEY_CONNECT_NO_SINGLE_INSTANCE)) {
try {
await chandler.post_connect_request(connect_data, () => new Promise<boolean>((resolve, reject) => {
spawnYesNo(tr("Another TeaWeb instance is already running"), tra("Another TeaWeb instance is already running.{:br:}Would you like to connect there?"), response => {
resolve(response);
}, {
closeable: false
}).open();
}));
log.info(LogCategory.CLIENT, tr("Executed connect successfully in another browser window. Closing this window"));
const message =
"You're connecting to {0} within the other TeaWeb instance.{:br:}" +
"You could now close this page.";
createInfoModal(
tr("Connecting successfully within other instance"),
2020-03-31 13:19:53 +00:00
formatMessage(/* @tr-ignore */ tr(message), connect_data.address),
{
closeable: false,
footer: undefined
}
).open();
return;
} catch(error) {
log.info(LogCategory.CLIENT, tr("Failed to execute connect within other TeaWeb instance. Using this one. Error: %o"), error);
}
}
2020-09-16 18:37:39 +00:00
preventWelcomeUI = true;
loader.register_task(loader.Stage.LOADED, {
priority: 0,
function: async () => handle_connect_request(connect_data, server_connections.active_connection() || server_connections.spawn_server_connection()),
name: tr("default url connect")
});
}
if(chandler) {
/* no instance avail, so lets make us avail */
chandler.callback_available = data => {
return !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION);
};
chandler.callback_execute = data => {
2020-09-16 18:37:39 +00:00
preventWelcomeUI = true;
handle_connect_request(data, server_connections.spawn_server_connection());
return true;
}
}
loader.register_task(loader.Stage.LOADED, task_teaweb_starter);
},
priority: 10
};
2019-04-04 19:47:52 +00:00
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.
2019-04-04 19:47:52 +00:00
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);
try {
try {
await bipc.getInstance().post_certificate_accpected(certificate_accept);
2019-04-04 19:47:52 +00:00
} catch(e) {} //FIXME remove!
log.info(LogCategory.IPC, tr("Other instance has acknowledged out work. Closing this window."));
const seconds_tag = $.spawn("a");
let seconds = 5;
let interval_id;
interval_id = setInterval(() => {
seconds--;
seconds_tag.text(seconds.toString());
if(seconds <= 0) {
clearTimeout(interval_id);
log.info(LogCategory.GENERAL, tr("Closing window"));
window.close();
return;
}
}, 1000);
createInfoModal(
tr("Certificate acccepted successfully"),
formatMessage(tr("You've successfully accepted the certificate.{:br:}This page will close in {0} seconds."), seconds_tag),
2019-04-04 19:47:52 +00:00
{
closeable: false,
footer: undefined
}
).open();
return;
} catch(error) {
log.warn(LogCategory.IPC, tr("Failed to successfully post certificate accept status: %o"), error);
}
} else {
log.info(LogCategory.IPC, tr("We're not used to accept certificated. Booting app."));
}
*/
loader.register_task(loader.Stage.LOADED, task_connect_handler);
2019-04-04 19:47:52 +00:00
},
priority: 10
};
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "jrendere initialize",
function: async () => {
try {
2020-08-23 08:48:15 +00:00
if(!setupJSRender())
throw "invalid load";
} catch (error) {
2019-08-30 21:06:39 +00:00
loader.critical_error(tr("Failed to setup jsrender"));
console.error(tr("Failed to load jsrender! %o"), error);
return;
}
},
2020-09-24 13:51:22 +00:00
priority: 110
});
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
2019-04-04 19:47:52 +00:00
name: "app starter",
function: async () => {
try {
await initialize();
if(__build.target == "web") {
2019-04-04 19:47:52 +00:00
loader.register_task(loader.Stage.LOADED, task_certificate_callback);
} else {
2019-04-04 19:47:52 +00:00
loader.register_task(loader.Stage.LOADED, task_teaweb_starter);
}
2019-04-04 19:47:52 +00:00
} catch (ex) {
if(ex instanceof Error || typeof(ex.stack) !== "undefined")
console.error((tr || (msg => msg))("Critical error stack trace: %o"), ex.stack);
2019-04-04 19:47:52 +00:00
if(ex instanceof ReferenceError || ex instanceof TypeError)
ex = ex.name + ": " + ex.message;
2019-08-30 21:06:39 +00:00
loader.critical_error("Failed to boot app function:<br>" + ex);
2019-04-04 19:47:52 +00:00
}
},
priority: 1000
2018-10-06 13:13:45 +00:00
});
2018-12-30 00:38:13 +00:00
2020-03-30 11:44:18 +00:00
loader.register_task(loader.Stage.LOADED, {
name: "error task",
function: async () => {
if(Settings.instance.static(Settings.KEY_LOAD_DUMMY_ERROR, false)) {
loader.critical_error("The tea is cold!", "Argh, this is evil! Cold tea dosn't taste good.");
throw "The tea is cold!";
}
},
priority: 20
2020-09-24 13:51:22 +00:00
});
loader.register_task(Stage.JAVASCRIPT_INITIALIZING,{
name: "app init",
function: async () => {
try { //Initialize main template
const main = $("#tmpl_main").renderTag({
multi_session: !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION),
app_version: __build.version
}).dividerfy();
$("body").append(main);
} catch(error) {
log.error(LogCategory.GENERAL, error);
loader.critical_error(tr("Failed to setup main page!"));
return;
}
},
priority: 100
2020-04-01 13:40:45 +00:00
});