Rendering the hostbanner via React

master
WolverinDEV 2021-01-05 14:46:09 +01:00
parent 2a120987cf
commit f1d24df7ac
17 changed files with 334 additions and 158 deletions

42
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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;

View File

@ -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());
}

View File

@ -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();
}
}
}

View File

@ -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();

View File

@ -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" }});
}
}

View File

@ -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
}
}

View File

@ -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;
}
}
}

View File

@ -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>
);
});

View File

@ -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;
}
}

View File

@ -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);
}
}));

View File

@ -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[]) {

View File

@ -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));
}

View File

@ -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;
}
}

View File

@ -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,
@ -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<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;
}

View File

@ -2,7 +2,6 @@ import {
AbstractServerConnection,
CommandOptionDefaults,
CommandOptions, ConnectionPing,
ConnectionStateListener,
ConnectionStatistics,
} from "tc-shared/connection/ConnectionBase";
import {ConnectionHandler, ConnectionState, DisconnectReason} from "tc-shared/ConnectionHandler";