TeaWeb/shared/js/ui/tree/RendererChannel.tsx

194 lines
7.9 KiB
TypeScript

import * as React from "react";
import {ChannelNameAlignment} from "tc-shared/ui/tree/Definitions";
import {ClientIcon} from "svg-sprites/client-icons";
import {IconRenderer, RemoteIconRenderer} from "tc-shared/ui/react-elements/Icon";
import {ClientIconRenderer} from "tc-shared/ui/react-elements/Icons";
import {getIconManager} from "tc-shared/file/Icons";
import {Settings, settings} from "tc-shared/settings";
import {RDPChannel} from "tc-shared/ui/tree/RendererDataProvider";
import {UnreadMarkerRenderer} from "tc-shared/ui/tree/RendererTreeEntry";
import {ChannelTreeView} from "tc-shared/ui/tree/RendererView";
const channelStyle = require("./Channel.scss");
const viewStyle = require("./View.scss");
export class ChannelIconClass extends React.Component<{ channel: RDPChannel }, {}> {
render() {
return <IconRenderer icon={this.props.channel.icon} className={channelStyle.channelType} />
}
}
export class ChannelIconsRenderer extends React.Component<{ channel: RDPChannel }, {}> {
render() {
const iconInfo = this.props.channel.icons;
const icons = [];
if (iconInfo?.default) {
icons.push(<ClientIconRenderer key={"icon-default"} icon={ClientIcon.ChannelDefault}
title={tr("Default channel")}/>);
}
if (iconInfo?.passwordProtected) {
icons.push(<ClientIconRenderer key={"icon-protected"} icon={ClientIcon.Register}
title={tr("The channel is password protected")}/>);
}
if (iconInfo?.musicQuality) {
icons.push(<ClientIconRenderer key={"icon-music"} icon={ClientIcon.Music} title={tr("Music quality")}/>);
}
if (iconInfo?.moderated) {
icons.push(<ClientIconRenderer key={"icon-moderated"} icon={ClientIcon.Moderated}
title={tr("Channel is moderated")}/>);
}
if (iconInfo && iconInfo.channelIcon.iconId !== 0) {
icons.push(
<RemoteIconRenderer icon={getIconManager().resolveIcon(iconInfo.channelIcon.iconId, iconInfo.channelIcon.serverUniqueId, this.props.channel.getHandlerId())}
title={tr("Channel icon")}
key={"icon-channel"}
/>
);
}
if (iconInfo?.codecUnsupported) {
icons.push(
<div key={"icon-unsupported"} className={channelStyle.icon_no_sound}>
<div className={"icon_entry icon client-conflict-icon"}
title={tr("You don't support the channel codec")}/>
<div className={channelStyle.background}/>
</div>
);
}
return (
<span className={channelStyle.icons}>
{icons}
</span>
);
}
}
const ChannelName = React.memo((props: { channelName: string | undefined, alignment: ChannelNameAlignment }) => {
let name: string;
if(typeof props.channelName === "string") {
name = props.channelName;
if(props.alignment === "repetitive") {
if (name.length) {
while (name.length < 8000) {
name += name;
}
}
}
} else {
name = "";
}
return (
<div className={channelStyle.containerChannelName + " " + channelStyle["align-" + props.alignment]}>
<a className={channelStyle.channelName}>{name}</a>
</div>
);
});
const ChannelCollapsedIndicator = (props: { collapsed: boolean, onToggle: () => void }) => {
return <div className={channelStyle.containerArrow + (!props.collapsed ? " " + channelStyle.down : "")}>
<div className={viewStyle.arrow + " " + (props.collapsed ? viewStyle.right : viewStyle.down)} onClick={event => {
event.preventDefault();
props.onToggle();
}}/>
</div>
};
export class RendererChannel extends React.Component<{ channel: RDPChannel }, {}> {
render() {
const channel = this.props.channel;
const info = this.props.channel.info;
const events = this.props.channel.getEvents();
const entryId = this.props.channel.entryId;
let channelIcon, channelIcons, collapsedIndicator;
if(!info || info.nameStyle === "normal") {
channelIcon = <ChannelIconClass channel={this.props.channel} key={"channel-icon"} ref={this.props.channel.refIcon} />;
channelIcons = <ChannelIconsRenderer channel={this.props.channel} key={"channel-icons"} ref={this.props.channel.refIcons} />;
}
if(info && info.collapsedState !== "unset") {
collapsedIndicator = (
<ChannelCollapsedIndicator key={"collapsed-indicator"}
onToggle={() => events.fire("action_set_collapsed_state", {
state: info.collapsedState === "expended" ? "collapsed" : "expended",
treeEntryId: entryId
})}
collapsed={info.collapsedState === "collapsed"}
/>
);
}
let dragClass;
if(channel.dragHint !== "none") {
dragClass = channelStyle["drag-" + channel.dragHint];
}
return (
<div
ref={this.props.channel.refChannelContainer}
className={viewStyle.treeEntry + " " + channelStyle.channelEntry + " " + (channel.selected ? viewStyle.selected : "") + " " + dragClass}
style={{
top: (channel.offsetTop * ChannelTreeView.EntryHeightEm) + "em",
}}
onMouseUp={event => {
if (event.button !== 0) {
return; /* only left mouse clicks */
}
this.props.channel.select("auto");
}}
onDoubleClick={() => events.fire("action_channel_join", { treeEntryId: entryId })}
onContextMenu={event => {
if (settings.getValue(Settings.KEY_DISABLE_CONTEXT_MENU)) {
return;
}
event.preventDefault();
this.props.channel.handleUiContextMenu(event.pageX, event.pageY);
}}
onMouseDown={event => {
if (event.buttons !== 4) {
return;
}
event.preventDefault();
events.fire("action_channel_open_file_browser", { treeEntryId: entryId });
}}
draggable={true}
onDragStart={event => this.props.channel.handleUiDragStart(event.nativeEvent)}
onDragOver={event => this.props.channel.handleUiDragOver(event.nativeEvent)}
onDrop={event => this.props.channel.handleUiDrop(event.nativeEvent)}
>
<div className={viewStyle.leftPadding + " " + channelStyle.leftPadding} style={{ paddingLeft: channel.offsetLeft + "em" }} />
<UnreadMarkerRenderer entry={this.props.channel} ref={this.props.channel.refUnread} />
{collapsedIndicator}
{channelIcon}
<ChannelName channelName={info?.name} alignment={info?.nameStyle} />
{channelIcons}
</div>
);
}
componentDidUpdate(prevProps: Readonly<{ channel: RDPChannel }>, prevState: Readonly<{}>, snapshot?: any) {
this.fixCssVariables();
}
componentDidMount() {
this.fixCssVariables();
}
private fixCssVariables() {
const container = this.props.channel.refChannelContainer.current;
if(!container) {
return;
}
container.style.setProperty("--drag-left-offset", this.props.channel.offsetLeft + "em");
}
}