import React from 'react'; import { debounce } from 'lodash'; import classNames from 'classnames'; // Use this to trigger whisper events import { trigger } from '../shims/events'; // Use this to check for password import { hasPassword } from '../shims/Signal'; import { Avatar } from './Avatar'; import { ContactName } from './conversation/ContactName'; import { cleanSearchTerm } from '../util/cleanSearchTerm'; import { LocalizerType } from '../types/Util'; import { clipboard } from 'electron'; import { validateNumber } from '../types/PhoneNumber'; interface MenuItem { id: string; name: string; onClick?: () => void; } export interface Props { searchTerm: string; // To be used as an ID ourNumber: string; regionCode: string; // For display phoneNumber: string; isMe: boolean; name?: string; color: string; verified: boolean; profileName?: string; avatarPath?: string; i18n: LocalizerType; updateSearchTerm: (searchTerm: string) => void; search: ( query: string, options: { regionCode: string; ourNumber: string; noteToSelf: string; } ) => void; clearSearch: () => void; onClick?: () => void; onCopyPublicKey?: () => void; } export class MainHeader extends React.Component { private readonly updateSearchBound: ( event: React.FormEvent ) => void; private readonly clearSearchBound: () => void; private readonly copyFromClipboardBound: () => void; private readonly handleKeyUpBound: ( event: React.KeyboardEvent ) => void; private readonly setFocusBound: () => void; private readonly inputRef: React.RefObject; private readonly debouncedSearch: (searchTerm: string) => void; constructor(props: Props) { super(props); this.state = { expanded: false, hasPass: null, clipboardText: '', menuItems: [], }; this.updateSearchBound = this.updateSearch.bind(this); this.clearSearchBound = this.clearSearch.bind(this); this.copyFromClipboardBound = this.copyFromClipboard.bind(this); this.handleKeyUpBound = this.handleKeyUp.bind(this); this.setFocusBound = this.setFocus.bind(this); this.inputRef = React.createRef(); this.debouncedSearch = debounce(this.search.bind(this), 20); setInterval(() => { const clipboardText = clipboard.readText(); this.setState({ clipboardText }); }, 100); } public componentWillMount() { // tslint:disable-next-line this.updateHasPass(); } public componentDidUpdate(_prevProps: Props, prevState: any) { if (prevState.hasPass !== this.state.hasPass) { this.updateMenuItems(); } } public search() { const { searchTerm, search, i18n, ourNumber, regionCode } = this.props; if (search) { search(searchTerm, { noteToSelf: i18n('noteToSelf').toLowerCase(), ourNumber, regionCode, }); } } public updateSearch(event: React.FormEvent) { const { updateSearchTerm, clearSearch } = this.props; const searchTerm = event.currentTarget.value; if (!searchTerm) { clearSearch(); return; } if (updateSearchTerm) { updateSearchTerm(searchTerm); } if (searchTerm.length < 2) { return; } const cleanedTerm = cleanSearchTerm(searchTerm); if (!cleanedTerm) { return; } this.debouncedSearch(cleanedTerm); } public clearSearch() { const { clearSearch } = this.props; clearSearch(); this.setFocus(); } public copyFromClipboard() { const { clipboardText } = this.state; this.props.updateSearchTerm(clipboardText); this.debouncedSearch(clipboardText); } public handleKeyUp(event: React.KeyboardEvent) { const { clearSearch } = this.props; if (event.key === 'Escape') { clearSearch(); } } public setFocus() { if (this.inputRef.current) { // @ts-ignore this.inputRef.current.focus(); } } public render() { const { onClick } = this.props; return (
{this.renderName()} {this.renderMenu()}
{this.renderSearch()}
); } private renderName() { const { avatarPath, i18n, color, name, phoneNumber, profileName, } = this.props; const { expanded } = this.state; return (
{ this.setState({ expanded: !expanded }); }} >
); } private renderMenu() { const { expanded, menuItems } = this.state; return (
{menuItems.map((item: MenuItem) => (
{item.name}
))}
); } private shouldShowPaste() { const { searchTerm, i18n } = this.props; const { clipboardText } = this.state; const error = validateNumber(clipboardText, i18n); return !searchTerm && !error; } private renderSearch() { const { searchTerm, i18n } = this.props; return (
{this.shouldShowPaste() ? ( ) : null} {searchTerm ? ( ) : null}
); } private async updateHasPass() { const hasPass = await hasPassword(); this.setState({ hasPass }); } private updateMenuItems() { const { i18n, onCopyPublicKey } = this.props; const { hasPass } = this.state; const menuItems = [ { id: 'copyPublicKey', name: i18n('copyPublicKey'), onClick: onCopyPublicKey, }, { id: 'editDisplayName', name: i18n('editDisplayName'), onClick: () => { trigger('onEditProfile'); }, }, { id: 'showSeed', name: i18n('showSeed'), onClick: () => { trigger('showSeedDialog'); }, }, { id: 'showQRCode', name: i18n('showQRCode'), onClick: () => { trigger('showQRDialog'); }, }, { id: 'showAddServer', name: i18n('showAddServer'), onClick: () => { trigger('showAddServerDialog'); }, }, ]; const passItem = (type: string) => ({ id: `${type}Password`, name: i18n(`${type}Password`), onClick: () => { trigger('showPasswordDialog', { type, resolve: () => { trigger('showToast', { message: i18n(`${type}PasswordSuccess`), }); setTimeout(async () => this.updateHasPass(), 100); }, }); }, }); if (hasPass) { menuItems.push(passItem('change'), passItem('remove')); } else { menuItems.push(passItem('set')); } this.setState({ menuItems }); } }