Rendering the hostbanner via React
parent
2a120987cf
commit
f1d24df7ac
|
@ -1494,8 +1494,7 @@
|
|||
"@types/prop-types": {
|
||||
"version": "15.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
|
||||
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
|
||||
},
|
||||
"@types/pvutils": {
|
||||
"version": "0.0.2",
|
||||
|
@ -1506,7 +1505,6 @@
|
|||
"version": "16.9.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.26.tgz",
|
||||
"integrity": "sha512-dGuSM+B0Pq1MKXYUMlUQWeS6Jj9IhSAUf9v8Ikaimj+YhkBcQrihWBkmyEhK/1fzkJTwZQkhZp5YhmWa2CH+Rw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^2.2.0"
|
||||
|
@ -1531,6 +1529,14 @@
|
|||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-transition-group": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz",
|
||||
"integrity": "sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==",
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/reactcss": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.3.tgz",
|
||||
|
@ -3897,8 +3903,7 @@
|
|||
"csstype": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz",
|
||||
"integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q=="
|
||||
},
|
||||
"currently-unhandled": {
|
||||
"version": "0.4.1",
|
||||
|
@ -4179,6 +4184,22 @@
|
|||
"utila": "~0.4"
|
||||
}
|
||||
},
|
||||
"dom-helpers": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz",
|
||||
"integrity": "sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"csstype": "^3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"csstype": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz",
|
||||
"integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"dom-serializer": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
|
||||
|
@ -10590,6 +10611,17 @@
|
|||
"react-fast-compare": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"react-transition-group": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
|
||||
"integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2"
|
||||
}
|
||||
},
|
||||
"read-pkg": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
|
||||
|
|
|
@ -92,6 +92,7 @@
|
|||
},
|
||||
"homepage": "https://www.teaspeak.de",
|
||||
"dependencies": {
|
||||
"@types/react-transition-group": "^4.4.0",
|
||||
"broadcastchannel-polyfill": "^1.0.1",
|
||||
"detect-browser": "^5.1.1",
|
||||
"dompurify": "^2.0.8",
|
||||
|
@ -104,6 +105,7 @@
|
|||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-player": "^2.5.0",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"remarkable": "^2.0.1",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"sdp-transform": "^2.14.0",
|
||||
|
|
|
@ -13,7 +13,6 @@ import * as htmltags from "./ui/htmltags";
|
|||
import {FilterMode, InputState, MediaStreamRequestResult} from "./voice/RecorderBase";
|
||||
import {CommandResult} from "./connection/ServerConnectionDeclaration";
|
||||
import {defaultRecorder, RecorderProfile} from "./voice/RecorderProfile";
|
||||
import {Hostbanner} from "./ui/frames/hostbanner";
|
||||
import {connection_log, Regex} from "./ui/modal/ModalConnect";
|
||||
import {formatMessage} from "./ui/frames/chat";
|
||||
import {spawnAvatarUpload} from "./ui/modal/ModalAvatar";
|
||||
|
@ -150,10 +149,6 @@ export class ConnectionHandler {
|
|||
settings: ServerSettings;
|
||||
sound: SoundManager;
|
||||
|
||||
hostbanner: Hostbanner;
|
||||
|
||||
tag_connection_handler: JQuery;
|
||||
|
||||
serverFeatures: ServerFeatures;
|
||||
|
||||
private sideBar: SideBarManager;
|
||||
|
@ -226,7 +221,6 @@ export class ConnectionHandler {
|
|||
|
||||
this.log = new ServerEventLog(this);
|
||||
this.sound = new SoundManager(this);
|
||||
this.hostbanner = new Hostbanner(this);
|
||||
|
||||
this.localClient = new LocalClientEntry(this);
|
||||
this.localClient.channelTree = this.channelTree;
|
||||
|
@ -676,7 +670,6 @@ export class ConnectionHandler {
|
|||
if(this.serverConnection)
|
||||
this.serverConnection.disconnect();
|
||||
|
||||
this.hostbanner.update();
|
||||
this.client_status.lastChannelCodecWarned = 0;
|
||||
|
||||
if(auto_reconnect) {
|
||||
|
@ -1027,9 +1020,6 @@ export class ConnectionHandler {
|
|||
this.event_registry.unregister_handler(this);
|
||||
this.cancel_reconnect(true);
|
||||
|
||||
this.hostbanner?.destroy();
|
||||
this.hostbanner = undefined;
|
||||
|
||||
this.pluginCmdRegistry?.destroy();
|
||||
this.pluginCmdRegistry = undefined;
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ import * as ReactDOM from "react-dom";
|
|||
import {SideBarController} from "tc-shared/ui/frames/SideBarController";
|
||||
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";
|
||||
|
||||
export let server_connections: ConnectionManager;
|
||||
|
||||
|
@ -33,13 +35,14 @@ export class ConnectionManager {
|
|||
private active_handler: ConnectionHandler | undefined;
|
||||
|
||||
private _container_channel_tree: JQuery;
|
||||
private _container_hostbanner: JQuery;
|
||||
private containerChannelVideo: ReplaceableContainer;
|
||||
private containerFooter: HTMLDivElement;
|
||||
private containerServerLog: HTMLDivElement;
|
||||
private containerHostBanner: HTMLDivElement;
|
||||
|
||||
private sideBarController: SideBarController;
|
||||
private serverLogController: ServerEventLogController;
|
||||
private hostBannerController: HostBannerController;
|
||||
|
||||
constructor() {
|
||||
this.event_registry = new Registry<ConnectionManagerEvents>();
|
||||
|
@ -47,12 +50,13 @@ export class ConnectionManager {
|
|||
|
||||
this.sideBarController = new SideBarController();
|
||||
this.serverLogController = new ServerEventLogController();
|
||||
this.hostBannerController = new HostBannerController();
|
||||
|
||||
this.containerChannelVideo = new ReplaceableContainer(document.getElementById("channel-video") as HTMLDivElement);
|
||||
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._container_hostbanner = $("#hostbanner");
|
||||
|
||||
this.sideBarController.renderInto(document.getElementById("chat") as HTMLDivElement);
|
||||
this.set_active_connection(undefined);
|
||||
|
@ -61,6 +65,7 @@ export class ConnectionManager {
|
|||
initializeReactComponents() {
|
||||
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);
|
||||
}
|
||||
|
||||
events() : Registry<ConnectionManagerEvents> {
|
||||
|
@ -122,13 +127,12 @@ export class ConnectionManager {
|
|||
private set_active_connection_(handler: ConnectionHandler) {
|
||||
this.sideBarController.setConnection(handler);
|
||||
this.serverLogController.setConnectionHandler(handler);
|
||||
this.hostBannerController.setConnectionHandler(handler);
|
||||
|
||||
this._container_channel_tree.children().detach();
|
||||
this._container_hostbanner.children().detach();
|
||||
this.containerChannelVideo.replaceWith(handler?.video_frame.getContainer());
|
||||
|
||||
if(handler) {
|
||||
this._container_hostbanner.append(handler.hostbanner.html_tag);
|
||||
this._container_channel_tree.append(handler.channelTree.tag_tree());
|
||||
}
|
||||
|
||||
|
|
|
@ -189,7 +189,7 @@ export class ChannelTree {
|
|||
if(settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)) {
|
||||
const conversation = this.client.getChannelConversations().findOrCreateConversation(0);
|
||||
this.client.getChannelConversations().setSelectedConversation(conversation);
|
||||
this.client.getSideBar().showChannel()
|
||||
this.client.getSideBar().showChannel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -267,7 +267,7 @@ export class ServerEntry extends ChannelTreeEntry<ServerEvents> {
|
|||
log.table(LogType.DEBUG, LogCategory.PERMISSIONS, "Server update properties", entries);
|
||||
}
|
||||
|
||||
let update_bannner = false, update_bookmarks = false;
|
||||
let update_bookmarks = false;
|
||||
for(let variable of variables) {
|
||||
JSON.map_field_to(this.properties, variable.value, variable.key);
|
||||
|
||||
|
@ -277,8 +277,6 @@ export class ServerEntry extends ChannelTreeEntry<ServerEvents> {
|
|||
*/
|
||||
this.properties.virtualserver_icon_id = variable.value as any >>> 0;
|
||||
update_bookmarks = true;
|
||||
} else if(variable.key.indexOf('hostbanner') != -1) {
|
||||
update_bannner = true;
|
||||
}
|
||||
}
|
||||
{
|
||||
|
@ -300,9 +298,6 @@ export class ServerEntry extends ChannelTreeEntry<ServerEvents> {
|
|||
}
|
||||
}
|
||||
|
||||
if(update_bannner)
|
||||
this.channelTree.client.hostbanner.update();
|
||||
|
||||
group.end();
|
||||
if(is_self_notify && this.info_request_promise_resolve) {
|
||||
this.info_request_promise_resolve();
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
import {ConnectionHandler, ConnectionState} from "tc-shared/ConnectionHandler";
|
||||
import {Registry} from "tc-shared/events";
|
||||
import {HostBannerInfoMode, HostBannerUiEvents} from "tc-shared/ui/frames/HostBannerDefinitions";
|
||||
|
||||
export class HostBannerController {
|
||||
readonly uiEvents: Registry<HostBannerUiEvents>;
|
||||
|
||||
private currentConnection: ConnectionHandler;
|
||||
private listenerConnection: (() => void)[];
|
||||
|
||||
constructor() {
|
||||
this.uiEvents = new Registry<HostBannerUiEvents>();
|
||||
}
|
||||
|
||||
setConnectionHandler(handler: ConnectionHandler) {
|
||||
if(this.currentConnection === handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.listenerConnection?.forEach(callback => callback());
|
||||
this.listenerConnection = [];
|
||||
|
||||
this.currentConnection = handler;
|
||||
if(this.currentConnection) {
|
||||
this.initializeConnectionHandler(handler);
|
||||
}
|
||||
|
||||
this.notifyHostBanner();
|
||||
}
|
||||
|
||||
protected initializeConnectionHandler(handler: ConnectionHandler) {
|
||||
this.listenerConnection.push(handler.channelTree.server.events.on("notify_properties_updated", event => {
|
||||
if(
|
||||
"virtualserver_hostbanner_url" in event.updated_properties ||
|
||||
"virtualserver_hostbanner_mode" in event.updated_properties ||
|
||||
"virtualserver_hostbanner_gfx_url" in event.updated_properties ||
|
||||
"virtualserver_hostbanner_gfx_interval" in event.updated_properties
|
||||
) {
|
||||
this.notifyHostBanner();
|
||||
}
|
||||
}));
|
||||
|
||||
this.listenerConnection.push(handler.events().on("notify_connection_state_changed", event => {
|
||||
if(event.oldState === ConnectionState.CONNECTED || event.newState === ConnectionState.CONNECTED) {
|
||||
this.notifyHostBanner();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private notifyHostBanner() {
|
||||
if(this.currentConnection?.connected) {
|
||||
const properties = this.currentConnection.channelTree.server.properties;
|
||||
if(properties.virtualserver_hostbanner_gfx_url) {
|
||||
let mode: HostBannerInfoMode;
|
||||
switch (properties.virtualserver_hostbanner_mode) {
|
||||
case 0:
|
||||
mode = "original";
|
||||
break;
|
||||
|
||||
case 1:
|
||||
mode = "resize";
|
||||
break;
|
||||
|
||||
case 2:
|
||||
default:
|
||||
mode = "resize-ratio";
|
||||
break;
|
||||
}
|
||||
|
||||
this.uiEvents.fire_react("notify_host_banner", {
|
||||
banner: {
|
||||
status: "set",
|
||||
|
||||
linkUrl: properties.virtualserver_hostbanner_url,
|
||||
mode: mode,
|
||||
|
||||
imageUrl: properties.virtualserver_hostbanner_gfx_url,
|
||||
updateInterval: properties.virtualserver_hostbanner_gfx_interval,
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.uiEvents.fire_react("notify_host_banner", { banner: { status: "none" }});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
export type HostBannerInfoMode = "original" | "resize-ratio" | "resize";
|
||||
export type HostBannerInfoSet = {
|
||||
mode: HostBannerInfoMode,
|
||||
linkUrl: string | undefined,
|
||||
|
||||
imageUrl: string,
|
||||
updateInterval: number,
|
||||
}
|
||||
export type HostBannerInfo = {
|
||||
status: "none"
|
||||
} | ({
|
||||
status: "set",
|
||||
} & HostBannerInfoSet);
|
||||
|
||||
export interface HostBannerUiEvents {
|
||||
query_host_banner: {},
|
||||
notify_host_banner: {
|
||||
banner: HostBannerInfo
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
@import "../../../css/static/properties";
|
||||
@import "../../../css/static/mixin";
|
||||
|
||||
html:root {
|
||||
--hostbanner-background: #2e2e2e;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
.withBackground {
|
||||
background-color: var(--hostbanner-background);
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
-moz-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.25);
|
||||
-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.25);
|
||||
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.25);
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.containerImage {
|
||||
height: 0;
|
||||
width: 100%;
|
||||
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
min-height: 0;
|
||||
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
/* We're disabling the transition since it only works on appearing not on disappearing */
|
||||
/* @include transition(height 0.5s ease-in-out); */
|
||||
|
||||
&.state-error {
|
||||
> img {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&.state-loaded {
|
||||
height: 9em;
|
||||
}
|
||||
|
||||
&.mode-original {
|
||||
/* do not adjust */
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.mode-resize {
|
||||
/* do adjust and ignore ration */
|
||||
display: flex;
|
||||
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
> img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.mode-resize-ratio {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
|
||||
> img {
|
||||
object-fit: contain;
|
||||
max-height: 100%;
|
||||
|
||||
/* "Normal" third more */
|
||||
//max-width: 100%;
|
||||
|
||||
/* better adoptable mode */
|
||||
width: min-content;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import {Registry} from "tc-shared/events";
|
||||
import {HostBannerInfo, HostBannerInfoSet, HostBannerUiEvents} from "tc-shared/ui/frames/HostBannerDefinitions";
|
||||
import * as React from "react";
|
||||
import {useEffect, useRef, useState} from "react";
|
||||
import {useGlobalSetting, useTr} from "tc-shared/ui/react-elements/Helper";
|
||||
import {ErrorBoundary} from "tc-shared/ui/react-elements/ErrorBoundary";
|
||||
import {Settings, settings} from "tc-shared/settings";
|
||||
|
||||
const cssStyle = require("./HostBannerRenderer.scss");
|
||||
|
||||
const HostBannerRenderer = React.memo((props: { banner: HostBannerInfoSet, }) => {
|
||||
const [ revision, setRevision ] = useState(Date.now());
|
||||
useEffect(() => {
|
||||
if(!props.banner.updateInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = setTimeout(() => setRevision(Date.now()), props.banner.updateInterval * 1000);
|
||||
return () => clearTimeout(id);
|
||||
});
|
||||
|
||||
const [ loadingState, setLoadingState ] = useState<"loading" | "error" | "loaded">("loading");
|
||||
const refImage = useRef<HTMLImageElement>();
|
||||
|
||||
useRef(() => {
|
||||
if(refImage.current.complete) {
|
||||
setLoadingState(refImage.current.naturalWidth === 0 ? "error" : "loaded");
|
||||
}
|
||||
});
|
||||
|
||||
let appendix = (props.banner.imageUrl.indexOf("?") === -1 ? "?" : "&") + "_ts=" + revision;
|
||||
const withBackground = useGlobalSetting(Settings.KEY_HOSTBANNER_BACKGROUND);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
cssStyle.containerImage + " " + cssStyle["mode-" + props.banner.mode] + " " + cssStyle["state-" + loadingState] + " " +
|
||||
(withBackground ? cssStyle.withBackground : "")
|
||||
}
|
||||
onClick={() => {
|
||||
if(props.banner.linkUrl) {
|
||||
window.open(props.banner.linkUrl, "_blank");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={props.banner.imageUrl + appendix}
|
||||
alt={useTr("Host banner")}
|
||||
onError={() => setLoadingState("error")}
|
||||
onLoad={() => setLoadingState("loaded")}
|
||||
ref={refImage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const HostBanner = React.memo((props: { events: Registry<HostBannerUiEvents> }) => {
|
||||
const [ hostBanner, setHostBanner ] = useState<HostBannerInfo>(() => {
|
||||
props.events.fire("query_host_banner");
|
||||
return { status: "none" };
|
||||
});
|
||||
|
||||
props.events.reactUse("notify_host_banner", event => {
|
||||
setHostBanner(event.banner);
|
||||
}, undefined, []);
|
||||
|
||||
return (
|
||||
<div className={cssStyle.container + " " + (hostBanner.status !== "set" ? cssStyle.disabled : "")}>
|
||||
<ErrorBoundary>
|
||||
{hostBanner.status === "set" ? <HostBannerRenderer key={"banner"} banner={hostBanner} /> : undefined}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
);
|
||||
});
|
|
@ -1,126 +0,0 @@
|
|||
import {ConnectionHandler} from "../../ConnectionHandler";
|
||||
import {settings, Settings} from "../../settings";
|
||||
import {LogCategory} from "../../log";
|
||||
import * as log from "../../log";
|
||||
import { tr } from "tc-shared/i18n/localize";
|
||||
|
||||
export class Hostbanner {
|
||||
readonly html_tag: JQuery<HTMLElement>;
|
||||
readonly client: ConnectionHandler;
|
||||
|
||||
private _destryed = false;
|
||||
private updater;
|
||||
|
||||
constructor(client: ConnectionHandler) {
|
||||
this.client = client;
|
||||
this.html_tag = $.spawn("div").addClass("container-hostbanner");
|
||||
this.html_tag.on('click', event => {
|
||||
const server = this.client.channelTree.server;
|
||||
if(!server || !server.properties.virtualserver_hostbanner_url)
|
||||
return;
|
||||
window.open(server.properties.virtualserver_hostbanner_url, '_blank');
|
||||
});
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if(this.updater) {
|
||||
clearTimeout(this.updater);
|
||||
this.updater = undefined;
|
||||
}
|
||||
if(this.html_tag) {
|
||||
this.html_tag.remove();
|
||||
}
|
||||
this._destryed = true;
|
||||
}
|
||||
|
||||
update() {
|
||||
if(this._destryed) return;
|
||||
|
||||
if(this.updater) {
|
||||
clearTimeout(this.updater);
|
||||
this.updater = undefined;
|
||||
}
|
||||
|
||||
this.html_tag.toggleClass("no-background", !settings.static_global(Settings.KEY_HOSTBANNER_BACKGROUND));
|
||||
|
||||
const tag = this.generate_tag();
|
||||
tag.then(element => {
|
||||
log.debug(LogCategory.CLIENT, tr("Regenerated hostbanner tag. Replacing it: %o"), element);
|
||||
if(!element) {
|
||||
this.html_tag.empty().addClass("disabled");
|
||||
return;
|
||||
}
|
||||
const children = this.html_tag.children();
|
||||
this.html_tag.append(element).removeClass("disabled");
|
||||
|
||||
/* allow the new image be loaded from cache URL */
|
||||
{
|
||||
children
|
||||
.css('z-index', '2')
|
||||
.css('position', 'absolute')
|
||||
.css('height', '100%')
|
||||
.css('width', '100%');
|
||||
setTimeout(() => {
|
||||
children.detach();
|
||||
}, 250);
|
||||
}
|
||||
}).catch(error => {
|
||||
log.warn(LogCategory.CLIENT, tr("Failed to load the hostbanner: %o"), error);
|
||||
this.html_tag.empty().addClass("disabled");
|
||||
});
|
||||
const server = this.client.channelTree.server;
|
||||
this.html_tag.attr('title', server ? server.properties.virtualserver_hostbanner_url : undefined);
|
||||
}
|
||||
|
||||
public static async generate_tag(banner_url: string | undefined, gfx_interval: number, mode: number) : Promise<JQuery | undefined> {
|
||||
if(!banner_url) return undefined;
|
||||
|
||||
if(gfx_interval > 0) {
|
||||
const update_interval = Math.max(gfx_interval, 60);
|
||||
const update_timestamp = (Math.floor((Date.now() / 1000) / update_interval) * update_interval).toString();
|
||||
try {
|
||||
const url = new URL(banner_url);
|
||||
if(url.search.length == 0)
|
||||
banner_url += "?_ts=" + update_timestamp;
|
||||
else
|
||||
banner_url += "&_ts=" + update_timestamp;
|
||||
} catch(error) {
|
||||
console.warn(tr("Failed to parse banner URL: %o. Using default '&' append."), error);
|
||||
banner_url += "&_ts=" + update_timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
/* first now load the image */
|
||||
const image_element = document.createElement("img");
|
||||
await new Promise((resolve, reject) => {
|
||||
image_element.onload = resolve;
|
||||
image_element.onerror = reject;
|
||||
image_element.src = banner_url;
|
||||
image_element.style.display = 'none';
|
||||
document.body.append(image_element);
|
||||
log.debug(LogCategory.CLIENT, tr("Successfully loaded hostbanner image."));
|
||||
});
|
||||
|
||||
image_element.parentNode.removeChild(image_element);
|
||||
image_element.style.display = 'unset';
|
||||
return $.spawn("div").addClass("hostbanner-image-container hostbanner-mode-" + mode).append($(image_element));
|
||||
}
|
||||
|
||||
private async generate_tag?() : Promise<JQuery | undefined> {
|
||||
if(!this.client.connected)
|
||||
return undefined;
|
||||
|
||||
const server = this.client.channelTree.server;
|
||||
if(!server) return undefined;
|
||||
if(!server.properties.virtualserver_hostbanner_gfx_url) return undefined;
|
||||
|
||||
const timeout = server.properties.virtualserver_hostbanner_gfx_interval;
|
||||
const tag = Hostbanner.generate_tag(server.properties.virtualserver_hostbanner_gfx_url, server.properties.virtualserver_hostbanner_gfx_interval, server.properties.virtualserver_hostbanner_mode);
|
||||
if(timeout > 0)
|
||||
this.updater = setTimeout(() => this.update(), timeout * 1000);
|
||||
|
||||
return tag;
|
||||
}
|
||||
}
|
|
@ -80,6 +80,8 @@ export class ChannelBarController {
|
|||
this.listenerConnection.push(handler.channelTree.events.on("notify_selected_entry_changed", event => {
|
||||
if(event.newEntry instanceof ChannelEntry) {
|
||||
this.setChannel(event.newEntry);
|
||||
} else {
|
||||
this.setChannel(undefined);
|
||||
}
|
||||
}));
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import {LogCategory} from "../../log";
|
|||
import * as tooltip from "../../ui/elements/Tooltip";
|
||||
import * as i18nc from "../../i18n/country";
|
||||
import {format_time, formatMessage} from "../../ui/frames/chat";
|
||||
import {Hostbanner} from "../../ui/frames/hostbanner";
|
||||
import * as moment from "moment";
|
||||
import {ErrorCode} from "../../connection/ErrorCode";
|
||||
import { tr } from "tc-shared/i18n/localize";
|
||||
|
@ -93,6 +92,8 @@ function apply_hostbanner(server: ServerEntry, tag: JQuery) {
|
|||
container = $.spawn("div").addClass("container-hostbanner")
|
||||
).addClass("hidden");
|
||||
|
||||
/* FIXME: .... */
|
||||
/*
|
||||
const htag = Hostbanner.generate_tag(server.properties.virtualserver_hostbanner_gfx_url, server.properties.virtualserver_hostbanner_gfx_interval, server.properties.virtualserver_hostbanner_mode);
|
||||
htag.then(t => {
|
||||
if (!t) return;
|
||||
|
@ -100,6 +101,7 @@ function apply_hostbanner(server: ServerEntry, tag: JQuery) {
|
|||
tag.removeClass("hidden");
|
||||
container.append(t);
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
function apply_category_1(server: ServerEntry, tag: JQuery, update_callbacks: ServerBandwidthInfoUpdateCallback[]) {
|
||||
|
|
|
@ -93,8 +93,6 @@ function settings_general_application(container: JQuery, modal: Modal) {
|
|||
const option = container.find(".option-hostbanner-background") as JQuery<HTMLInputElement>;
|
||||
option.on('change', event => {
|
||||
settings.changeGlobal(Settings.KEY_HOSTBANNER_BACKGROUND, option[0].checked);
|
||||
for (const sc of server_connections.all_connections())
|
||||
sc.hostbanner.update();
|
||||
}).prop("checked", settings.static_global(Settings.KEY_HOSTBANNER_BACKGROUND));
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,10 @@ export class ErrorBoundary extends React.Component<{}, ErrorBoundaryState> {
|
|||
<div className={cssStyle.text}>A rendering error has occurred</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
} else if(typeof this.props.children !== "undefined") {
|
||||
return this.props.children;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {Dispatch, SetStateAction, useMemo, useState} from "react";
|
||||
import {Dispatch, SetStateAction, useEffect, useMemo, useState} from "react";
|
||||
import {ConfigValueTypes, settings, SettingsKey, ValuedSettingsKey} from "tc-shared/settings";
|
||||
|
||||
export function useDependentState<S>(
|
||||
factory: (prevState?: S) => S,
|
||||
|
@ -32,3 +33,12 @@ export function useTr(message: string) : string {
|
|||
export function joinClassList(...classes: any[]) : string {
|
||||
return classes.filter(value => typeof value === "string" && value.length > 0).join(" ");
|
||||
}
|
||||
|
||||
export function useGlobalSetting<V extends ConfigValueTypes, DV>(key: SettingsKey<V>, defaultValue: DV) : V | DV;
|
||||
export function useGlobalSetting<V extends ConfigValueTypes>(key: ValuedSettingsKey<V>, defaultValue?: V) : V;
|
||||
export function useGlobalSetting<V extends ConfigValueTypes, DV>(key: SettingsKey<V>, defaultValue: DV) : V | DV {
|
||||
const [ value, setValue ] = useState(settings.global(key, defaultValue));
|
||||
useEffect(() => settings.globalChangeListener(key, value => setValue(value)), []);
|
||||
|
||||
return value;
|
||||
}
|
|
@ -2,7 +2,6 @@ import {
|
|||
AbstractServerConnection,
|
||||
CommandOptionDefaults,
|
||||
CommandOptions, ConnectionPing,
|
||||
ConnectionStateListener,
|
||||
ConnectionStatistics,
|
||||
} from "tc-shared/connection/ConnectionBase";
|
||||
import {ConnectionHandler, ConnectionState, DisconnectReason} from "tc-shared/ConnectionHandler";
|
||||
|
|
Loading…
Reference in New Issue