import {Registry} from "tc-shared/events"; import {ConnectionListUIEvents, HandlerStatus} from "tc-shared/ui/frames/connection-handler-list/Definitions"; import * as React from "react"; import {useContext, useEffect, useRef, useState} from "react"; import {IconRenderer, LocalIconRenderer} from "tc-shared/ui/react-elements/Icon"; import {ClientIcon} from "svg-sprites/client-icons"; import {Translatable} from "tc-shared/ui/react-elements/i18n"; import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots"; import ResizeObserver from 'resize-observer-polyfill'; import {LogCategory, logWarn} from "tc-shared/log"; import {ClientIconRenderer} from "tc-shared/ui/react-elements/Icons"; const cssStyle = require("./Renderer.scss"); const Events = React.createContext>(undefined); const ConnectionHandler = (props: { handlerId: string, active: boolean }) => { const events = useContext(Events); const [ status, setStatus ] = useState(() => { events.fire_async("query_handler_status", { handlerId: props.handlerId }); return "loading"; }); events.reactUse("notify_handler_status", event => { if(event.handlerId !== props.handlerId) { return; } setStatus(event.status); }); let displayedName; let cutoffName = false; let voiceReplaying = false; let icon = ; if(status === "loading") { displayedName = tr("loading status"); } else { switch (status.connectionState) { case "connected": cutoffName = status.handlerName.length > 30; voiceReplaying = status.voiceReplaying; displayedName = {status.handlerName}; if(status.serverIcon) { icon = ; } break; case "connecting": displayedName = <>Connecting to server ; break; case "disconnected": displayedName = Not connected; break; } } return (
{ if(props.active) { return; } events.fire("action_set_active_handler", { handlerId: props.handlerId }); }} >
{icon}
{displayedName}
{ events.fire("action_destroy_handler", { handlerId: props.handlerId }) }}>
); } const HandlerList = (props: { refContainer: React.Ref, refSpacer: React.Ref }) => { const events = useContext(Events); const [ handlers, setHandlers ] = useState(() => { events.fire("query_handler_list"); return "loading"; }); const [ activeHandler, setActiveHandler ] = useState(); events.reactUse("notify_handler_list", event => { setHandlers(event.handlerIds.slice()); setActiveHandler(event.activeHandlerId); }); events.reactUse("notify_active_handler", event => setActiveHandler(event.handlerId)); return (
{handlers === "loading" ? undefined : handlers.map(handlerId => ) }
) } const ScrollMenu = (props: { shown: boolean }) => { const events = useContext(Events); const [ scrollLeft, setScrollLeft ] = useState(false); const [ scrollRight, setScrollRight ] = useState(false); events.on("notify_scroll_status", event => { setScrollLeft(event.left); setScrollRight(event.right); }); return (
scrollLeft && events.fire_async("action_scroll", { direction: "left" })}>
scrollRight && events.fire_async("action_scroll", { direction: "right" })}>
); } export const ConnectionHandlerList = (props: { events: Registry }) => { const [ shown, setShown ] = useState(false); const observer = useRef(); const refHandlerContainer = useRef(); const refContainer = useRef(); const refScrollSpacer = useRef(); const refScrollShown = useRef(false); const [ scrollShown, setScrollShown ] = useState(false); refScrollShown.current = scrollShown; const updateScrollButtons = (scrollLeft: number) => { const container = refHandlerContainer.current; if(!container) { return; } props.events.fire_async("notify_scroll_status", { right: Math.ceil(scrollLeft + container.clientWidth + 2) < container.scrollWidth, left: scrollLeft !== 0 }); } useEffect(() => { if(!refHandlerContainer.current) { return; } if(observer.current) { throw "useEffect called without a detachment..."; } observer.current = new ResizeObserver(events => { if(!refContainer.current || !refHandlerContainer.current) { return; } if(events.length !== 1) { logWarn(LogCategory.CLIENT, tr("Handler list resize observer received events for more than one element (%d elements)."), events.length); return; } const width = events[0].target.scrollWidth; const shouldScroll = width > refContainer.current.clientWidth; let scrollShown = refScrollShown.current; if(scrollShown && !shouldScroll) { props.events.fire_async("notify_scroll_status", { left: false, right: false }); } else if(!scrollShown && shouldScroll) { props.events.fire_async("notify_scroll_status", { left: false, right: true }); } else { return; } setScrollShown(shouldScroll); }); observer.current.observe(refHandlerContainer.current); return () => { observer.current?.disconnect(); observer.current = undefined; } }, []); props.events.reactUse("notify_handler_list", event => setShown(event.handlerIds.length > 1)); props.events.reactUse("action_scroll", event => { if(!scrollShown || !refHandlerContainer.current || !refScrollSpacer.current) { return; } const container = refHandlerContainer.current; const scrollContainer = refScrollSpacer.current; const getChildAt = (width: number) => { let currentChild: HTMLDivElement; for(const childElement of container.children) { const child = childElement as HTMLDivElement; if((!currentChild || child.offsetLeft > currentChild.offsetLeft) && child.offsetLeft <= width) { currentChild = child; } } return currentChild; } let scrollLeft; if(event.direction === "right") { const currentLeft = container.scrollLeft; const target = getChildAt(currentLeft + container.clientWidth + 5 - scrollContainer.clientWidth); if(!target) { /* well we're fucked up? :D */ scrollLeft = container.scrollLeft + 50; } else { scrollLeft = target.offsetLeft + target.clientWidth - container.clientWidth + scrollContainer.clientWidth; } } else if(event.direction === "left") { const currentLeft = container.scrollLeft; const target = getChildAt(currentLeft - 1); if(!target) { /* well we're fucked up? :D */ scrollLeft = container.scrollLeft - 50; } else { scrollLeft = target.offsetLeft; } } else { return; } container.scrollLeft = scrollLeft; updateScrollButtons(scrollLeft); }, true, [scrollShown]); return (
) };