Globally rendering the app via React

master
WolverinDEV 2021-01-05 18:13:57 +01:00
parent 0dbf991e36
commit 2211da243d
22 changed files with 447 additions and 333 deletions

View File

@ -3,6 +3,7 @@
/* FIXME: Resolve variable usage! */
html:root {
--side-background: #353535;
--side-info-background: #2e2e2e;
--side-info-shadow: rgba(0, 0, 0, 0.25);
--side-info-title: #8b8b8b;

View File

@ -7,8 +7,6 @@ $animation_length: .5s;
html:root {
--app-background: #1e1e1e;
--control-bar-background: #454545;
--chat-background: #353535;
--channel-tree-background: #353535;
--server-log-background: #353535;
@ -20,201 +18,16 @@ html:root {
--channel-chat-seperator-selected: #707070;
}
.app {
min-width: 600px;
min-height: 330px;
padding: 5px;
.container-app-main {
height: 100%;
width: 100%;
min-height: 500px;
margin-top: 5px;
position: relative;
display: flex;
flex-direction: column;
justify-content: stretch;
.container-channel-chat {
height: 80%; /* "default" settings */
width: 100%;
min-height: 27em; /* fits with the music bot interface */
min-width: 100px;
display: flex;
flex-direction: row;
justify-content: stretch;
& > * {
height: 100%;
min-height: 250px;
border-radius: 5px;
}
> .container-channel-tree {
width: 50%; /* "default" settings */
height: 100%;
background: var(--channel-tree-background);
min-width: 200px;
display: flex;
flex-direction: column;
justify-content: stretch;
min-height: 100px;
overflow: hidden;
> .hostbanner {
flex-grow: 0;
flex-shrink: 0;
max-height: 9em; /* same size as the info pannel */
display: flex;
flex-direction: column;
justify-content: stretch;
}
> .channel-tree {
min-height: 5em;
flex-grow: 1;
flex-shrink: 1;
.channel-tree-container {
height: 100%;
flex-grow: 1;
flex-shrink: 1;
overflow: hidden;
overflow-y: auto;
}
}
}
> .container-chat {
width: 50%; /* "default" settings */
height: 100%;
background: var(--chat-background);
min-width: 350px;
display: flex;
flex-direction: column;
justify-content: stretch;
}
}
> .container-bottom {
height: 20%;
min-height: 1.5em;
width: 100%;
display: flex;
flex-direction: column;
justify-content: stretch;
> .container-server-log {
display: flex;
flex-direction: column;
justify-content: stretch;
flex-shrink: 1;
flex-grow: 1;
min-height: 0;
width: 100%;
overflow: hidden;
border-radius: 5px 5px 0 0;
padding-right: 5px;
padding-left: 5px;
background: var(--server-log-background);
}
> .container-footer {
flex-shrink: 0;
flex-grow: 0;
height: 1.5em;
background: var(--footer-background);
color: var(--footer-text);
border-radius: 0 0 5px 5px;
padding-right: 5px;
padding-left: 5px;
padding-top: 2px;
-webkit-box-shadow: inset 0 2px 5px 0 rgba(0,0,0,0.125);
-moz-box-shadow: inset 0 2px 5px 0 rgba(0,0,0,0.125);
box-shadow: inset 0 2px 5px 0 rgba(0,0,0,0.125);
display: flex;
flex-direction: row;
justify-content: space-between;
> * {
align-self: center;
}
> span {
display: flex;
flex-direction: row;
justify-content: flex-start;
> a {
margin-right: .5em;
}
}
a[href], a[href]:visited {
color: var(--footer-text)!important;
}
}
}
}
.container-control-bar {
z-index: 200;
flex-shrink: 0;
border-radius: 5px;
height: 2em;
width: 100%;
background-color: var(--control-bar-background);
display: flex;
flex-direction: column;
justify-content: center;
}
.hide-small {
.hide-small {
opacity: 1;
transition: opacity $animation_length linear;
}
}
.show-small {
.show-small {
display: none;
opacity: 0;
transition: opacity $animation_length linear;
}
}
.app-container {

View File

@ -7,36 +7,6 @@
</head>
<body>
<script class="jsrender-template" id="tmpl_main" type="text/html">
<div class="app-container">
<div class="app">
<!-- navigation bar -->
<div class="container-control-bar">
<div id="control_bar" class="control_bar">
</div>
</div>
<div class="container-connection-handlers" id="connection-handler-list"></div>
<div class="container-app-main">
<div class="container-channel-video" id="channel-video"></div>
<div class="container-channel-chat">
<!-- Channel tree -->
<div class="container-channel-tree">
<div class="hostbanner" id="hostbanner"></div>
<div class="channel-tree" id="channelTree"></div>
</div>
<div class="container-seperator vertical" seperator-id="seperator-channel-chat"></div>
<!-- Chat window -->
<div class="container-chat" id="chat"></div>
</div>
<div class="container-seperator horizontal" seperator-id="seperator-main-log"></div>
<div class="container-bottom">
<div class="container-server-log" id="server-log"></div>
<div class="container-footer" id="container-footer">
</div>
</div> <!-- Selection info -->
</div>
</div>
</div>
<div id="contextMenu" class="context-menu"></div>
<div class="overlay-image-preview hidden" id="overlay-image-preview">
<div class="container-menu-bar">

View File

@ -133,7 +133,7 @@ export interface ConnectParameters {
export class ConnectionHandler {
readonly handlerId: string;
private readonly event_registry: Registry<ConnectionEvents>;
private readonly events_: Registry<ConnectionEvents>;
channelTree: ChannelTree;
connection_state: ConnectionState = ConnectionState.UNCONNECTED;
@ -159,13 +159,13 @@ export class ConnectionHandler {
private clientInfoManager: SelectedClientInfo;
private _clientId: number = 0;
private localClientId: number = 0;
private localClient: LocalClientEntry;
private _reconnect_timer: number;
private _reconnect_attempt: boolean = false;
private autoReconnectTimer: number;
private autoReconnectAttempt: boolean = false;
private _connect_initialize_id: number = 1;
private connectAttemptId: number = 1;
private echoTestRunning = false;
private pluginCmdRegistry: PluginCmdRegistry;
@ -188,8 +188,8 @@ export class ConnectionHandler {
constructor() {
this.handlerId = guid();
this.event_registry = new Registry<ConnectionEvents>();
this.event_registry.enableDebug("connection-handler");
this.events_ = new Registry<ConnectionEvents>();
this.events_.enableDebug("connection-handler");
this.settings = new ServerSettings();
@ -225,10 +225,10 @@ export class ConnectionHandler {
this.localClient = new LocalClientEntry(this);
this.localClient.channelTree = this.channelTree;
this.event_registry.register_handler(this);
this.events_.register_handler(this);
this.pluginCmdRegistry.registerHandler(new W2GPluginCmdHandler());
this.events().fire("notify_handler_initialized");
this.events_.fire("notify_handler_initialized");
}
initialize_client_state(source?: ConnectionHandler) {
@ -242,12 +242,12 @@ export class ConnectionHandler {
}
events() : Registry<ConnectionEvents> {
return this.event_registry;
return this.events_;
}
async startConnection(addr: string, profile: ConnectionProfile, user_action: boolean, parameters: ConnectParameters) {
this.cancel_reconnect(false);
this._reconnect_attempt = parameters.auto_reconnect_attempt || false;
this.autoReconnectAttempt = parameters.auto_reconnect_attempt || false;
this.handleDisconnect(DisconnectReason.REQUESTED);
let server_address: ServerAddress = {
@ -298,11 +298,11 @@ export class ConnectionHandler {
if(server_address.host === "localhost") {
server_address.host = "127.0.0.1";
} else if(dns.supported() && !server_address.host.match(Regex.IP_V4) && !server_address.host.match(Regex.IP_V6)) {
const id = ++this._connect_initialize_id;
const id = ++this.connectAttemptId;
this.log.log("connection.hostname.resolve", {});
try {
const resolved = await dns.resolve_address(server_address, { timeout: 5000 }) || {} as any;
if(id != this._connect_initialize_id)
if(id != this.connectAttemptId)
return; /* we're old */
server_address.host = typeof(resolved.target_ip) === "string" ? resolved.target_ip : server_address.host;
@ -314,7 +314,7 @@ export class ConnectionHandler {
}
});
} catch(error) {
if(id != this._connect_initialize_id)
if(id != this.connectAttemptId)
return; /* we're old */
this.handleDisconnect(DisconnectReason.DNS_FAILED, error);
@ -348,7 +348,7 @@ export class ConnectionHandler {
}
getClient() : LocalClientEntry { return this.localClient; }
getClientId() { return this._clientId; }
getClientId() { return this.localClientId; }
getPrivateConversations() : PrivateConversationManager {
return this.privateConversations;
@ -371,7 +371,7 @@ export class ConnectionHandler {
}
initializeLocalClient(clientId: number, acceptedName: string) {
this._clientId = clientId;
this.localClientId = clientId;
this.localClient["_clientId"] = clientId;
this.channelTree.registerClient(this.localClient);
@ -477,7 +477,7 @@ export class ConnectionHandler {
private _certificate_modal: Modal;
handleDisconnect(type: DisconnectReason, data: any = {}) {
this._connect_initialize_id++;
this.connectAttemptId++;
let auto_reconnect = false;
switch (type) {
@ -498,7 +498,7 @@ export class ConnectionHandler {
this.sound.play(Sound.CONNECTION_REFUSED);
break;
case DisconnectReason.CONNECT_FAILURE:
if(this._reconnect_attempt) {
if(this.autoReconnectAttempt) {
auto_reconnect = true;
break;
}
@ -574,7 +574,7 @@ export class ConnectionHandler {
break;
case DisconnectReason.CONNECTION_CLOSED:
log.error(LogCategory.CLIENT, tr("Lost connection to remote server!"));
if(!this._reconnect_attempt) {
if(!this.autoReconnectAttempt) {
createErrorModal(
tr("Connection closed"),
tr("The connection was closed by remote host")
@ -683,8 +683,8 @@ export class ConnectionHandler {
const server_address = this.serverConnection.remote_address();
const profile = this.serverConnection.handshake_handler().profile;
this._reconnect_timer = setTimeout(() => {
this._reconnect_timer = undefined;
this.autoReconnectTimer = setTimeout(() => {
this.autoReconnectTimer = undefined;
this.log.log("reconnect.execute", {});
log.info(LogCategory.NETWORKING, tr("Reconnecting..."));
@ -696,16 +696,16 @@ export class ConnectionHandler {
}
cancel_reconnect(log_event: boolean) {
if(this._reconnect_timer) {
if(this.autoReconnectTimer) {
if(log_event) this.log.log("reconnect.canceled", {});
clearTimeout(this._reconnect_timer);
this._reconnect_timer = undefined;
clearTimeout(this.autoReconnectTimer);
this.autoReconnectTimer = undefined;
}
}
private on_connection_state_changed(old_state: ConnectionState, new_state: ConnectionState) {
console.log("From %s to %s", ConnectionState[old_state], ConnectionState[new_state]);
this.event_registry.fire("notify_connection_state_changed", {
this.events_.fire("notify_connection_state_changed", {
oldState: old_state,
newState: new_state
});
@ -805,14 +805,14 @@ export class ConnectionHandler {
if(shouldRecord || this.echoTestRunning) {
if(this.getInputHardwareState() !== InputHardwareState.START_FAILED) {
this.startVoiceRecorder(Date.now() - this._last_record_error_popup > 10 * 1000).then(() => {
this.event_registry.fire("notify_state_updated", { state: "microphone" });
this.events_.fire("notify_state_updated", { state: "microphone" });
});
}
} else {
currentInput.stop().catch(error => {
logWarn(LogCategory.AUDIO, tr("Failed to stop the microphone input recorder: %o"), error);
}).then(() => {
this.event_registry.fire("notify_state_updated", { state: "microphone" });
this.events_.fire("notify_state_updated", { state: "microphone" });
});
}
}
@ -1017,7 +1017,7 @@ export class ConnectionHandler {
}
destroy() {
this.event_registry.unregister_handler(this);
this.events_.unregister_handler(this);
this.cancel_reconnect(true);
this.pluginCmdRegistry?.destroy();
@ -1093,7 +1093,7 @@ export class ConnectionHandler {
this.sound.play(muted ? Sound.MICROPHONE_MUTED : Sound.MICROPHONE_ACTIVATED);
}
this.update_voice_status();
this.event_registry.fire("notify_state_updated", { state: "microphone" });
this.events_.fire("notify_state_updated", { state: "microphone" });
}
toggleMicrophone() { this.setMicrophoneMuted(!this.isMicrophoneMuted()); }
@ -1104,7 +1104,7 @@ export class ConnectionHandler {
if(this.client_status.output_muted === muted) return;
if(muted && !dontPlaySound) this.sound.play(Sound.SOUND_MUTED); /* play the sound *before* we're setting the muted state */
this.client_status.output_muted = muted;
this.event_registry.fire("notify_state_updated", { state: "speaker" });
this.events_.fire("notify_state_updated", { state: "speaker" });
if(!muted && !dontPlaySound) this.sound.play(Sound.SOUND_ACTIVATED); /* play the sound *after* we're setting we've unmuted the sound */
this.update_voice_status();
this.serverConnection.getVoiceConnection().stopAllVoiceReplays();
@ -1129,7 +1129,7 @@ export class ConnectionHandler {
} else {
this.channelTree.unsubscribe_all_channels();
}
this.event_registry.fire("notify_state_updated", { state: "subscribe" });
this.events_.fire("notify_state_updated", { state: "subscribe" });
}
isSubscribeToAllChannels() : boolean { return this.client_status.channel_subscribe_all; }
@ -1156,7 +1156,7 @@ export class ConnectionHandler {
this.log.log("error.custom", {message: tr("Failed to update away status.")});
});
this.event_registry.fire("notify_state_updated", {
this.events_.fire("notify_state_updated", {
state: "away"
});
}
@ -1168,7 +1168,7 @@ export class ConnectionHandler {
this.client_status.queries_visible = flag;
this.channelTree.toggle_server_queries(flag);
this.event_registry.fire("notify_state_updated", {
this.events_.fire("notify_state_updated", {
state: "query"
});
}
@ -1183,7 +1183,7 @@ export class ConnectionHandler {
return;
this.inputHardwareState = state;
this.event_registry.fire("notify_state_updated", { state: "microphone" });
this.events_.fire("notify_state_updated", { state: "microphone" });
}
hasOutputHardware() : boolean { return true; }

View File

@ -10,6 +10,8 @@ import {ServerEventLogController} from "tc-shared/ui/frames/log/Controller";
import {ServerLogFrame} from "tc-shared/ui/frames/log/Renderer";
import {HostBannerController} from "tc-shared/ui/frames/HostBannerController";
import {HostBanner} from "tc-shared/ui/frames/HostBannerRenderer";
import {ChannelTreeView} from "tc-shared/ui/tree/RendererView";
import {ChannelTreeRenderer} from "tc-shared/ui/tree/Renderer";
export let server_connections: ConnectionManager;
@ -34,15 +36,16 @@ export class ConnectionManager {
private connection_handlers: ConnectionHandler[] = [];
private active_handler: ConnectionHandler | undefined;
private _container_channel_tree: JQuery;
private containerChannelVideo: ReplaceableContainer;
private containerFooter: HTMLDivElement;
private containerServerLog: HTMLDivElement;
private containerHostBanner: HTMLDivElement;
private containerChannelTree: HTMLDivElement;
private sideBarController: SideBarController;
private serverLogController: ServerEventLogController;
private hostBannerController: HostBannerController;
/* FIXME: Move these controller out! */
sideBarController: SideBarController;
serverLogController: ServerEventLogController;
hostBannerController: HostBannerController;
constructor() {
this.event_registry = new Registry<ConnectionManagerEvents>();
@ -56,13 +59,14 @@ export class ConnectionManager {
this.containerServerLog = document.getElementById("server-log") as HTMLDivElement;
this.containerFooter = document.getElementById("container-footer") as HTMLDivElement;
this.containerHostBanner = document.getElementById("hostbanner") as HTMLDivElement;
this._container_channel_tree = $("#channelTree");
this.containerChannelTree = document.getElementById("channelTree") as HTMLDivElement;
this.sideBarController.renderInto(document.getElementById("chat") as HTMLDivElement);
this.set_active_connection(undefined);
}
initializeReactComponents() {
return;
ReactDOM.render(React.createElement(FooterRenderer), this.containerFooter);
ReactDOM.render(React.createElement(ServerLogFrame, { events: this.serverLogController.events }), this.containerServerLog);
ReactDOM.render(React.createElement(HostBanner, { events: this.hostBannerController.uiEvents }), this.containerHostBanner);
@ -129,12 +133,15 @@ export class ConnectionManager {
this.serverLogController.setConnectionHandler(handler);
this.hostBannerController.setConnectionHandler(handler);
this._container_channel_tree.children().detach();
/*
this.containerChannelVideo.replaceWith(handler?.video_frame.getContainer());
if(handler) {
this._container_channel_tree.append(handler.channelTree.tag_tree());
ReactDOM.render(React.createElement(ChannelTreeRenderer, { handlerId: handler.handlerId, events: handler.channelTree.mainTreeUiEvents }), this.containerChannelTree);
} else {
ReactDOM.render(undefined, this.containerChannelTree);
}
*/
const old_handler = this.active_handler;
this.active_handler = handler;

View File

@ -47,15 +47,11 @@ import "./connection/rtc/Connection";
import "./connection/rtc/video/Connection";
import "./video/VideoSource";
import "./media/Video";
import "./ui/AppController";
import {defaultConnectProfile, findConnectProfile} from "tc-shared/profiles/ConnectionProfile";
import {server_connections} from "tc-shared/ConnectionManager";
import {initializeConnectionUIList} from "tc-shared/ui/frames/connection-handler-list/Controller";
import ContextMenuEvent = JQuery.ContextMenuEvent;
import {Registry} from "tc-shared/events";
import {ControlBarEvents} from "tc-shared/ui/frames/control-bar/Definitions";
import {ControlBar2} from "tc-shared/ui/frames/control-bar/Renderer";
import {initializeControlBarController} from "tc-shared/ui/frames/control-bar/Controller";
let preventWelcomeUI = false;
async function initialize() {
@ -71,15 +67,7 @@ async function initialize() {
}
async function initialize_app() {
initializeConnectionUIList();
global_ev_handler.initialize(global_client_actions);
{
const events = new Registry<ControlBarEvents>()
initializeControlBarController(events, "main");
ReactDOM.render(<ControlBar2 events={events} />, $(".container-control-bar")[0]);
}
/*
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "settings init",
@ -88,8 +76,9 @@ async function initialize_app() {
});
*/
if(!aplayer.initialize())
if(!aplayer.initialize()) {
console.warn(tr("Failed to initialize audio controller!"));
}
aplayer.on_ready(() => {
if(aplayer.set_master_volume)

View File

@ -20,13 +20,14 @@ import {spawnBanClient} from "tc-shared/ui/modal/ModalBanClient";
import {formatMessage} from "tc-shared/ui/frames/chat";
import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
import {tr, tra} from "tc-shared/i18n/localize";
import {renderChannelTree} from "tc-shared/ui/tree/Controller";
import {initializeChannelTreeUiEvents, renderChannelTree} from "tc-shared/ui/tree/Controller";
import {ChannelTreePopoutController} from "tc-shared/ui/tree/popout/Controller";
import {Settings, settings} from "tc-shared/settings";
import {ClientIcon} from "svg-sprites/client-icons";
import "./EntryTagsHandler";
import {spawnChannelEditNew} from "tc-shared/ui/modal/channel-edit/Controller";
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
export interface ChannelTreeEvents {
/* general tree notified */
@ -96,7 +97,11 @@ export class ChannelTree {
readonly popoutController: ChannelTreePopoutController;
private readonly tagContainer: JQuery;
/*
* We're constantly keeping the UI controller used (IDK yet how fast event attachment/detachment is)
* The main background is to speed up server tab switching.
*/
mainTreeUiEvents: Registry<ChannelTreeUIEvents>;
private selectedEntry: ChannelTreeEntry<any> | undefined;
private showQueries: boolean;
@ -112,8 +117,7 @@ export class ChannelTree {
this.server = new ServerEntry(this, "undefined", undefined);
this.popoutController = new ChannelTreePopoutController(this);
this.tagContainer = $.spawn("div").addClass("channel-tree-container");
renderChannelTree(this, this.tagContainer[0], { popoutButton: true });
this.mainTreeUiEvents = initializeChannelTreeUiEvents(this, { popoutButton: true });
this.events.on("notify_channel_list_received", () => {
if(!this.selectedEntry) {
@ -124,10 +128,6 @@ export class ChannelTree {
this.reset();
}
tag_tree() : HTMLDivElement {
return this.tagContainer[0] as HTMLDivElement;
}
channelsOrdered() : ChannelEntry[] {
const result = [];
@ -195,7 +195,9 @@ export class ChannelTree {
}
destroy() {
ReactDOM.unmountComponentAtNode(this.tagContainer[0]);
this.mainTreeUiEvents?.fire("notify_destroy");
this.mainTreeUiEvents?.destroy();
this.mainTreeUiEvents = undefined;
if(this.server) {
this.server.destroy();
@ -207,7 +209,6 @@ export class ChannelTree {
this.channelLast = undefined;
this.popoutController.destroy();
this.tagContainer.remove();
this.events.destroy();
}

View File

@ -0,0 +1,115 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import {Registry} from "tc-shared/events";
import {ControlBarEvents} from "tc-shared/ui/frames/control-bar/Definitions";
import {initializeControlBarController} from "tc-shared/ui/frames/control-bar/Controller";
import {TeaAppMainView} from "tc-shared/ui/AppRenderer";
import {ConnectionListUIEvents} from "tc-shared/ui/frames/connection-handler-list/Definitions";
import {initializeConnectionListController} from "tc-shared/ui/frames/connection-handler-list/Controller";
import * as loader from "tc-loader";
import {Stage} from "tc-loader";
import {server_connections} from "tc-shared/ConnectionManager";
import {AppUiEvents} from "tc-shared/ui/AppDefinitions";
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
export class AppController {
private uiEvents: Registry<AppUiEvents>;
private listener: (() => void)[];
private currentConnection: ConnectionHandler;
private listenerConnection: (() => void)[];
private container: HTMLDivElement;
private controlBarEvents: Registry<ControlBarEvents>;
private connectionListEvents: Registry<ConnectionListUIEvents>;
constructor() {
this.uiEvents = new Registry<AppUiEvents>();
this.uiEvents.on("query_channel_tree", () => this.notifyChannelTree());
this.listener = [];
}
destroy() {
this.listener?.forEach(callback => callback());
this.listener = [];
ReactDOM.unmountComponentAtNode(this.container);
this.container.remove();
this.container = undefined;
this.controlBarEvents?.fire("notify_destroy");
this.controlBarEvents?.destroy();
this.controlBarEvents = undefined;
this.connectionListEvents?.fire("notify_destroy");
this.connectionListEvents?.destroy();
this.connectionListEvents = undefined;
this.uiEvents?.destroy();
this.uiEvents = undefined;
}
initialize() {
this.listener = [];
this.container = document.createElement("div");
this.container.classList.add("app-container");
document.body.append(this.container);
this.controlBarEvents = new Registry<ControlBarEvents>()
initializeControlBarController(this.controlBarEvents, "main");
this.connectionListEvents = new Registry<ConnectionListUIEvents>();
initializeConnectionListController(this.connectionListEvents);
this.listener.push(server_connections.events().on("notify_active_handler_changed", event => this.setConnectionHandler(event.newHandler)));
this.setConnectionHandler(server_connections.active_connection());
}
setConnectionHandler(connection: ConnectionHandler) {
if(this.currentConnection === connection) {
return;
}
this.listenerConnection?.forEach(callback => callback());
this.listenerConnection = [];
this.currentConnection = connection;
this.notifyChannelTree();
}
renderApp() {
ReactDOM.render(React.createElement(TeaAppMainView, {
controlBar: this.controlBarEvents,
connectionList: this.connectionListEvents,
sidebar: server_connections.getSidebarController().uiEvents,
sidebarHeader: server_connections.getSidebarController().getHeaderController().uiEvents,
log: server_connections.serverLogController.events,
events: this.uiEvents,
hostBanner: server_connections.hostBannerController.uiEvents
}), this.container);
}
private notifyChannelTree() {
this.uiEvents.fire_react("notify_channel_tree", {
handlerId: this.currentConnection?.handlerId,
events: this.currentConnection?.channelTree.mainTreeUiEvents
});
}
}
let appViewController: AppController;
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
name: "app view",
function: async () => {
appViewController = new AppController();
appViewController.initialize();
appViewController.renderApp();
(window as any).AppController = AppController;
(window as any).appViewController = appViewController;
},
priority: 0
});

View File

@ -0,0 +1,11 @@
import {Registry} from "tc-shared/events";
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
export interface AppUiEvents {
query_channel_tree: {},
notify_channel_tree: {
events: Registry<ChannelTreeUIEvents> | undefined,
handlerId: string
}
}

View File

@ -0,0 +1,68 @@
@import "../../css/static/properties";
@import "../../css/static/mixin";
.app {
display: flex;
flex-direction: column;
justify-content: stretch;
overflow: hidden;
height: 100%;
width: 100%;
padding: 5px;
/* TODO: Move this into the control bar? */
.controlBar {
z-index: 200;
display: flex;
flex-direction: row;
justify-content: center;
flex-shrink: 0;
flex-grow: 0;
border-radius: 5px;
height: 2em;
width: 100%;
}
.channelTreeAndSidebar {
display: flex;
flex-direction: row;
justify-content: stretch;
margin-top: 5px;
min-height: 27em;
}
.channelTree {
display: flex;
flex-direction: column;
justify-content: stretch;
background: var(--channel-tree-background);
min-width: 200px;
min-height: 100px;
overflow: hidden;
border-radius: 5px;
}
.sideBar {
min-width: 350px;
}
.containerLog {
display: flex;
flex-direction: row;
justify-content: stretch;
flex-shrink: 1;
flex-grow: 1;
min-height: 0;
}
}

View File

@ -0,0 +1,114 @@
import * as React from "react";
import {ControlBar2} from "tc-shared/ui/frames/control-bar/Renderer";
import {Registry} from "tc-shared/events";
import {ControlBarEvents} from "tc-shared/ui/frames/control-bar/Definitions";
import {ConnectionListUIEvents} from "tc-shared/ui/frames/connection-handler-list/Definitions";
import {ConnectionHandlerList} from "tc-shared/ui/frames/connection-handler-list/Renderer";
import {ErrorBoundary} from "tc-shared/ui/react-elements/ErrorBoundary";
import {ContextDivider} from "tc-shared/ui/react-elements/ContextDivider";
import {SideBarRenderer} from "tc-shared/ui/frames/SideBarRenderer";
import {SideBarEvents} from "tc-shared/ui/frames/SideBarDefinitions";
import {SideHeaderEvents} from "tc-shared/ui/frames/side/HeaderDefinitions";
import {ServerLogFrame} from "tc-shared/ui/frames/log/Renderer";
import {ServerEventLogUiEvents} from "tc-shared/ui/frames/log/Definitions";
import {FooterRenderer} from "tc-shared/ui/frames/footer/Renderer";
import {HostBanner} from "tc-shared/ui/frames/HostBannerRenderer";
import {HostBannerUiEvents} from "tc-shared/ui/frames/HostBannerDefinitions";
import {AppUiEvents} from "tc-shared/ui/AppDefinitions";
import {useState} from "react";
import {ChannelTreeRenderer} from "tc-shared/ui/tree/Renderer";
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
const cssStyle = require("./AppRenderer.scss");
/*
<div class="app-container">
<div class="app">
<!-- navigation bar -->
<div class="container-control-bar">
<div id="control_bar" class="control_bar">
</div>
</div>
<div class="container-connection-handlers" id="connection-handler-list"></div>
<div class="container-app-main">
<div class="container-channel-video" id="channel-video"></div>
<div class="container-channel-chat">
<!-- Channel tree -->
<div class="container-channel-tree">
<div class="hostbanner" id="hostbanner"></div>
<div class="channel-tree" id="channelTree"></div>
</div>
<div class="container-seperator vertical" seperator-id="seperator-channel-chat"></div>
<!-- Chat window -->
<div class="container-chat" id="chat"></div>
</div>
<div class="container-seperator horizontal" seperator-id="seperator-main-log"></div>
<div class="container-bottom">
<div class="container-server-log" id="server-log"></div>
<div class="container-footer" id="container-footer">
</div>
</div> <!-- Selection info -->
</div>
</div>
</div>
*/
const ChannelTree = React.memo((props: { events: Registry<AppUiEvents> }) => {
const [ data, setData ] = useState<{ events: Registry<ChannelTreeUIEvents>, handlerId: string }>(() => {
props.events.fire("query_channel_tree");
return undefined;
});
props.events.reactUse("notify_channel_tree", event => {
setData({ events: event.events, handlerId: event.handlerId });
}, undefined, []);
if(!data?.events) {
return null;
}
return <ChannelTreeRenderer handlerId={data.handlerId} events={data.events} />;
});
export const TeaAppMainView = (props: {
events: Registry<AppUiEvents>
controlBar: Registry<ControlBarEvents>,
connectionList: Registry<ConnectionListUIEvents>,
sidebar: Registry<SideBarEvents>,
sidebarHeader: Registry<SideHeaderEvents>,
log: Registry<ServerEventLogUiEvents>,
hostBanner: Registry<HostBannerUiEvents>
}) => {
return (
<div className={cssStyle.app}>
<ErrorBoundary>
<ControlBar2 events={props.controlBar} className={cssStyle.controlBar} />
</ErrorBoundary>
<ErrorBoundary>
<ConnectionHandlerList events={props.connectionList} />
</ErrorBoundary>
{/* TODO: The video! */}
<div className={cssStyle.channelTreeAndSidebar}>
<div className={cssStyle.channelTree}>
<ErrorBoundary>
<HostBanner events={props.hostBanner} />
<ChannelTree events={props.events} />
</ErrorBoundary>
</div>
<ContextDivider id={"channel-chat"} direction={"horizontal"} defaultValue={25} />
<SideBarRenderer events={props.sidebar} eventsHeader={props.sidebarHeader} className={cssStyle.sideBar} />
</div>
<ContextDivider id={"main-log"} direction={"vertical"} defaultValue={75} />
<ErrorBoundary>
<div className={cssStyle.containerLog}>
<ServerLogFrame events={props.log} />
</div>
</ErrorBoundary>
<FooterRenderer />
</div>
);
}
/* ConnectionHandlerList */

View File

@ -13,6 +13,8 @@ html:root {
flex-direction: column;
justify-content: stretch;
flex-shrink: 0;
.withBackground {
background-color: var(--hostbanner-background);
border-top-left-radius: 5px;

View File

@ -2,9 +2,6 @@ import {ConnectionHandler} from "../../ConnectionHandler";
import {PrivateConversationController} from "./side/PrivateConversationController";
import {ClientInfoController} from "tc-shared/ui/frames/side/ClientInfoController";
import {SideHeaderController} from "tc-shared/ui/frames/side/HeaderController";
import * as ReactDOM from "react-dom";
import {SideBarRenderer} from "tc-shared/ui/frames/SideBarRenderer";
import * as React from "react";
import {SideBarEvents, SideBarType} from "tc-shared/ui/frames/SideBarDefinitions";
import {Registry} from "tc-shared/events";
import {LogCategory, logWarn} from "tc-shared/log";
@ -12,7 +9,7 @@ import {ChannelBarController} from "tc-shared/ui/frames/side/ChannelBarControlle
import {MusicBotController} from "tc-shared/ui/frames/side/MusicBotController";
export class SideBarController {
private readonly uiEvents: Registry<SideBarEvents>;
readonly uiEvents: Registry<SideBarEvents>;
private currentConnection: ConnectionHandler;
private listenerConnection: (() => void)[];
@ -77,16 +74,22 @@ export class SideBarController {
}
renderInto(container: HTMLDivElement) {
/*
ReactDOM.render(React.createElement(SideBarRenderer, {
events: this.uiEvents,
eventsHeader: this.header["uiEvents"],
}), container);
*/
}
getMusicController() : MusicBotController {
return this.musicPanel;
}
getHeaderController() : SideHeaderController {
return this.header;
}
private sendContent() {
if(this.currentConnection) {
this.uiEvents.fire("notify_content", { content: this.currentConnection.getSideBar().getSideBarContent() });

View File

@ -34,4 +34,6 @@
display: flex;
flex-direction: column;
background: var(--side-background);
}

View File

@ -169,7 +169,8 @@ const SideBarHeader = (props: { type: SideBarType, eventsHeader: Registry<SideHe
export const SideBarRenderer = (props: {
events: Registry<SideBarEvents>,
eventsHeader: Registry<SideHeaderEvents>
eventsHeader: Registry<SideHeaderEvents>,
className?: string
}) => {
const [ content, setContent ] = useState<SideBarType>(() => {
props.events.fire("query_content");
@ -179,7 +180,7 @@ export const SideBarRenderer = (props: {
return (
<EventContent.Provider value={props.events}>
<div className={cssStyle.container}>
<div className={cssStyle.container + " " + props.className}>
<ErrorBoundary>
<SideBarHeader eventsHeader={props.eventsHeader} type={content} />
</ErrorBoundary>

View File

@ -1,23 +1,11 @@
import {Registry} from "tc-shared/events";
import {ConnectionListUIEvents, HandlerConnectionState} from "tc-shared/ui/frames/connection-handler-list/Definitions";
import * as React from "react";
import * as ReactDOM from "react-dom";
import {ConnectionHandlerList} from "tc-shared/ui/frames/connection-handler-list/Renderer";
import {server_connections} from "tc-shared/ConnectionManager";
import {LogCategory, logWarn} from "tc-shared/log";
import {ConnectionState} from "tc-shared/ConnectionHandler";
import { tr } from "tc-shared/i18n/localize";
export function initializeConnectionUIList() {
const container = document.getElementById("connection-handler-list");
const events = new Registry<ConnectionListUIEvents>();
//events.enableDebug("Handler-List");
initializeController(events);
ReactDOM.render(React.createElement(ConnectionHandlerList, { events: events }), container);
}
function initializeController(events: Registry<ConnectionListUIEvents>) {
export function initializeConnectionListController(events: Registry<ConnectionListUIEvents>) {
let registeredHandlerEvents: {[key: string]:(() => void)[]} = {};
events.on("notify_destroy", () => {

View File

@ -467,7 +467,7 @@ export const ControlBar2 = (props: { events: Registry<ControlBarEvents>, classNa
return (
<Events.Provider value={props.events}>
<ModeContext.Provider value={mode}>
<div className={cssStyle.controlBar + " " + cssStyle["mode-" + mode]}>
<div className={cssStyle.controlBar + " " + cssStyle["mode-" + mode] + " " + props.className}>
{items}
</div>
</ModeContext.Provider>

View File

@ -11,12 +11,29 @@
@include user-select(none);
width: 100%;
.version {
margin-right: .5em;
flex-shrink: 0;
flex-grow: 0;
height: 1.5em;
background: var(--footer-background);
color: var(--footer-text);
border-radius: 0 0 5px 5px;
padding-right: 5px;
padding-left: 5px;
padding-top: 2px;
-webkit-box-shadow: inset 0 2px 5px 0 rgba(0,0,0,0.125);
-moz-box-shadow: inset 0 2px 5px 0 rgba(0,0,0,0.125);
box-shadow: inset 0 2px 5px 0 rgba(0,0,0,0.125);
> * {
align-self: center;
}
.source {
display: inline-block;
a[href], a[href]:visited {
color: var(--footer-text)!important;
}
}

View File

@ -23,6 +23,7 @@ const VersionsRenderer = () => (
</React.Fragment>
);
/* FIXME: Outsource this! */
const RtcStatus = () => {
const statusController = useMemo(() => new StatusController(new Registry<ConnectionStatusEvents>()), []);
statusController.setConnectionHandler(server_connections.active_connection());

View File

@ -10,14 +10,21 @@
}
.logContainer {
flex-shrink: 1;
flex-grow: 1;
background: var(--server-log-background);
display: flex;
flex-direction: column;
justify-content: flex-start;
flex-shrink: 1;
flex-grow: 1;
min-height: 1em;
width: 100%;
border-radius: 5px 5px 0 0;
padding-right: 5px;
padding-left: 5px;
overflow-x: hidden;
overflow-y: auto;

View File

@ -21,7 +21,7 @@ const ChannelInfoUpdateProperties: (keyof ChannelProperties)[] = [
/* TODO: Remove the ping interval handler. It's currently still there since the clients are not emitting the event yet */
export class SideHeaderController {
private readonly uiEvents: Registry<SideHeaderEvents>;
readonly uiEvents: Registry<SideHeaderEvents>;
private connection: ConnectionHandler;

View File

@ -25,10 +25,15 @@ export interface ChannelTreeRendererOptions {
popoutButton: boolean;
}
export function renderChannelTree(channelTree: ChannelTree, target: HTMLElement, options: ChannelTreeRendererOptions) {
export function initializeChannelTreeUiEvents(channelTree: ChannelTree, options: ChannelTreeRendererOptions) : Registry<ChannelTreeUIEvents> {
const events = new Registry<ChannelTreeUIEvents>();
events.enableDebug("channel-tree-view");
initializeChannelTreeController(events, channelTree, options);
return events;
}
export function renderChannelTree(channelTree: ChannelTree, target: HTMLElement, options: ChannelTreeRendererOptions) {
const events = initializeChannelTreeUiEvents(channelTree, options);
ReactDOM.render(<ChannelTreeRenderer handlerId={channelTree.client.handlerId} events={events} />, target);
@ -89,7 +94,6 @@ class ChannelTreeController {
/* the key here is the unique entry id! */
private eventListeners: {[key: number]: (() => void)[]} = {};
private channelTreeInitialized = false;
private readonly connectionStateListener;
private readonly voiceConnectionStateListener;
@ -135,7 +139,7 @@ class ChannelTreeController {
private handleConnectionStateChanged(event: ConnectionEvents["notify_connection_state_changed"]) {
if(event.newState !== ConnectionState.CONNECTED) {
this.channelTreeInitialized = false;
this.channelTree.channelsInitialized = false;
this.sendChannelTreeEntries();
}
this.sendServerStatus(this.channelTree.server);
@ -146,7 +150,7 @@ class ChannelTreeController {
return;
}
if(!this.channelTreeInitialized) {
if(!this.channelTree.channelsInitialized) {
return;
}
@ -154,7 +158,7 @@ class ChannelTreeController {
}
private handleGroupsUpdated(event: GroupManagerEvents["notify_groups_updated"]) {
if(!this.channelTreeInitialized) {
if(!this.channelTree.channelsInitialized) {
return;
}
@ -176,7 +180,7 @@ class ChannelTreeController {
}
private handleGroupsReceived() {
if(!this.channelTreeInitialized) {
if(!this.channelTree.channelsInitialized) {
return;
}
@ -186,13 +190,13 @@ class ChannelTreeController {
/* general channel tree event handlers */
@EventHandler<ChannelTreeEvents>("notify_popout_state_changed")
private handlePoputStateChanged() {
private handlePopoutStateChanged() {
this.sendPopoutState();
}
@EventHandler<ChannelTreeEvents>("notify_channel_list_received")
private handleChannelListReceived() {
this.channelTreeInitialized = true;
this.channelTree.channelsInitialized = true;
this.channelTree.channels.forEach(channel => this.initializeChannelEvents(channel));
this.channelTree.clients.forEach(channel => this.initializeClientEvents(channel));
this.sendChannelTreeEntries();
@ -201,14 +205,14 @@ class ChannelTreeController {
@EventHandler<ChannelTreeEvents>("notify_channel_created")
private handleChannelCreated(event: ChannelTreeEvents["notify_channel_created"]) {
if(!this.channelTreeInitialized) { return; }
if(!this.channelTree.channelsInitialized) { return; }
this.initializeChannelEvents(event.channel);
this.sendChannelTreeEntries();
}
@EventHandler<ChannelTreeEvents>("notify_channel_moved")
private handleChannelMoved(event: ChannelTreeEvents["notify_channel_moved"]) {
if(!this.channelTreeInitialized) { return; }
if(!this.channelTree.channelsInitialized) { return; }
this.sendChannelTreeEntries();
if(event.previousParent && !event.previousParent.child_channel_head) {
@ -223,14 +227,14 @@ class ChannelTreeController {
@EventHandler<ChannelTreeEvents>("notify_channel_deleted")
private handleChannelDeleted(event: ChannelTreeEvents["notify_channel_deleted"]) {
if(!this.channelTreeInitialized) { return; }
if(!this.channelTree.channelsInitialized) { return; }
this.finalizeEvents(event.channel);
this.sendChannelTreeEntries();
}
@EventHandler<ChannelTreeEvents>("notify_client_enter_view")
private handleClientEnter(event: ChannelTreeEvents["notify_client_enter_view"]) {
if(!this.channelTreeInitialized) { return; }
if(!this.channelTree.channelsInitialized) { return; }
this.initializeClientEvents(event.client);
this.sendChannelInfo(event.targetChannel);
@ -240,7 +244,7 @@ class ChannelTreeController {
@EventHandler<ChannelTreeEvents>("notify_client_leave_view")
private handleClientLeave(event: ChannelTreeEvents["notify_client_leave_view"]) {
if(!this.channelTreeInitialized) { return; }
if(!this.channelTree.channelsInitialized) { return; }
this.finalizeEvents(event.client);
this.sendChannelInfo(event.sourceChannel);
@ -250,7 +254,7 @@ class ChannelTreeController {
@EventHandler<ChannelTreeEvents>("notify_client_moved")
private handleClientMoved(event: ChannelTreeEvents["notify_client_moved"]) {
if(!this.channelTreeInitialized) { return; }
if(!this.channelTree.channelsInitialized) { return; }
this.sendChannelInfo(event.oldChannel);
this.sendChannelStatusIcon(event.oldChannel);
@ -264,7 +268,7 @@ class ChannelTreeController {
@EventHandler<ChannelTreeEvents>("notify_selected_entry_changed")
private handleSelectedEntryChanged(_event: ChannelTreeEvents["notify_selected_entry_changed"]) {
if(!this.channelTreeInitialized) { return; }
if(!this.channelTree.channelsInitialized) { return; }
this.sendSelectedEntry();
}