import { BatchUpdateAssignment, BatchUpdateType, ReactComponentBase } from "tc-shared/ui/react-elements/ReactComponentBase"; import * as React from "react"; import {ChannelEntry as ChannelEntryController, ChannelEvents, ChannelProperties} from "../../tree/Channel"; import {RemoteIconRenderer} from "tc-shared/ui/react-elements/Icon"; import {EventHandler, ReactEventHandler} from "tc-shared/events"; import {Settings, settings} from "tc-shared/settings"; import {TreeEntry, UnreadMarker} from "tc-shared/ui/tree/TreeEntry"; import {spawnFileTransferModal} from "tc-shared/ui/modal/transfer/ModalFileTransfer"; import {ClientIconRenderer} from "tc-shared/ui/react-elements/Icons"; import {ClientIcon} from "svg-sprites/client-icons"; import {VoiceConnectionStatus} from "tc-shared/connection/VoiceConnection"; import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase"; import {getIconManager} from "tc-shared/file/Icons"; const channelStyle = require("./Channel.scss"); const viewStyle = require("./View.scss"); interface ChannelEntryIconsProperties { channel: ChannelEntryController; } interface ChannelEntryIconsState { icons_shown: boolean; is_default: boolean; is_password_protected: boolean; is_music_quality: boolean; is_moderated: boolean; is_codec_supported: boolean; custom_icon_id: number; } @ReactEventHandler(e => e.props.channel.events) @BatchUpdateAssignment(BatchUpdateType.CHANNEL_TREE) class ChannelEntryIcons extends ReactComponentBase { private readonly listenerVoiceStatusChange; private serverConnection: AbstractServerConnection; constructor(props) { super(props); this.listenerVoiceStatusChange = () => { let stateUpdate = {} as ChannelEntryIconsState; this.updateVoiceStatus(stateUpdate, this.props.channel.properties.channel_codec); this.setState(stateUpdate); } } componentDidMount() { const voiceConnection = this.serverConnection.getVoiceConnection(); voiceConnection.events.on("notify_connection_status_changed", this.listenerVoiceStatusChange); } componentWillUnmount() { const voiceConnection = this.serverConnection.getVoiceConnection(); voiceConnection.events.off("notify_connection_status_changed", this.listenerVoiceStatusChange); } protected defaultState(): ChannelEntryIconsState { this.serverConnection = this.props.channel.channelTree.client.serverConnection; const properties = this.props.channel.properties; const status = { icons_shown: this.props.channel.parsed_channel_name.alignment === "normal", custom_icon_id: properties.channel_icon_id, is_music_quality: properties.channel_codec === 3 || properties.channel_codec === 5, is_codec_supported: false, is_default: properties.channel_flag_default, is_password_protected: properties.channel_flag_password, is_moderated: properties.channel_needed_talk_power !== 0 } this.updateVoiceStatus(status, this.props.channel.properties.channel_codec); return status; } render() { let icons = []; if (!this.state.icons_shown) return null; if (this.state.is_default) { icons.push(); } if (this.state.is_password_protected) { icons.push(); } if (this.state.is_music_quality) { icons.push(); } if (this.state.is_moderated) { icons.push(); } if (this.state.custom_icon_id) { const connection = this.props.channel.channelTree.client; icons.push(); } if (!this.state.is_codec_supported) { icons.push(
); } return ( {icons} ); } @EventHandler("notify_properties_updated") private handlePropertiesUpdate(event: ChannelEvents["notify_properties_updated"]) { let updates = {} as ChannelEntryIconsState; if (typeof event.updated_properties.channel_icon_id !== "undefined") updates.custom_icon_id = event.updated_properties.channel_icon_id; if (typeof event.updated_properties.channel_codec !== "undefined" || typeof event.updated_properties.channel_codec_quality !== "undefined") { const codec = event.channel_properties.channel_codec; updates.is_music_quality = codec === 3 || codec === 5; } if (typeof event.updated_properties.channel_codec !== "undefined") { this.updateVoiceStatus(updates, event.channel_properties.channel_codec); } if (typeof event.updated_properties.channel_flag_default !== "undefined") updates.is_default = event.updated_properties.channel_flag_default; if (typeof event.updated_properties.channel_flag_password !== "undefined") updates.is_password_protected = event.updated_properties.channel_flag_password; if (typeof event.updated_properties.channel_needed_talk_power !== "undefined") updates.is_moderated = event.updated_properties.channel_needed_talk_power !== 0; if (typeof event.updated_properties.channel_name !== "undefined") updates.icons_shown = this.props.channel.parsed_channel_name.alignment === "normal"; this.setState(updates); } private updateVoiceStatus(state: ChannelEntryIconsState, currentCodec: number) { const voiceConnection = this.serverConnection.getVoiceConnection(); const voiceState = voiceConnection.getConnectionState(); switch (voiceState) { case VoiceConnectionStatus.Connected: state.is_codec_supported = voiceConnection.decodingSupported(currentCodec); break; default: state.is_codec_supported = false; } } } interface ChannelEntryIconProperties { channel: ChannelEntryController; } @ReactEventHandler(e => e.props.channel.events) @BatchUpdateAssignment(BatchUpdateType.CHANNEL_TREE) class ChannelEntryIcon extends ReactComponentBase { private static readonly IconUpdateKeys: (keyof ChannelProperties)[] = [ "channel_name", "channel_flag_password", "channel_maxclients", "channel_flag_maxclients_unlimited", "channel_maxfamilyclients", "channel_flag_maxfamilyclients_inherited", "channel_flag_maxfamilyclients_unlimited", ]; render() { if (this.props.channel.formattedChannelName() !== this.props.channel.channelName()) return null; const channel_properties = this.props.channel.properties; const subscribed = this.props.channel.flag_subscribed; let channelIcon: ClientIcon; if (channel_properties.channel_flag_password === true && !this.props.channel.cached_password()) { channelIcon = subscribed ? ClientIcon.ChannelYellowSubscribed : ClientIcon.ChannelYellow; } else if (!channel_properties.channel_flag_maxclients_unlimited && this.props.channel.clients().length >= channel_properties.channel_maxclients) { channelIcon = subscribed ? ClientIcon.ChannelRedSubscribed : ClientIcon.ChannelRed; } else if (!channel_properties.channel_flag_maxfamilyclients_unlimited && channel_properties.channel_maxfamilyclients >= 0 && this.props.channel.clients(true).length >= channel_properties.channel_maxfamilyclients) { channelIcon = subscribed ? ClientIcon.ChannelRedSubscribed : ClientIcon.ChannelRed; } else { channelIcon = subscribed ? ClientIcon.ChannelGreenSubscribed : ClientIcon.ChannelGreen; } return ; } @EventHandler("notify_properties_updated") private handlePropertiesUpdate(event: ChannelEvents["notify_properties_updated"]) { for (const key of ChannelEntryIcon.IconUpdateKeys) { if (key in event.updated_properties) { this.forceUpdate(); return; } } } /* A client change may cause the channel to show another flag */ @EventHandler("notify_clients_changed") private handleClientsUpdated() { this.forceUpdate(); } @EventHandler("notify_cached_password_updated") private handleCachedPasswordUpdate() { this.forceUpdate(); } @EventHandler("notify_subscribe_state_changed") private handleSubscribeModeChanges() { this.forceUpdate(); } } interface ChannelEntryNameProperties { channel: ChannelEntryController; } @ReactEventHandler(e => e.props.channel.events) @BatchUpdateAssignment(BatchUpdateType.CHANNEL_TREE) class ChannelEntryName extends ReactComponentBase { render() { const name = this.props.channel.parsed_channel_name; let class_name: string; let text: string; if (name.repetitive) { class_name = "align-repetitive"; text = name.text; if (text.length) { while (text.length < 8000) text += text; } } else { text = name.text; class_name = "align-" + name.alignment; } return ; } @EventHandler("notify_properties_updated") private handlePropertiesUpdate(event: ChannelEvents["notify_properties_updated"]) { if (typeof event.updated_properties.channel_name !== "undefined") this.forceUpdate(); } } export interface ChannelEntryViewProperties { channel: ChannelEntryController; depth: number; offset: number; } const ChannelCollapsedIndicator = (props: { collapsed: boolean, onToggle: () => void }) => { return
{ event.preventDefault(); props.onToggle(); }}/>
}; @ReactEventHandler(e => e.props.channel.events) @BatchUpdateAssignment(BatchUpdateType.CHANNEL_TREE) export class ChannelEntryView extends TreeEntry { shouldComponentUpdate(nextProps: Readonly, nextState: Readonly<{}>, nextContext: any): boolean { if (nextProps.offset !== this.props.offset) return true; if (nextProps.depth !== this.props.depth) return true; return nextProps.channel !== this.props.channel; } render() { const collapsed_indicator = this.props.channel.child_channel_head || this.props.channel.clients(false).length > 0; return
this.onMouseUp(e)} onDoubleClick={() => this.onDoubleClick()} onContextMenu={e => this.onContextMenu(e)} onMouseDown={e => this.onMouseDown(e)} > {collapsed_indicator && this.onCollapsedToggle()} collapsed={this.props.channel.collapsed}/>}
; } private onCollapsedToggle() { this.props.channel.collapsed = !this.props.channel.collapsed; } private onMouseUp(event: React.MouseEvent) { if (event.button !== 0) return; /* only left mouse clicks */ const channel = this.props.channel; if (channel.channelTree.isClientMoveActive()) return; channel.channelTree.events.fire("action_select_entries", { entries: [channel], mode: "auto" }); } private onDoubleClick() { const channel = this.props.channel; if (channel.channelTree.selection.is_multi_select()) return; channel.joinChannel(); } private onMouseDown(event: React.MouseEvent) { if (event.buttons !== 4) return; spawnFileTransferModal(this.props.channel.getChannelId()); } private onContextMenu(event: React.MouseEvent) { if (settings.static(Settings.KEY_DISABLE_CONTEXT_MENU)) return; event.preventDefault(); const channel = this.props.channel; if (channel.channelTree.selection.is_multi_select() && channel.isSelected()) return; channel.channelTree.events.fire("action_select_entries", { entries: [channel], mode: "exclusive" }); channel.showContextMenu(event.pageX, event.pageY); } @EventHandler("notify_select_state_change") private handleSelectStateChange() { this.forceUpdate(); } }