Rendering the main UI only via IPC

This commit is contained in:
WolverinDEV 2021-04-29 22:18:11 +02:00
parent a0ba132182
commit f2ab9800d4
14 changed files with 212 additions and 139 deletions

View file

@ -9,20 +9,22 @@ import {initializeConnectionListController} from "tc-shared/ui/frames/connection
import * as loader from "tc-loader"; import * as loader from "tc-loader";
import {Stage} from "tc-loader"; import {Stage} from "tc-loader";
import {server_connections} from "tc-shared/ConnectionManager"; import {server_connections} from "tc-shared/ConnectionManager";
import {AppUiEvents} from "tc-shared/ui/AppDefinitions"; import {AppUiEvents, AppUiVariables} from "tc-shared/ui/AppDefinitions";
import {ConnectionHandler} from "tc-shared/ConnectionHandler"; import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {SideBarController} from "tc-shared/ui/frames/SideBarController"; import {SideBarController} from "tc-shared/ui/frames/SideBarController";
import {ServerEventLogController} from "tc-shared/ui/frames/log/Controller"; import {ServerEventLogController} from "tc-shared/ui/frames/log/Controller";
import {HostBannerController} from "tc-shared/ui/frames/HostBannerController"; import {HostBannerController} from "tc-shared/ui/frames/HostBannerController";
import {UiVariableProvider} from "tc-shared/ui/utils/Variable";
import {createIpcUiVariableProvider, IpcUiVariableProvider} from "tc-shared/ui/utils/IpcVariable";
export class AppController { export class AppController {
private uiEvents: Registry<AppUiEvents>;
private listener: (() => void)[]; private listener: (() => void)[];
private currentConnection: ConnectionHandler; private currentConnection: ConnectionHandler;
private listenerConnection: (() => void)[]; private listenerConnection: (() => void)[];
private variables: IpcUiVariableProvider<AppUiVariables>;
private container: HTMLDivElement; private container: HTMLDivElement;
private controlBarEvents: Registry<ControlBarEvents>; private controlBarEvents: Registry<ControlBarEvents>;
private connectionListEvents: Registry<ConnectionListUIEvents>; private connectionListEvents: Registry<ConnectionListUIEvents>;
@ -32,9 +34,21 @@ export class AppController {
private hostBannerController: HostBannerController; private hostBannerController: HostBannerController;
constructor() { constructor() {
this.uiEvents = new Registry<AppUiEvents>(); this.variables = createIpcUiVariableProvider();
this.uiEvents.on("query_channel_tree", () => this.notifyChannelTree()); this.variables.setVariableProvider("connectionList", () => this.connectionListEvents.generateIpcDescription());
this.uiEvents.on("query_video", () => this.notifyVideoContainer()); this.variables.setVariableProvider("controlBar", () => this.controlBarEvents.generateIpcDescription());
this.variables.setVariableProvider("hostBanner", () => this.hostBannerController.uiEvents.generateIpcDescription());
this.variables.setVariableProvider("log", () => this.serverLogController.events.generateIpcDescription());
this.variables.setVariableProvider("sidebar", () => this.sideBarController.uiEvents.generateIpcDescription());
this.variables.setVariableProvider("sidebarHeader", () => this.sideBarController.getHeaderController().uiEvents.generateIpcDescription());
this.variables.setVariableProvider("channelTree", () => ({
events: this.currentConnection?.channelTree.mainTreeUiEvents.generateIpcDescription(),
handlerId: this.currentConnection?.handlerId
}));
this.variables.setVariableProvider("channelVideo", () => ({
events: this.currentConnection?.video_frame.getEvents().generateIpcDescription(),
handlerId: this.currentConnection?.handlerId
}));
this.listener = []; this.listener = [];
} }
@ -64,8 +78,8 @@ export class AppController {
this.hostBannerController?.destroy(); this.hostBannerController?.destroy();
this.hostBannerController = undefined; this.hostBannerController = undefined;
this.uiEvents?.destroy(); this.variables?.destroy();
this.uiEvents = undefined; this.variables = undefined;
} }
initialize() { initialize() {
@ -102,35 +116,15 @@ export class AppController {
this.serverLogController.setConnectionHandler(connection); this.serverLogController.setConnectionHandler(connection);
this.hostBannerController.setConnectionHandler(connection); this.hostBannerController.setConnectionHandler(connection);
this.notifyChannelTree(); this.variables.sendVariable("channelTree");
this.notifyVideoContainer(); this.variables.sendVariable("channelVideo");
} }
renderApp() { renderApp() {
ReactDOM.render(React.createElement(TeaAppMainView, { ReactDOM.render(React.createElement(TeaAppMainView, {
controlBar: this.controlBarEvents, variables: this.variables.generateConsumerDescription(),
connectionList: this.connectionListEvents,
sidebar: this.sideBarController.uiEvents,
sidebarHeader: this.sideBarController.getHeaderController().uiEvents,
log: this.serverLogController.events,
events: this.uiEvents,
hostBanner: this.hostBannerController.uiEvents
}), this.container); }), this.container);
} }
private notifyChannelTree() {
this.uiEvents.fire_react("notify_channel_tree", {
handlerId: this.currentConnection?.handlerId,
events: this.currentConnection?.channelTree.mainTreeUiEvents
});
}
private notifyVideoContainer() {
this.uiEvents.fire_react("notify_video", {
events: this.currentConnection?.video_frame.getEvents(),
handlerId: this.currentConnection?.handlerId
});
}
} }
export let appViewController: AppController; export let appViewController: AppController;

View file

@ -1,6 +1,29 @@
import {Registry} from "tc-shared/events"; import {IpcRegistryDescription, Registry} from "tc-shared/events";
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions"; import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
import {ChannelVideoEvents} from "tc-shared/ui/frames/video/Definitions"; import {ChannelVideoEvents} from "tc-shared/ui/frames/video/Definitions";
import {ControlBarEvents} from "tc-shared/ui/frames/control-bar/Definitions";
import {ConnectionListUIEvents} from "tc-shared/ui/frames/connection-handler-list/Definitions";
import {SideBarEvents} from "tc-shared/ui/frames/SideBarDefinitions";
import {SideHeaderEvents} from "tc-shared/ui/frames/side/HeaderDefinitions";
import {ServerEventLogUiEvents} from "tc-shared/ui/frames/log/Definitions";
import {HostBannerUiEvents} from "tc-shared/ui/frames/HostBannerDefinitions";
export interface AppUiVariables {
readonly channelTree: {
events: IpcRegistryDescription<ChannelTreeUIEvents> | undefined,
handlerId: string
},
readonly channelVideo: {
events: IpcRegistryDescription<ChannelVideoEvents>,
handlerId: string
},
readonly controlBar: IpcRegistryDescription<ControlBarEvents>,
readonly connectionList: IpcRegistryDescription<ConnectionListUIEvents>,
readonly sidebar: IpcRegistryDescription<SideBarEvents>,
readonly sidebarHeader: IpcRegistryDescription<SideHeaderEvents>,
readonly log: IpcRegistryDescription<ServerEventLogUiEvents>,
readonly hostBanner: IpcRegistryDescription<HostBannerUiEvents>,
}
export interface AppUiEvents { export interface AppUiEvents {
query_channel_tree: {}, query_channel_tree: {},

View file

@ -1,96 +1,117 @@
import * as React from "react"; import * as React from "react";
import {useState} from "react"; import {useEffect, useMemo} from "react";
import {ControlBar2} from "tc-shared/ui/frames/control-bar/Renderer"; import {ControlBar2} from "tc-shared/ui/frames/control-bar/Renderer";
import {Registry} from "tc-shared/events"; import {IpcRegistryDescription, 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 {ConnectionHandlerList} from "tc-shared/ui/frames/connection-handler-list/Renderer";
import {ErrorBoundary} from "tc-shared/ui/react-elements/ErrorBoundary"; import {ErrorBoundary} from "tc-shared/ui/react-elements/ErrorBoundary";
import {ContextDivider} from "tc-shared/ui/react-elements/ContextDivider"; import {ContextDivider} from "tc-shared/ui/react-elements/ContextDivider";
import {SideBarRenderer} from "tc-shared/ui/frames/SideBarRenderer"; 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 {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 {FooterRenderer} from "tc-shared/ui/frames/footer/Renderer";
import {HostBanner} from "tc-shared/ui/frames/HostBannerRenderer"; import {HostBanner} from "tc-shared/ui/frames/HostBannerRenderer";
import {HostBannerUiEvents} from "tc-shared/ui/frames/HostBannerDefinitions"; import {AppUiVariables} from "tc-shared/ui/AppDefinitions";
import {AppUiEvents} from "tc-shared/ui/AppDefinitions";
import {ChannelTreeRenderer} from "tc-shared/ui/tree/Renderer"; import {ChannelTreeRenderer} from "tc-shared/ui/tree/Renderer";
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
import {ImagePreviewHook} from "tc-shared/ui/frames/ImagePreview"; import {ImagePreviewHook} from "tc-shared/ui/frames/ImagePreview";
import {InternalModalHook} from "tc-shared/ui/react-elements/modal/internal"; import {InternalModalHook} from "tc-shared/ui/react-elements/modal/internal";
import {TooltipHook} from "tc-shared/ui/react-elements/Tooltip"; import {TooltipHook} from "tc-shared/ui/react-elements/Tooltip";
import {ChannelVideoEvents} from "tc-shared/ui/frames/video/Definitions";
import {ChannelVideoRenderer} from "tc-shared/ui/frames/video/Renderer"; import {ChannelVideoRenderer} from "tc-shared/ui/frames/video/Renderer";
import {
createIpcUiVariableConsumer,
IpcVariableDescriptor
} from "tc-shared/ui/utils/IpcVariable";
import {UiVariableConsumer} from "tc-shared/ui/utils/Variable";
import {useRegistry} from "tc-shared/ui/react-elements/Helper";
const cssStyle = require("./AppRenderer.scss"); const cssStyle = require("./AppRenderer.scss");
const VideoFrame = React.memo((props: { events: Registry<AppUiEvents> }) => {
const [ data, setData ] = useState<{ events: Registry<ChannelVideoEvents> | undefined, handlerId: string | undefined }>(() => { const VideoFrame = React.memo((props: { variables: UiVariableConsumer<AppUiVariables> }) => {
props.events.fire("query_video"); const data = props.variables.useReadOnly("channelVideo", undefined, { handlerId: undefined, events: undefined });
return { events: undefined, handlerId: undefined }; const events = useRegistry(data.events);
});
props.events.reactUse("notify_video", event => setData({ handlerId: event.handlerId, events: event.events }));
if(!data.events) { if(!data.events) {
return null; return null;
} }
return <ChannelVideoRenderer handlerId={data.handlerId} events={data.events} key={"video-" + data.handlerId} />; return <ChannelVideoRenderer handlerId={data.handlerId} events={events} key={"video-" + data.handlerId} />;
}); });
const ChannelTree = React.memo((props: { events: Registry<AppUiEvents> }) => { const ChannelTree = React.memo((props: { variables: UiVariableConsumer<AppUiVariables> }) => {
const [ data, setData ] = useState<{ events: Registry<ChannelTreeUIEvents>, handlerId: string }>(() => { const data = props.variables.useReadOnly("channelTree", undefined, { handlerId: undefined, events: undefined });
props.events.fire("query_channel_tree"); const events = useRegistry(data.events);
return undefined;
});
props.events.reactUse("notify_channel_tree", event => { if(!events) {
setData({ events: event.events, handlerId: event.handlerId });
}, undefined, []);
if(!data?.events) {
return null; return null;
} }
return <ChannelTreeRenderer handlerId={data.handlerId} events={data.events} />; return <ChannelTreeRenderer handlerId={data.handlerId} events={events} key={"tree-" + data.handlerId} />;
}); });
const SideBar = React.memo((props: { variables: UiVariableConsumer<AppUiVariables> }) => (
<EventProvider registry={props.variables.useReadOnly("sidebar", undefined, undefined)}>
{sidebarRegistry => (
<EventProvider registry={props.variables.useReadOnly("sidebarHeader", undefined, undefined)}>
{sidebarHeaderRegistry => sidebarRegistry && sidebarHeaderRegistry ? (
<SideBarRenderer events={sidebarRegistry} eventsHeader={sidebarHeaderRegistry} className={cssStyle.sideBar} />
) : null}
</EventProvider>
)}
</EventProvider>
));
function EventProvider<T>(props: {
registry: IpcRegistryDescription<T> | undefined,
children: (registry: Registry<T> | undefined) => React.ReactElement
}) : React.ReactElement {
return props.children(useRegistry(props.registry));
}
export const TeaAppMainView = (props: { export const TeaAppMainView = (props: {
events: Registry<AppUiEvents> variables: IpcVariableDescriptor<AppUiVariables>,
controlBar: Registry<ControlBarEvents>,
connectionList: Registry<ConnectionListUIEvents>,
sidebar: Registry<SideBarEvents>,
sidebarHeader: Registry<SideHeaderEvents>,
log: Registry<ServerEventLogUiEvents>,
hostBanner: Registry<HostBannerUiEvents>
}) => { }) => {
const variables = useMemo(() => createIpcUiVariableConsumer(props.variables), [ props.variables ]);
useEffect(() => () => variables.destroy(), [ props.variables ]);
return ( return (
<div className={cssStyle.app}> <div className={cssStyle.app}>
<ErrorBoundary> <ErrorBoundary>
<ControlBar2 events={props.controlBar} className={cssStyle.controlBar} /> <EventProvider registry={variables.useReadOnly("controlBar", undefined, undefined)}>
{registry => registry ? (
<ControlBar2 events={registry} className={cssStyle.controlBar} />
) : null}
</EventProvider>
</ErrorBoundary> </ErrorBoundary>
<ErrorBoundary> <ErrorBoundary>
<ConnectionHandlerList events={props.connectionList} /> <EventProvider registry={variables.useReadOnly("connectionList", undefined, undefined)}>
{registry => registry ? (
<ConnectionHandlerList events={registry} />
) : null}
</EventProvider>
</ErrorBoundary> </ErrorBoundary>
<div className={cssStyle.mainContainer}> <div className={cssStyle.mainContainer}>
<VideoFrame events={props.events} /> <VideoFrame variables={variables} />
<div className={cssStyle.channelTreeAndSidebar}> <div className={cssStyle.channelTreeAndSidebar}>
<div className={cssStyle.channelTree}> <div className={cssStyle.channelTree}>
<ErrorBoundary> <ErrorBoundary>
<HostBanner events={props.hostBanner} /> <EventProvider registry={variables.useReadOnly("hostBanner", undefined, undefined)}>
<ChannelTree events={props.events} /> {registry => registry ? (
<HostBanner events={registry} />
) : null}
</EventProvider>
<ChannelTree variables={variables} />
</ErrorBoundary> </ErrorBoundary>
</div> </div>
<ContextDivider id={"channel-chat"} direction={"horizontal"} defaultValue={25} /> <ContextDivider id={"channel-chat"} direction={"horizontal"} defaultValue={25} />
<SideBarRenderer events={props.sidebar} eventsHeader={props.sidebarHeader} className={cssStyle.sideBar} /> <SideBar variables={variables} />
</div> </div>
<ContextDivider id={"main-log"} direction={"vertical"} defaultValue={75} /> <ContextDivider id={"main-log"} direction={"vertical"} defaultValue={75} />
<ErrorBoundary> <ErrorBoundary>
<div className={cssStyle.containerLog}> <div className={cssStyle.containerLog}>
<ServerLogFrame events={props.log} /> <EventProvider registry={variables.useReadOnly("log", undefined, undefined)}>
{registry => registry ? (
<ServerLogFrame events={registry} />
) : null}
</EventProvider>
</div> </div>
</ErrorBoundary> </ErrorBoundary>
</div> </div>

View file

@ -73,15 +73,6 @@ export class SideBarController {
this.privateConversations = undefined; this.privateConversations = undefined;
} }
renderInto(container: HTMLDivElement) {
/*
ReactDOM.render(React.createElement(SideBarRenderer, {
events: this.uiEvents,
eventsHeader: this.header["uiEvents"],
}), container);
*/
}
getMusicController() : MusicBotController { getMusicController() : MusicBotController {
return this.musicPanel; return this.musicPanel;
} }
@ -116,7 +107,7 @@ export class SideBarController {
this.uiEvents.fire_react("notify_content_data", { this.uiEvents.fire_react("notify_content_data", {
content: "channel", content: "channel",
data: { data: {
events: this.channelBar.uiEvents, events: this.channelBar.uiEvents.generateIpcDescription(),
} }
}); });
break; break;
@ -125,7 +116,7 @@ export class SideBarController {
this.uiEvents.fire_react("notify_content_data", { this.uiEvents.fire_react("notify_content_data", {
content: "server", content: "server",
data: this.currentConnection ? { data: this.currentConnection ? {
chatEvents: this.channelBar.getChannelConversationController().getUiEvents(), chatEvents: this.channelBar.getChannelConversationController().getUiEvents().generateIpcDescription(),
handlerId: this.currentConnection.handlerId handlerId: this.currentConnection.handlerId
} : undefined } : undefined
}); });
@ -140,7 +131,7 @@ export class SideBarController {
this.uiEvents.fire_react("notify_content_data", { this.uiEvents.fire_react("notify_content_data", {
content: "private-chat", content: "private-chat",
data: { data: {
events: this.privateConversations["uiEvents"], events: this.privateConversations["uiEvents"].generateIpcDescription(),
handlerId: this.currentConnection.handlerId handlerId: this.currentConnection.handlerId
} }
}); });
@ -155,7 +146,7 @@ export class SideBarController {
this.uiEvents.fire_react("notify_content_data", { this.uiEvents.fire_react("notify_content_data", {
content: "client-info", content: "client-info",
data: { data: {
events: this.clientInfo["uiEvents"], events: this.clientInfo["uiEvents"].generateIpcDescription(),
} }
}); });
break; break;
@ -169,8 +160,8 @@ export class SideBarController {
this.uiEvents.fire_react("notify_content_data", { this.uiEvents.fire_react("notify_content_data", {
content: "music-manage", content: "music-manage",
data: { data: {
botEvents: this.musicPanel.getBotUiEvents(), botEvents: this.musicPanel.getBotUiEvents().generateIpcDescription(),
playlistEvents: this.musicPanel.getPlaylistUiEvents() playlistEvents: this.musicPanel.getPlaylistUiEvents().generateIpcDescription()
} }
}); });
break; break;

View file

@ -1,4 +1,4 @@
import {Registry} from "tc-shared/events"; import {IpcRegistryDescription, Registry} from "tc-shared/events";
import {PrivateConversationUIEvents} from "tc-shared/ui/frames/side/PrivateConversationDefinitions"; import {PrivateConversationUIEvents} from "tc-shared/ui/frames/side/PrivateConversationDefinitions";
import {ClientInfoEvents} from "tc-shared/ui/frames/side/ClientInfoDefinitions"; import {ClientInfoEvents} from "tc-shared/ui/frames/side/ClientInfoDefinitions";
import {SideHeaderEvents} from "tc-shared/ui/frames/side/HeaderDefinitions"; import {SideHeaderEvents} from "tc-shared/ui/frames/side/HeaderDefinitions";
@ -7,28 +7,26 @@ import {MusicBotUiEvents} from "tc-shared/ui/frames/side/MusicBotDefinitions";
import {MusicPlaylistUiEvents} from "tc-shared/ui/frames/side/MusicPlaylistDefinitions"; import {MusicPlaylistUiEvents} from "tc-shared/ui/frames/side/MusicPlaylistDefinitions";
import {ChannelConversationUiEvents} from "tc-shared/ui/frames/side/ChannelConversationDefinitions"; import {ChannelConversationUiEvents} from "tc-shared/ui/frames/side/ChannelConversationDefinitions";
/* TODO: Somehow outsource the event registries to IPC? */
export type SideBarType = "none" | "server" | "channel" | "private-chat" | "client-info" | "music-manage"; export type SideBarType = "none" | "server" | "channel" | "private-chat" | "client-info" | "music-manage";
export interface SideBarTypeData { export interface SideBarTypeData {
"none": {}, "none": {},
"channel": { "channel": {
events: Registry<ChannelBarUiEvents> events: IpcRegistryDescription<ChannelBarUiEvents>
}, },
"private-chat": { "private-chat": {
events: Registry<PrivateConversationUIEvents>, events: IpcRegistryDescription<PrivateConversationUIEvents>,
handlerId: string handlerId: string
}, },
"client-info": { "client-info": {
events: Registry<ClientInfoEvents>, events: IpcRegistryDescription<ClientInfoEvents>,
}, },
"music-manage": { "music-manage": {
botEvents: Registry<MusicBotUiEvents>, botEvents: IpcRegistryDescription<MusicBotUiEvents>,
playlistEvents: Registry<MusicPlaylistUiEvents> playlistEvents: IpcRegistryDescription<MusicPlaylistUiEvents>
}, },
"server": { "server": {
handlerId: string, handlerId: string,
chatEvents: Registry<ChannelConversationUiEvents> chatEvents: IpcRegistryDescription<ChannelConversationUiEvents>
} }
} }
@ -40,9 +38,7 @@ export type SideBarNotifyContentData<T extends SideBarType> = {
export interface SideBarEvents { export interface SideBarEvents {
query_content: {}, query_content: {},
query_content_data: { content: SideBarType }, query_content_data: { content: SideBarType },
query_header_data: {},
notify_content: { content: SideBarType }, notify_content: { content: SideBarType },
notify_content_data: SideBarNotifyContentData<SideBarType>, notify_content_data: SideBarNotifyContentData<SideBarType>,
notify_header_data: { events: Registry<SideHeaderEvents> }
} }

View file

@ -11,6 +11,7 @@ import {ErrorBoundary} from "tc-shared/ui/react-elements/ErrorBoundary";
import {MusicBotRenderer} from "tc-shared/ui/frames/side/MusicBotRenderer"; import {MusicBotRenderer} from "tc-shared/ui/frames/side/MusicBotRenderer";
import {ConversationPanel} from "tc-shared/ui/frames/side/AbstractConversationRenderer"; import {ConversationPanel} from "tc-shared/ui/frames/side/AbstractConversationRenderer";
import React = require("react"); import React = require("react");
import {useRegistry} from "tc-shared/ui/react-elements/Helper";
const cssStyle = require("./SideBarRenderer.scss"); const cssStyle = require("./SideBarRenderer.scss");
@ -29,24 +30,28 @@ function useContentData<T extends SideBarType>(type: T) : SideBarTypeData[T] {
const ContentRendererChannel = () => { const ContentRendererChannel = () => {
const contentData = useContentData("channel"); const contentData = useContentData("channel");
if(!contentData) { return null; } const events = useRegistry(contentData?.events);
if(!contentData || !events) { return null; }
return ( return (
<ChannelBarRenderer <ChannelBarRenderer
key={"channel"} key={"channel"}
events={contentData.events} events={events}
/> />
); );
}; };
const ContentRendererServer = () => { const ContentRendererServer = () => {
const contentData = useContentData("server"); const contentData = useContentData("server");
if(!contentData) { return null; } const events = useRegistry(contentData?.chatEvents);
if(!contentData || !events) { return null; }
return ( return (
<ConversationPanel <ConversationPanel
key={"server"} key={"server"}
events={contentData.chatEvents} events={events}
handlerId={contentData.handlerId} handlerId={contentData.handlerId}
messagesDeletable={true} messagesDeletable={true}
noFirstMessageOverlay={false} noFirstMessageOverlay={false}
@ -57,11 +62,13 @@ const ContentRendererServer = () => {
const ContentRendererPrivateConversation = () => { const ContentRendererPrivateConversation = () => {
const contentData = useContentData("private-chat"); const contentData = useContentData("private-chat");
if(!contentData) { return null; } const events = useRegistry(contentData?.events);
if(!contentData || !events) { return null; }
return ( return (
<PrivateConversationsPanel <PrivateConversationsPanel
events={contentData.events} events={events}
handlerId={contentData.handlerId} handlerId={contentData.handlerId}
/> />
); );
@ -69,23 +76,26 @@ const ContentRendererPrivateConversation = () => {
const ContentRendererClientInfo = () => { const ContentRendererClientInfo = () => {
const contentData = useContentData("client-info"); const contentData = useContentData("client-info");
if(!contentData) { return null; } const events = useRegistry(contentData?.events);
if(!contentData || !events) { return null; }
return ( return (
<ClientInfoRenderer <ClientInfoRenderer
events={contentData.events} events={events}
/> />
); );
}; };
const ContentRendererMusicManage = () => { const ContentRendererMusicManage = () => {
const contentData = useContentData("music-manage"); const contentData = useContentData("music-manage");
if(!contentData) { return null; } const botEvents = useRegistry(contentData?.botEvents);
const playlistEvents = useRegistry(contentData?.playlistEvents);
if(!contentData || !botEvents || !playlistEvents) { return null; }
return ( return (
<MusicBotRenderer <MusicBotRenderer
botEvents={contentData.botEvents} botEvents={botEvents}
playlistEvents={contentData.playlistEvents} playlistEvents={playlistEvents}
/> />
); );
}; };

View file

@ -181,7 +181,7 @@ export class ChannelBarController {
this.uiEvents.fire_react("notify_data", { this.uiEvents.fire_react("notify_data", {
content: "conversation", content: "conversation",
data: { data: {
events: this.channelConversations.getUiEvents() events: this.channelConversations.getUiEvents().generateIpcDescription()
} }
}); });
break; break;
@ -190,7 +190,7 @@ export class ChannelBarController {
this.uiEvents.fire_react("notify_data", { this.uiEvents.fire_react("notify_data", {
content: "description", content: "description",
data: { data: {
events: this.description.uiEvents events: this.description.uiEvents.generateIpcDescription()
} }
}); });
break; break;
@ -199,7 +199,7 @@ export class ChannelBarController {
this.uiEvents.fire_react("notify_data", { this.uiEvents.fire_react("notify_data", {
content: "file-transfer", content: "file-transfer",
data: { data: {
events: this.fileBrowser.uiEvents events: this.fileBrowser.uiEvents.generateIpcDescription()
} }
}); });
break; break;

View file

@ -1,4 +1,4 @@
import {Registry} from "tc-shared/events"; import {IpcRegistryDescription} from "tc-shared/events";
import {ChannelConversationUiEvents} from "tc-shared/ui/frames/side/ChannelConversationDefinitions"; import {ChannelConversationUiEvents} from "tc-shared/ui/frames/side/ChannelConversationDefinitions";
import {ChannelDescriptionUiEvents} from "tc-shared/ui/frames/side/ChannelDescriptionDefinitions"; import {ChannelDescriptionUiEvents} from "tc-shared/ui/frames/side/ChannelDescriptionDefinitions";
import {ChannelFileBrowserUiEvents} from "tc-shared/ui/frames/side/ChannelFileBrowserDefinitions"; import {ChannelFileBrowserUiEvents} from "tc-shared/ui/frames/side/ChannelFileBrowserDefinitions";
@ -7,13 +7,13 @@ export type ChannelBarMode = "conversation" | "description" | "file-transfer" |
export interface ChannelBarModeData { export interface ChannelBarModeData {
"conversation": { "conversation": {
events: Registry<ChannelConversationUiEvents>, events: IpcRegistryDescription<ChannelConversationUiEvents>,
}, },
"description": { "description": {
events: Registry<ChannelDescriptionUiEvents> events: IpcRegistryDescription<ChannelDescriptionUiEvents>
}, },
"file-transfer": { "file-transfer": {
events: Registry<ChannelFileBrowserUiEvents> events: IpcRegistryDescription<ChannelFileBrowserUiEvents>
}, },
"none": {} "none": {}
} }

View file

@ -3,7 +3,7 @@ import {ChannelBarMode, ChannelBarModeData, ChannelBarUiEvents} from "tc-shared/
import * as React from "react"; import * as React from "react";
import {useContext, useState} from "react"; import {useContext, useState} from "react";
import {ConversationPanel} from "tc-shared/ui/frames/side/AbstractConversationRenderer"; import {ConversationPanel} from "tc-shared/ui/frames/side/AbstractConversationRenderer";
import {useDependentState} from "tc-shared/ui/react-elements/Helper"; import {useDependentState, useRegistry} from "tc-shared/ui/react-elements/Helper";
import {ChannelDescriptionRenderer} from "tc-shared/ui/frames/side/ChannelDescriptionRenderer"; import {ChannelDescriptionRenderer} from "tc-shared/ui/frames/side/ChannelDescriptionRenderer";
import {ChannelFileBrowser} from "tc-shared/ui/frames/side/ChannelFileBrowserRenderer"; import {ChannelFileBrowser} from "tc-shared/ui/frames/side/ChannelFileBrowserRenderer";
@ -49,12 +49,13 @@ const ModeRenderer = () => {
const ModeRendererConversation = React.memo(() => { const ModeRendererConversation = React.memo(() => {
const channelContext = useContext(ChannelContext); const channelContext = useContext(ChannelContext);
const data = useModeData("conversation", [ channelContext ]); const data = useModeData("conversation", [ channelContext ]);
if(!data) { return null; } const events = useRegistry(data?.events);
if(!data || !events) { return null; }
return ( return (
<ConversationPanel <ConversationPanel
key={"conversation"} key={"conversation"}
events={data.events} events={events}
handlerId={channelContext.handlerId} handlerId={channelContext.handlerId}
messagesDeletable={true} messagesDeletable={true}
noFirstMessageOverlay={false} noFirstMessageOverlay={false}
@ -66,20 +67,22 @@ const ModeRendererConversation = React.memo(() => {
const ModeRendererDescription = React.memo(() => { const ModeRendererDescription = React.memo(() => {
const channelContext = useContext(ChannelContext); const channelContext = useContext(ChannelContext);
const data = useModeData("description", [ channelContext ]); const data = useModeData("description", [ channelContext ]);
if(!data) { return null; } const events = useRegistry(data?.events);
if(!data || !events) { return null; }
return ( return (
<ChannelDescriptionRenderer events={data.events} /> <ChannelDescriptionRenderer events={events} />
); );
}); });
const ModeRendererFileTransfer = React.memo(() => { const ModeRendererFileTransfer = React.memo(() => {
const channelContext = useContext(ChannelContext); const channelContext = useContext(ChannelContext);
const data = useModeData("file-transfer", [ channelContext ]); const data = useModeData("file-transfer", [ channelContext ]);
if(!data) { return null; } const events = useRegistry(data?.events);
if(!data || !events) { return null; }
return ( return (
<ChannelFileBrowser events={data.events} /> <ChannelFileBrowser events={events} />
); );
}); });

View file

@ -1,11 +1,8 @@
import {ConnectionHandler} from "tc-shared/ConnectionHandler"; import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import * as React from "react";
import * as ReactDOM from "react-dom";
import {ChannelVideoRenderer} from "tc-shared/ui/frames/video/Renderer";
import {Registry} from "tc-shared/events"; import {Registry} from "tc-shared/events";
import { import {
ChannelVideoEvents, ChannelVideoEvents,
ChannelVideoStreamState, ChannelVideoStreamState, getVideoStreamMap,
kLocalVideoId, kLocalVideoId,
VideoStreamState VideoStreamState
} from "tc-shared/ui/frames/video/Definitions"; } from "tc-shared/ui/frames/video/Definitions";
@ -24,8 +21,7 @@ import * as _ from "lodash";
import PermissionType from "tc-shared/permission/PermissionType"; import PermissionType from "tc-shared/permission/PermissionType";
import {createErrorModal} from "tc-shared/ui/elements/Modal"; import {createErrorModal} from "tc-shared/ui/elements/Modal";
import {spawnVideoViewerInfo} from "tc-shared/ui/modal/video-viewers/Controller"; import {spawnVideoViewerInfo} from "tc-shared/ui/modal/video-viewers/Controller";
import {guid} from "tc-shared/crypto/uid";
const cssStyle = require("./Renderer.scss");
let videoIdIndex = 0; let videoIdIndex = 0;
interface ClientVideoController { interface ClientVideoController {
@ -234,7 +230,12 @@ class RemoteClientVideoController implements ClientVideoController {
if(!stream) { if(!stream) {
state = { state: "failed", reason: tr("Missing video stream") }; state = { state: "failed", reason: tr("Missing video stream") };
} else { } else {
state = { state: "connected", stream: stream }; const streamUuid = guid();
const streamMap = getVideoStreamMap();
streamMap[streamUuid] = stream;
setTimeout(() => delete streamMap[streamUuid], 60 * 1000);
state = { state: "connected", streamObjectId: streamUuid };
} }
} }

View file

@ -48,7 +48,7 @@ export type VideoStreamState = {
reason?: string reason?: string
} | { } | {
state: "connected", state: "connected",
stream: MediaStream streamObjectId: string
}; };
export type VideoSubscribeInfo = { export type VideoSubscribeInfo = {
@ -173,4 +173,15 @@ export function makeVideoAutoplay(video: HTMLVideoElement) : () => void {
video.removeEventListener("pause", listenerPause); video.removeEventListener("pause", listenerPause);
video.removeEventListener("ended", listenerEnded); video.removeEventListener("ended", listenerEnded);
}; };
}
const kVideoStreamMapName = "__teaspeak_video_streams__" + __build.timestamp + "_" + __build.version;
export function getVideoStreamMap() : { [key: string]: MediaStream } {
let windowInstance = window;
while(windowInstance.opener) {
/* Are we sure about this while loop? */
windowInstance = windowInstance.opener;
}
return windowInstance[kVideoStreamMapName] || (windowInstance[kVideoStreamMapName] = {});
} }

View file

@ -5,7 +5,7 @@ import {ClientIcon} from "svg-sprites/client-icons";
import {Registry} from "tc-shared/events"; import {Registry} from "tc-shared/events";
import { import {
ChannelVideoEvents, ChannelVideoInfo, ChannelVideoEvents, ChannelVideoInfo,
ChannelVideoStreamState, ChannelVideoStreamState, getVideoStreamMap,
kLocalVideoId, makeVideoAutoplay, kLocalVideoId, makeVideoAutoplay,
VideoStreamState, VideoStreamState,
VideoSubscribeInfo VideoSubscribeInfo
@ -269,7 +269,7 @@ const MediaStreamVideoRenderer = React.memo((props: { stream: MediaStream | unde
) )
}); });
const VideoStreamPlayer = (props: { videoId: string, streamType: VideoBroadcastType, className?: string }) => { const VideoStreamPlayer = React.memo((props: { videoId: string, streamType: VideoBroadcastType, className?: string }) => {
const events = useContext(EventContext); const events = useContext(EventContext);
const [ state, setState ] = useState<VideoStreamState>(() => { const [ state, setState ] = useState<VideoStreamState>(() => {
events.fire("query_video_stream", { videoId: props.videoId, broadcastType: props.streamType }); events.fire("query_video_stream", { videoId: props.videoId, broadcastType: props.streamType });
@ -301,9 +301,20 @@ const VideoStreamPlayer = (props: { videoId: string, streamType: VideoBroadcastT
); );
case "connected": case "connected":
const streamMap = getVideoStreamMap();
if(typeof streamMap[state.streamObjectId] === "undefined") {
return (
<div className={cssStyle.text} key={"missing-stream-object"}>
<div>
<Translatable>Missing stream object</Translatable>
</div>
</div>
);
}
return ( return (
<MediaStreamVideoRenderer <MediaStreamVideoRenderer
stream={state.stream} stream={streamMap[state.streamObjectId]}
className={props.className} className={props.className}
title={props.streamType === "camera" ? tr("Camera") : tr("Screen")} title={props.streamType === "camera" ? tr("Camera") : tr("Screen")}
key={"connected"} key={"connected"}
@ -324,7 +335,7 @@ const VideoStreamPlayer = (props: { videoId: string, streamType: VideoBroadcastT
</div> </div>
); );
} }
} });
const VideoPlayer = React.memo((props: { videoId: string, cameraState: ChannelVideoStreamState, screenState: ChannelVideoStreamState }) => { const VideoPlayer = React.memo((props: { videoId: string, cameraState: ChannelVideoStreamState, screenState: ChannelVideoStreamState }) => {
const streamElements = []; const streamElements = [];

View file

@ -1,5 +1,6 @@
import {Dispatch, SetStateAction, useEffect, useMemo, useState} from "react"; import {Dispatch, SetStateAction, useEffect, useMemo, useState} from "react";
import {RegistryKey, RegistryValueType, settings, ValuedRegistryKey} from "tc-shared/settings"; import {RegistryKey, RegistryValueType, settings, ValuedRegistryKey} from "tc-shared/settings";
import {IpcRegistryDescription, Registry} from "tc-events";
export function useDependentState<S>( export function useDependentState<S>(
factory: (prevState?: S) => S, factory: (prevState?: S) => S,
@ -42,4 +43,12 @@ export function useGlobalSetting(key, defaultValue) {
useEffect(() => settings.globalChangeListener(key, value => setValue(value)), []); useEffect(() => settings.globalChangeListener(key, value => setValue(value)), []);
return value; return value;
}
export function useRegistry<T>(description: IpcRegistryDescription<T> | undefined) : Registry<T> | undefined;
export function useRegistry<T>(description: IpcRegistryDescription<T>) : Registry<T>
export function useRegistry(description) {
const events = useMemo(() => description ? Registry.fromIpcDescription(description) : undefined, [ description ]);
useEffect(() => () => events?.destroy(), [ description ]);
return events;
} }

View file

@ -150,6 +150,9 @@ export class WebWindowManager implements WindowManager {
}); });
const features = { const features = {
/* TODO: Configureable and enabled by default! */
noopener: "no",
status: "no", status: "no",
location: "no", location: "no",
toolbar: "no", toolbar: "no",