diff --git a/package-lock.json b/package-lock.json index cabbb6e3..fe565e80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 3d100cc7..ca6cd69e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/shared/js/ConnectionHandler.ts b/shared/js/ConnectionHandler.ts index e91be4dc..a755e5db 100644 --- a/shared/js/ConnectionHandler.ts +++ b/shared/js/ConnectionHandler.ts @@ -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; diff --git a/shared/js/ConnectionManager.ts b/shared/js/ConnectionManager.ts index 7f6970b5..cb2254b6 100644 --- a/shared/js/ConnectionManager.ts +++ b/shared/js/ConnectionManager.ts @@ -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(); @@ -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 { @@ -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()); } diff --git a/shared/js/tree/ChannelTree.tsx b/shared/js/tree/ChannelTree.tsx index 79da1c65..dcc0de62 100644 --- a/shared/js/tree/ChannelTree.tsx +++ b/shared/js/tree/ChannelTree.tsx @@ -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(); } } } diff --git a/shared/js/tree/Server.ts b/shared/js/tree/Server.ts index f028e3bf..25bf0974 100644 --- a/shared/js/tree/Server.ts +++ b/shared/js/tree/Server.ts @@ -267,7 +267,7 @@ export class ServerEntry extends ChannelTreeEntry { 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 { */ 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 { } } - if(update_bannner) - this.channelTree.client.hostbanner.update(); - group.end(); if(is_self_notify && this.info_request_promise_resolve) { this.info_request_promise_resolve(); diff --git a/shared/js/ui/frames/HostBannerController.ts b/shared/js/ui/frames/HostBannerController.ts new file mode 100644 index 00000000..7125aae5 --- /dev/null +++ b/shared/js/ui/frames/HostBannerController.ts @@ -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; + + private currentConnection: ConnectionHandler; + private listenerConnection: (() => void)[]; + + constructor() { + this.uiEvents = new Registry(); + } + + 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" }}); + } +} \ No newline at end of file diff --git a/shared/js/ui/frames/HostBannerDefinitions.ts b/shared/js/ui/frames/HostBannerDefinitions.ts new file mode 100644 index 00000000..2242da47 --- /dev/null +++ b/shared/js/ui/frames/HostBannerDefinitions.ts @@ -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 + } +} \ No newline at end of file diff --git a/shared/js/ui/frames/HostBannerRenderer.scss b/shared/js/ui/frames/HostBannerRenderer.scss new file mode 100644 index 00000000..81595762 --- /dev/null +++ b/shared/js/ui/frames/HostBannerRenderer.scss @@ -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; + } + } +} \ No newline at end of file diff --git a/shared/js/ui/frames/HostBannerRenderer.tsx b/shared/js/ui/frames/HostBannerRenderer.tsx new file mode 100644 index 00000000..71173598 --- /dev/null +++ b/shared/js/ui/frames/HostBannerRenderer.tsx @@ -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(); + + 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 ( +
{ + if(props.banner.linkUrl) { + window.open(props.banner.linkUrl, "_blank"); + } + }} + > + {useTr("Host setLoadingState("error")} + onLoad={() => setLoadingState("loaded")} + ref={refImage} + /> +
+ ); +}); + +export const HostBanner = React.memo((props: { events: Registry }) => { + const [ hostBanner, setHostBanner ] = useState(() => { + props.events.fire("query_host_banner"); + return { status: "none" }; + }); + + props.events.reactUse("notify_host_banner", event => { + setHostBanner(event.banner); + }, undefined, []); + + return ( +
+ + {hostBanner.status === "set" ? : undefined} + +
+ ); +}); \ No newline at end of file diff --git a/shared/js/ui/frames/hostbanner.ts b/shared/js/ui/frames/hostbanner.ts deleted file mode 100644 index 9c3a6bac..00000000 --- a/shared/js/ui/frames/hostbanner.ts +++ /dev/null @@ -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; - 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 { - 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 { - 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; - } -} diff --git a/shared/js/ui/frames/side/ChannelBarController.ts b/shared/js/ui/frames/side/ChannelBarController.ts index 1facdf2e..ad30f842 100644 --- a/shared/js/ui/frames/side/ChannelBarController.ts +++ b/shared/js/ui/frames/side/ChannelBarController.ts @@ -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); } })); diff --git a/shared/js/ui/modal/ModalServerInfo.ts b/shared/js/ui/modal/ModalServerInfo.ts index 442fc8ce..d82df6f4 100644 --- a/shared/js/ui/modal/ModalServerInfo.ts +++ b/shared/js/ui/modal/ModalServerInfo.ts @@ -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[]) { diff --git a/shared/js/ui/modal/ModalSettings.tsx b/shared/js/ui/modal/ModalSettings.tsx index dd9d5213..1ea61e7b 100644 --- a/shared/js/ui/modal/ModalSettings.tsx +++ b/shared/js/ui/modal/ModalSettings.tsx @@ -93,8 +93,6 @@ function settings_general_application(container: JQuery, modal: Modal) { const option = container.find(".option-hostbanner-background") as JQuery; 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)); } diff --git a/shared/js/ui/react-elements/ErrorBoundary.tsx b/shared/js/ui/react-elements/ErrorBoundary.tsx index 065a315d..1b962ebb 100644 --- a/shared/js/ui/react-elements/ErrorBoundary.tsx +++ b/shared/js/ui/react-elements/ErrorBoundary.tsx @@ -19,8 +19,10 @@ export class ErrorBoundary extends React.Component<{}, ErrorBoundaryState> {
A rendering error has occurred
); - } else { + } else if(typeof this.props.children !== "undefined") { return this.props.children; + } else { + return null; } } diff --git a/shared/js/ui/react-elements/Helper.ts b/shared/js/ui/react-elements/Helper.ts index e741a859..464c1131 100644 --- a/shared/js/ui/react-elements/Helper.ts +++ b/shared/js/ui/react-elements/Helper.ts @@ -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( factory: (prevState?: S) => S, @@ -31,4 +32,13 @@ 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(key: SettingsKey, defaultValue: DV) : V | DV; +export function useGlobalSetting(key: ValuedSettingsKey, defaultValue?: V) : V; +export function useGlobalSetting(key: SettingsKey, defaultValue: DV) : V | DV { + const [ value, setValue ] = useState(settings.global(key, defaultValue)); + useEffect(() => settings.globalChangeListener(key, value => setValue(value)), []); + + return value; } \ No newline at end of file diff --git a/web/app/connection/ServerConnection.ts b/web/app/connection/ServerConnection.ts index be54869e..cddcc3cf 100644 --- a/web/app/connection/ServerConnection.ts +++ b/web/app/connection/ServerConnection.ts @@ -2,7 +2,6 @@ import { AbstractServerConnection, CommandOptionDefaults, CommandOptions, ConnectionPing, - ConnectionStateListener, ConnectionStatistics, } from "tc-shared/connection/ConnectionBase"; import {ConnectionHandler, ConnectionState, DisconnectReason} from "tc-shared/ConnectionHandler";