217 lines
No EOL
7.9 KiB
TypeScript
217 lines
No EOL
7.9 KiB
TypeScript
import * as React from "react";
|
|
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 {UnreadMarkerRenderer} from "tc-shared/ui/tree/RendererTreeEntry";
|
|
import {RDPClient} from "tc-shared/ui/tree/RendererDataProvider";
|
|
import * as DOMPurify from "dompurify";
|
|
import {ChannelTreeView} from "tc-shared/ui/tree/RendererView";
|
|
import {LogCategory, logWarn} from "tc-shared/log";
|
|
|
|
const clientStyle = require("./Client.scss");
|
|
const viewStyle = require("./View.scss");
|
|
|
|
/* TODO: Render a talk power request */
|
|
export class ClientStatus extends React.Component<{ client: RDPClient }, {}> {
|
|
render() {
|
|
return <IconRenderer icon={this.props.client.status} className={clientStyle.statusIcon} />
|
|
}
|
|
}
|
|
|
|
export class ClientName extends React.Component<{ client: RDPClient }, {}> {
|
|
render() {
|
|
const name = this.props.client.name;
|
|
|
|
if(!name) {
|
|
return null;
|
|
} else {
|
|
let prefixString = "";
|
|
let suffixString = "";
|
|
let awayMessage = "";
|
|
|
|
if(name.prefix.length > 0) {
|
|
prefixString = `[${name.prefix.join(" ")}] `;
|
|
}
|
|
|
|
if(name.suffix.length > 0) {
|
|
suffixString = ` [${name.suffix.join(" ")}]`;
|
|
}
|
|
|
|
if(name.awayMessage) {
|
|
awayMessage = " " + name.awayMessage;
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={clientStyle.clientName + " " + (this.props.client.localClient ? clientStyle.clientNameOwn : "")}>
|
|
{prefixString + name.name + suffixString + awayMessage}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class ClientTalkStatusIcon extends React.Component<{ client: RDPClient }, {}> {
|
|
render() {
|
|
switch (this.props.client.talkStatus) {
|
|
case "prohibited":
|
|
case "requested":
|
|
return <ClientIconRenderer icon={ClientIcon.InputMuted} key={"not-granted"} />;
|
|
|
|
case "granted":
|
|
return <ClientIconRenderer icon={ClientIcon.IsTalker} key={"talker"} />;
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
export class ClientIconsRenderer extends React.Component<{ client: RDPClient }, {}> {
|
|
render() {
|
|
const iconInfo = this.props.client.icons;
|
|
const handlerId = this.props.client.getHandlerId();
|
|
|
|
let icons = [ <ClientTalkStatusIcon client={this.props.client} key={"talk-icon"} ref={this.props.client.refTalkStatus} /> ];
|
|
|
|
if(iconInfo) {
|
|
icons.push(...iconInfo.serverGroupIcons
|
|
.map(icon => (
|
|
<RemoteIconRenderer
|
|
icon={getIconManager().resolveIcon(icon.iconId, icon.serverUniqueId, handlerId)}
|
|
title={`${icon.groupName} (${icon.groupId})`}
|
|
key={"icon-sg-" + icon.groupId}
|
|
/>
|
|
)));
|
|
icons.push(...[iconInfo.channelGroupIcon].filter(e => !!e)
|
|
.map(icon => (
|
|
<RemoteIconRenderer
|
|
icon={getIconManager().resolveIcon(icon.iconId, icon.serverUniqueId, handlerId)}
|
|
title={`${icon.groupName} (${icon.groupId})`}
|
|
key={"icon-cg-" + icon.groupId}
|
|
/>
|
|
)));
|
|
if(iconInfo.clientIcon) {
|
|
icons.push(
|
|
<RemoteIconRenderer
|
|
icon={getIconManager().resolveIcon(iconInfo.clientIcon.iconId, iconInfo.clientIcon.serverUniqueId, handlerId)}
|
|
title={tr("Client icon")}
|
|
key={"icon-client"}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className={clientStyle.containerIcons}>
|
|
{icons}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
interface ClientNameEditProps {
|
|
editFinished: (new_name?: string) => void;
|
|
initialName: string;
|
|
}
|
|
|
|
declare global{
|
|
interface HTMLElement {
|
|
createTextRange;
|
|
}
|
|
}
|
|
function selectText(node: HTMLElement) {
|
|
if (document.body.createTextRange) {
|
|
const range = document.body.createTextRange();
|
|
range.moveToElementText(node);
|
|
range.select();
|
|
} else if (window.getSelection) {
|
|
const selection = window.getSelection();
|
|
const range = document.createRange();
|
|
range.selectNodeContents(node);
|
|
selection.removeAllRanges();
|
|
selection.addRange(range);
|
|
} else {
|
|
logWarn(LogCategory.GENERAL, tr("Could not select text in node: Unsupported browser."));
|
|
}
|
|
}
|
|
|
|
|
|
class ClientNameEdit extends React.Component<ClientNameEditProps, {}> {
|
|
private readonly refDiv: React.RefObject<HTMLDivElement> = React.createRef();
|
|
|
|
componentDidMount(): void {
|
|
this.refDiv.current.focus();
|
|
selectText(this.refDiv.current);
|
|
}
|
|
|
|
render() {
|
|
return <div
|
|
className={clientStyle.clientName + " " + clientStyle.edit}
|
|
contentEditable={true}
|
|
ref={this.refDiv}
|
|
dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(this.props.initialName)}}
|
|
onBlur={() => this.onBlur()}
|
|
onKeyPress={e => this.onKeyPress(e)}
|
|
/>
|
|
}
|
|
|
|
private onBlur() {
|
|
this.props.editFinished(this.refDiv.current.textContent);
|
|
}
|
|
|
|
private onKeyPress(event: React.KeyboardEvent) {
|
|
if (event.key === "Enter") {
|
|
event.preventDefault();
|
|
this.onBlur();
|
|
}
|
|
}
|
|
}
|
|
|
|
export class RendererClient extends React.Component<{ client: RDPClient }, {}> {
|
|
render() {
|
|
const client = this.props.client;
|
|
const selected = this.props.client.selected;
|
|
const events = this.props.client.getEvents();
|
|
|
|
return (
|
|
<div className={clientStyle.clientEntry + " " + viewStyle.treeEntry + " " + (selected ? viewStyle.selected : "")}
|
|
style={{ top: (client.offsetTop * ChannelTreeView.EntryHeightEm) + "em" }}
|
|
onContextMenu={event => {
|
|
if (settings.getValue(Settings.KEY_DISABLE_CONTEXT_MENU)) {
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
this.props.client.handleUiContextMenu(event.pageX, event.pageY);
|
|
}}
|
|
onMouseUp={event => {
|
|
if (event.button !== 0) {
|
|
return; /* only left mouse clicks */
|
|
}
|
|
|
|
this.props.client.select("auto");
|
|
}}
|
|
onDoubleClick={() => this.props.client.handleUiDoubleClicked()}
|
|
draggable={!client.rename}
|
|
onDragStart={event => this.props.client.handleUiDragStart(event.nativeEvent)}
|
|
onDragOver={event => this.props.client.handleUiDragOver(event.nativeEvent)}
|
|
onDrop={event => this.props.client.handleUiDrop(event.nativeEvent)}
|
|
>
|
|
<div className={viewStyle.leftPadding} style={{ paddingLeft: client.offsetLeft + "em" }} />
|
|
<UnreadMarkerRenderer entry={client} ref={client.refUnread} />
|
|
<ClientStatus client={client} ref={client.refStatus} />
|
|
{...(client.rename ? [
|
|
<ClientNameEdit initialName={client.renameDefault} editFinished={value => {
|
|
events.fire("action_client_name_submit", { treeEntryId: client.entryId, name: value });
|
|
}} key={"rename"} />
|
|
] : [
|
|
<ClientName client={client} ref={client.refName} key={"name"} />,
|
|
<ClientIconsRenderer client={client} ref={client.refIcons} key={"icons"} />
|
|
])}
|
|
</div>
|
|
);
|
|
}
|
|
} |