add channel section and refactor closableOverlay
This commit is contained in:
parent
2335dbab93
commit
c44437f62c
|
@ -748,7 +748,7 @@
|
|||
"message": "Loki Messenger"
|
||||
},
|
||||
"search": {
|
||||
"message": "Enter name or public key",
|
||||
"message": "Search",
|
||||
"description": "Placeholder text in the search input"
|
||||
},
|
||||
"noSearchResults": {
|
||||
|
@ -2550,5 +2550,26 @@
|
|||
"example": "26"
|
||||
}
|
||||
}
|
||||
},
|
||||
"channels": {
|
||||
"message": "Channels"
|
||||
},
|
||||
"addChannel": {
|
||||
"message": "Add channel"
|
||||
},
|
||||
"enterChannelURL": {
|
||||
"message": "Enter Channel URL"
|
||||
},
|
||||
"channelUrlPlaceholder": {
|
||||
"message": "https://yourchannel.lokinet.org"
|
||||
},
|
||||
"addChannelDescription": {
|
||||
"message": "Enter the URL of the public channel you'd like to join in the format above."
|
||||
},
|
||||
"joinChannel": {
|
||||
"message": "Join Channel"
|
||||
},
|
||||
"next": {
|
||||
"message": "Next"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -211,6 +211,9 @@ $session-compose-margin: 20px;
|
|||
}
|
||||
}
|
||||
}
|
||||
.module-search-results {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.module-conversations-list-content {
|
||||
overflow-x: hidden;
|
||||
|
@ -395,6 +398,8 @@ $session-compose-margin: 20px;
|
|||
& > div {
|
||||
display: block;
|
||||
}
|
||||
flex-shrink: 0;
|
||||
@include session-dark-background;
|
||||
|
||||
&.active {
|
||||
background-color: $session-shade-9;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
|
||||
import { MainViewController } from './MainViewController';
|
||||
|
||||
import { ActionsPanel, SectionType } from './session/ActionsPanel';
|
||||
import { LeftPaneMessageSection } from './session/LeftPaneMessageSection';
|
||||
|
@ -9,21 +8,11 @@ import { PropsData as ConversationListItemPropsType } from './ConversationListIt
|
|||
import { PropsData as SearchResultsProps } from './SearchResults';
|
||||
import { SearchOptions } from '../types/Search';
|
||||
import { LeftPaneSectionHeader } from './session/LeftPaneSectionHeader';
|
||||
import {
|
||||
SessionIconButton,
|
||||
SessionIconSize,
|
||||
SessionIconType,
|
||||
} from './session/icon';
|
||||
import { SessionIdEditable } from './session/SessionIdEditable';
|
||||
import { UserSearchDropdown } from './session/UserSearchDropdown';
|
||||
import {
|
||||
SessionButton,
|
||||
SessionButtonColor,
|
||||
SessionButtonType,
|
||||
} from './session/SessionButton';
|
||||
|
||||
import { ConversationType } from '../state/ducks/conversations';
|
||||
import { LeftPaneContactSection } from './session/LeftPaneContactSection';
|
||||
import { LeftPaneSettingSection } from './session/LeftPaneSettingSection';
|
||||
import { LeftPaneChannelSection } from './session/LeftPaneChannelSection';
|
||||
|
||||
// from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5
|
||||
export type RowRendererParamsType = {
|
||||
|
@ -86,81 +75,6 @@ export class LeftPane extends React.Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
public static RENDER_CLOSABLE_OVERLAY(
|
||||
isAddContactView: boolean,
|
||||
onChangeSessionID: any,
|
||||
onCloseClick: any,
|
||||
onButtonClick: any,
|
||||
searchTerm: string,
|
||||
searchResults?: any,
|
||||
updateSearch?: any
|
||||
): JSX.Element {
|
||||
const title = isAddContactView
|
||||
? window.i18n('addContact')
|
||||
: window.i18n('enterRecipient');
|
||||
const buttonText = isAddContactView
|
||||
? window.i18n('addContact')
|
||||
: window.i18n('message');
|
||||
const ourSessionID = window.textsecure.storage.user.getNumber();
|
||||
|
||||
return (
|
||||
<div className="module-left-pane-overlay">
|
||||
<div className="exit">
|
||||
<SessionIconButton
|
||||
iconSize={SessionIconSize.Small}
|
||||
iconType={SessionIconType.Exit}
|
||||
onClick={onCloseClick}
|
||||
/>
|
||||
</div>
|
||||
<h2>{title}</h2>
|
||||
<h3>{window.i18n('enterSessionID')}</h3>
|
||||
<div className="module-left-pane-overlay-border-container">
|
||||
<hr className="white" />
|
||||
<hr className="green" />
|
||||
</div>
|
||||
<SessionIdEditable
|
||||
editable={true}
|
||||
placeholder={window.i18n('pasteSessionIDRecipient')}
|
||||
onChange={onChangeSessionID}
|
||||
/>
|
||||
|
||||
<div className="session-description-long">
|
||||
{window.i18n('usersCanShareTheir...')}
|
||||
</div>
|
||||
{isAddContactView || <h4>{window.i18n('or')}</h4>}
|
||||
|
||||
{isAddContactView || (
|
||||
<UserSearchDropdown
|
||||
searchTerm={searchTerm}
|
||||
updateSearch={updateSearch}
|
||||
placeholder={window.i18n('searchByIDOrDisplayName')}
|
||||
searchResults={searchResults}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isAddContactView && (
|
||||
<div className="panel-text-divider">
|
||||
<span>{window.i18n('yourPublicKey')}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isAddContactView && (
|
||||
<SessionIdEditable
|
||||
editable={false}
|
||||
placeholder=""
|
||||
text={ourSessionID}
|
||||
/>
|
||||
)}
|
||||
<SessionButton
|
||||
buttonColor={SessionButtonColor.Green}
|
||||
buttonType={SessionButtonType.BrandOutline}
|
||||
text={buttonText}
|
||||
onClick={onButtonClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public handleSectionSelected(section: SectionType) {
|
||||
this.props.clearSearch();
|
||||
this.setState({ selectedSection: section });
|
||||
|
@ -187,6 +101,8 @@ export class LeftPane extends React.Component<Props, State> {
|
|||
return this.renderMessageSection();
|
||||
case SectionType.Contact:
|
||||
return this.renderContactSection();
|
||||
case SectionType.Channel:
|
||||
return this.renderChannelSection();
|
||||
case SectionType.Settings:
|
||||
return this.renderSettingSection();
|
||||
case SectionType.Moon:
|
||||
|
@ -259,4 +175,30 @@ export class LeftPane extends React.Component<Props, State> {
|
|||
private renderSettingSection() {
|
||||
return <LeftPaneSettingSection />;
|
||||
}
|
||||
|
||||
private renderChannelSection() {
|
||||
const {
|
||||
openConversationInternal,
|
||||
conversations,
|
||||
searchResults,
|
||||
searchTerm,
|
||||
isSecondaryDevice,
|
||||
updateSearchTerm,
|
||||
search,
|
||||
clearSearch,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<LeftPaneChannelSection
|
||||
openConversationInternal={openConversationInternal}
|
||||
conversations={conversations}
|
||||
searchResults={searchResults}
|
||||
searchTerm={searchTerm}
|
||||
isSecondaryDevice={isSecondaryDevice}
|
||||
updateSearchTerm={updateSearchTerm}
|
||||
search={search}
|
||||
clearSearch={clearSearch}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ export enum SectionType {
|
|||
Profile,
|
||||
Message,
|
||||
Contact,
|
||||
Globe,
|
||||
Channel,
|
||||
Settings,
|
||||
Moon,
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ const Section = ({
|
|||
case SectionType.Contact:
|
||||
iconType = SessionIconType.Users;
|
||||
break;
|
||||
case SectionType.Globe:
|
||||
case SectionType.Channel:
|
||||
iconType = SessionIconType.Globe;
|
||||
break;
|
||||
case SectionType.Settings:
|
||||
|
@ -147,7 +147,7 @@ export class ActionsPanel extends React.Component<Props, State> {
|
|||
const isProfilePageSelected = selectedSection === SectionType.Profile;
|
||||
const isMessagePageSelected = selectedSection === SectionType.Message;
|
||||
const isContactPageSelected = selectedSection === SectionType.Contact;
|
||||
const isGlobePageSelected = selectedSection === SectionType.Globe;
|
||||
const isChannelPageSelected = selectedSection === SectionType.Channel;
|
||||
const isSettingsPageSelected = selectedSection === SectionType.Settings;
|
||||
const isMoonPageSelected = selectedSection === SectionType.Moon;
|
||||
|
||||
|
@ -172,8 +172,8 @@ export class ActionsPanel extends React.Component<Props, State> {
|
|||
notificationCount={receivedFriendRequestCount}
|
||||
/>
|
||||
<Section
|
||||
type={SectionType.Globe}
|
||||
isSelected={isGlobePageSelected}
|
||||
type={SectionType.Channel}
|
||||
isSelected={isChannelPageSelected}
|
||||
onSelect={this.handleSectionSelect}
|
||||
/>
|
||||
<Section
|
||||
|
|
|
@ -0,0 +1,400 @@
|
|||
import React from 'react';
|
||||
import { AutoSizer, List } from 'react-virtualized';
|
||||
|
||||
import {
|
||||
ConversationListItem,
|
||||
PropsData as ConversationListItemPropsType,
|
||||
} from '../ConversationListItem';
|
||||
|
||||
import { LeftPane, RowRendererParamsType } from '../LeftPane';
|
||||
import { SessionButton, SessionButtonColor, SessionButtonType } from './SessionButton';
|
||||
import {
|
||||
PropsData as SearchResultsProps,
|
||||
SearchResults,
|
||||
} from '../SearchResults';
|
||||
import { SearchOptions } from '../../types/Search';
|
||||
import { debounce } from 'lodash';
|
||||
import { cleanSearchTerm } from '../../util/cleanSearchTerm';
|
||||
import { SessionSearchInput } from './SessionSearchInput';
|
||||
import { SessionClosableOverlay } from './SessionClosableOverlay';
|
||||
|
||||
export interface Props {
|
||||
searchTerm: string;
|
||||
isSecondaryDevice: boolean;
|
||||
|
||||
conversations?: Array<ConversationListItemPropsType>;
|
||||
|
||||
searchResults?: SearchResultsProps;
|
||||
|
||||
updateSearchTerm: (searchTerm: string) => void;
|
||||
search: (query: string, options: SearchOptions) => void;
|
||||
openConversationInternal: (id: string, messageId?: string) => void;
|
||||
clearSearch: () => void;
|
||||
}
|
||||
|
||||
|
||||
interface State {
|
||||
showAddChannelView: boolean;
|
||||
channelUrlPasted: string;
|
||||
loading: boolean;
|
||||
connectSuccess: boolean;
|
||||
}
|
||||
|
||||
export class LeftPaneChannelSection extends React.Component<Props, State> {
|
||||
private readonly updateSearchBound: (searchedString: string) => void;
|
||||
private readonly debouncedSearch: (searchTerm: string) => void;
|
||||
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showAddChannelView: false,
|
||||
channelUrlPasted: '',
|
||||
loading: false,
|
||||
connectSuccess: false,
|
||||
};
|
||||
|
||||
this.handleOnPasteUrl = this.handleOnPasteUrl.bind(this);
|
||||
this.handleJoinChannelButtonClick = this.handleJoinChannelButtonClick.bind(this);
|
||||
this.handleToggleOverlay = this.handleToggleOverlay.bind(this);
|
||||
this.updateSearchBound = this.updateSearch.bind(this);
|
||||
this.debouncedSearch = debounce(this.search.bind(this), 20);
|
||||
this.attemptConnection = this.attemptConnection.bind(this);
|
||||
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.updateSearch('');
|
||||
}
|
||||
|
||||
public getCurrentConversations():
|
||||
| Array<ConversationListItemPropsType>
|
||||
| undefined {
|
||||
const { conversations } = this.props;
|
||||
|
||||
let conversationList = conversations;
|
||||
if (conversationList !== undefined) {
|
||||
conversationList = conversationList.filter(
|
||||
// a channel is either a public group or a rss group
|
||||
conversation =>
|
||||
conversation.type === 'group' && (conversation.isPublic || (conversation.lastMessage && conversation.lastMessage.isRss))
|
||||
);
|
||||
}
|
||||
|
||||
return conversationList;
|
||||
}
|
||||
|
||||
|
||||
public renderRow = ({
|
||||
index,
|
||||
key,
|
||||
style,
|
||||
}: RowRendererParamsType): JSX.Element => {
|
||||
const { openConversationInternal } = this.props;
|
||||
|
||||
const conversations = this.getCurrentConversations();
|
||||
|
||||
if (!conversations) {
|
||||
throw new Error('renderRow: Tried to render without conversations');
|
||||
}
|
||||
|
||||
const conversation = conversations[index];
|
||||
|
||||
return (
|
||||
<ConversationListItem
|
||||
key={key}
|
||||
style={style}
|
||||
{...conversation}
|
||||
onClick={openConversationInternal}
|
||||
i18n={window.i18n}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
public renderList(): JSX.Element | Array<JSX.Element | null> {
|
||||
const { openConversationInternal, searchResults } = this.props;
|
||||
|
||||
if (searchResults) {
|
||||
return (
|
||||
<SearchResults
|
||||
{...searchResults}
|
||||
openConversation={openConversationInternal}
|
||||
i18n={window.i18n}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const conversations = this.getCurrentConversations();
|
||||
|
||||
if (!conversations) {
|
||||
throw new Error(
|
||||
'render: must provided conversations if no search results are provided'
|
||||
);
|
||||
}
|
||||
|
||||
const length = conversations.length;
|
||||
|
||||
// Note: conversations is not a known prop for List, but it is required to ensure that
|
||||
// it re-renders when our conversation data changes. Otherwise it would just render
|
||||
// on startup and scroll.
|
||||
const list = (
|
||||
<div className="module-left-pane__list" key={0}>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<List
|
||||
className="module-left-pane__virtual-list"
|
||||
conversations={conversations}
|
||||
height={height}
|
||||
rowCount={length}
|
||||
rowHeight={64}
|
||||
rowRenderer={this.renderRow}
|
||||
width={width}
|
||||
autoHeight={true}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
);
|
||||
|
||||
return [list];
|
||||
}
|
||||
|
||||
public renderHeader(): JSX.Element {
|
||||
const labels = [window.i18n('channels')];
|
||||
|
||||
return LeftPane.RENDER_HEADER(
|
||||
labels,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div className="session-left-pane-section-content">
|
||||
{this.renderHeader()}
|
||||
{this.state.showAddChannelView
|
||||
? this.renderClosableOverlay()
|
||||
: this.renderGroups()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public renderGroups() {
|
||||
|
||||
return (
|
||||
<div className="module-conversations-list-content" >
|
||||
<SessionSearchInput
|
||||
searchString={this.props.searchTerm}
|
||||
onChange={this.updateSearchBound}
|
||||
placeholder={window.i18n('search')}
|
||||
/>
|
||||
{this.renderList()}
|
||||
{this.renderBottomButtons()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public updateSearch(searchTerm: string) {
|
||||
const { updateSearchTerm, clearSearch } = this.props;
|
||||
|
||||
if (!searchTerm) {
|
||||
clearSearch();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ channelUrlPasted: '' }, () => {
|
||||
window.Session.emptyContentEditableDivs();
|
||||
});
|
||||
|
||||
if (updateSearchTerm) {
|
||||
updateSearchTerm(searchTerm);
|
||||
}
|
||||
|
||||
if (searchTerm.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanedTerm = cleanSearchTerm(searchTerm);
|
||||
if (!cleanedTerm) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.debouncedSearch(cleanedTerm);
|
||||
}
|
||||
|
||||
public clearSearch() {
|
||||
this.props.clearSearch();
|
||||
}
|
||||
|
||||
public search() {
|
||||
const { search } = this.props;
|
||||
const { searchTerm, isSecondaryDevice } = this.props;
|
||||
|
||||
if (search) {
|
||||
search(searchTerm, {
|
||||
noteToSelf: window.i18n('noteToSelf').toLowerCase(),
|
||||
ourNumber: window.textsecure.storage.user.getNumber(),
|
||||
regionCode: '',
|
||||
isSecondaryDevice,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private handleToggleOverlay() {
|
||||
this.setState(prevState => ({ showAddChannelView: !prevState.showAddChannelView }));
|
||||
}
|
||||
|
||||
private renderClosableOverlay() {
|
||||
const { searchTerm } = this.props;
|
||||
const { loading } = this.state;
|
||||
|
||||
return (
|
||||
<SessionClosableOverlay overlayMode="channel" onChangeSessionID={this.handleOnPasteUrl} onCloseClick={this.handleToggleOverlay} onButtonClick={this.handleJoinChannelButtonClick} searchTerm={searchTerm} updateSearch={this.updateSearchBound} showSpinner={loading}/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderBottomButtons(): JSX.Element {
|
||||
const edit = window.i18n('edit');
|
||||
const addChannel = window.i18n('addChannel');
|
||||
|
||||
return (
|
||||
<div className="left-pane-contact-bottom-buttons">
|
||||
<SessionButton
|
||||
text={edit}
|
||||
buttonType={SessionButtonType.SquareOutline}
|
||||
buttonColor={SessionButtonColor.White}
|
||||
/>
|
||||
<SessionButton
|
||||
text={addChannel}
|
||||
buttonType={SessionButtonType.SquareOutline}
|
||||
buttonColor={SessionButtonColor.Green}
|
||||
onClick={this.handleToggleOverlay}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private handleOnPasteUrl(e: any) {
|
||||
if (e.target.innerHTML) {
|
||||
const cleanText = e.target.innerHTML.replace(/<\/?[^>]+(>|$)/g, '');
|
||||
this.setState({ channelUrlPasted: cleanText });
|
||||
}
|
||||
}
|
||||
|
||||
private handleJoinChannelButtonClick() {
|
||||
|
||||
const { loading, channelUrlPasted } = this.state;
|
||||
|
||||
if (loading) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (channelUrlPasted.length <= 0) {
|
||||
window.pushToast({
|
||||
title: window.i18n('noServerURL'),
|
||||
type: 'error',
|
||||
id: 'connectToServerFail',
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Make this not hard coded
|
||||
const channelId = 1;
|
||||
this.setState({ loading: true });
|
||||
const connectionResult = this.attemptConnection(channelUrlPasted, channelId);
|
||||
|
||||
// Give 5s maximum for promise to revole. Else, throw error.
|
||||
const maxConnectionDuration = 5000;
|
||||
const connectionTimeout = setTimeout(() => {
|
||||
if (!this.state.connectSuccess) {
|
||||
this.setState({ loading: false });
|
||||
window.pushToast({
|
||||
title: window.i18n('connectToServerFail'),
|
||||
type: 'error',
|
||||
id: 'connectToServerFail',
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}, maxConnectionDuration);
|
||||
|
||||
connectionResult
|
||||
.then(() => {
|
||||
clearTimeout(connectionTimeout);
|
||||
|
||||
if (this.state.loading) {
|
||||
this.setState({
|
||||
connectSuccess: true,
|
||||
loading: false,
|
||||
});
|
||||
window.pushToast({
|
||||
title: window.i18n('connectToServerSuccess'),
|
||||
id: 'connectToServerSuccess',
|
||||
type: 'success',
|
||||
});
|
||||
this.handleToggleOverlay();
|
||||
}
|
||||
})
|
||||
.catch((connectionError: string) => {
|
||||
clearTimeout(connectionTimeout);
|
||||
this.setState({
|
||||
connectSuccess: true,
|
||||
loading: false,
|
||||
});
|
||||
window.pushToast({
|
||||
title: connectionError,
|
||||
id: 'connectToServerFail',
|
||||
type: 'error',
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private async attemptConnection(serverURL: string, channelId: number) {
|
||||
const rawserverURL = serverURL
|
||||
.replace(/^https?:\/\//i, '')
|
||||
.replace(/[/\\]+$/i, '');
|
||||
const sslServerURL = `https://${rawserverURL}`;
|
||||
const conversationId = `publicChat:${channelId}@${rawserverURL}`;
|
||||
|
||||
const conversationExists = window.ConversationController.get(
|
||||
conversationId
|
||||
);
|
||||
if (conversationExists) {
|
||||
// We are already a member of this public chat
|
||||
return new Promise((_resolve, reject) => {
|
||||
reject(window.i18n('publicChatExists'));
|
||||
});
|
||||
}
|
||||
|
||||
const serverAPI = await window.lokiPublicChatAPI.findOrCreateServer(
|
||||
sslServerURL
|
||||
);
|
||||
if (!serverAPI) {
|
||||
// Url incorrect or server not compatible
|
||||
return new Promise((_resolve, reject) => {
|
||||
reject(window.i18n('connectToServerFail'));
|
||||
});
|
||||
}
|
||||
|
||||
const conversation = await window.ConversationController.getOrCreateAndWait(
|
||||
conversationId,
|
||||
'group'
|
||||
);
|
||||
|
||||
await serverAPI.findOrCreateChannel(channelId, conversationId);
|
||||
await conversation.setPublicSource(sslServerURL, channelId);
|
||||
await conversation.setFriendRequestStatus(
|
||||
window.friends.friendRequestStatusEnum.friends
|
||||
);
|
||||
|
||||
return conversation;
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ import {
|
|||
import { AutoSizer, List } from 'react-virtualized';
|
||||
import { validateNumber } from '../../types/PhoneNumber';
|
||||
import { ConversationType } from '../../state/ducks/conversations';
|
||||
import { SessionClosableOverlay } from './SessionClosableOverlay';
|
||||
|
||||
export interface Props {
|
||||
searchTerm: string;
|
||||
|
@ -96,13 +97,7 @@ export class LeftPaneContactSection extends React.Component<Props, State> {
|
|||
<div className="left-pane-contact-section">
|
||||
{this.renderHeader()}
|
||||
{this.state.showAddContactView
|
||||
? LeftPane.RENDER_CLOSABLE_OVERLAY(
|
||||
true,
|
||||
this.handleRecipientSessionIDChanged,
|
||||
this.handleToggleOverlay,
|
||||
this.handleOnAddContact,
|
||||
''
|
||||
)
|
||||
? this.renderClosableOverlay()
|
||||
: this.renderContacts()}
|
||||
</div>
|
||||
);
|
||||
|
@ -198,6 +193,12 @@ export class LeftPaneContactSection extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
private renderClosableOverlay() {
|
||||
return (
|
||||
<SessionClosableOverlay overlayMode="contact" onChangeSessionID={this.handleRecipientSessionIDChanged} onCloseClick={this.handleToggleOverlay} onButtonClick={this.handleOnAddContact} />
|
||||
);
|
||||
}
|
||||
|
||||
private getCurrentFriends(): Array<ConversationType> {
|
||||
const { friends } = this.props;
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import { cleanSearchTerm } from '../../util/cleanSearchTerm';
|
|||
import { SearchOptions } from '../../types/Search';
|
||||
import { validateNumber } from '../../types/PhoneNumber';
|
||||
import { LeftPane, RowRendererParamsType } from '../LeftPane';
|
||||
import { SessionClosableOverlay } from './SessionClosableOverlay';
|
||||
|
||||
export interface Props {
|
||||
searchTerm: string;
|
||||
|
@ -44,7 +45,7 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
|
|||
};
|
||||
|
||||
this.updateSearchBound = this.updateSearch.bind(this);
|
||||
this.handleComposeClick = this.handleComposeClick.bind(this);
|
||||
this.handleToggleOverlay = this.handleToggleOverlay.bind(this);
|
||||
this.handleOnPasteSessionID = this.handleOnPasteSessionID.bind(this);
|
||||
this.handleMessageButtonClick = this.handleMessageButtonClick.bind(this);
|
||||
this.debouncedSearch = debounce(this.search.bind(this), 20);
|
||||
|
@ -152,7 +153,7 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
|
|||
labels,
|
||||
null,
|
||||
window.i18n('compose'),
|
||||
this.handleComposeClick
|
||||
this.handleToggleOverlay
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -163,15 +164,7 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
|
|||
<div className="session-left-pane-section-content">
|
||||
{this.renderHeader()}
|
||||
{this.state.showComposeView
|
||||
? LeftPane.RENDER_CLOSABLE_OVERLAY(
|
||||
false,
|
||||
this.handleOnPasteSessionID,
|
||||
this.handleComposeClick,
|
||||
this.handleMessageButtonClick,
|
||||
this.props.searchTerm,
|
||||
this.props.searchResults,
|
||||
this.updateSearchBound
|
||||
)
|
||||
? this.renderClosableOverlay()
|
||||
: this.renderConversations()}
|
||||
</div>
|
||||
);
|
||||
|
@ -221,7 +214,6 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
|
|||
|
||||
public clearSearch() {
|
||||
this.props.clearSearch();
|
||||
//this.setFocus();
|
||||
}
|
||||
|
||||
public search() {
|
||||
|
@ -238,7 +230,15 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
|
|||
}
|
||||
}
|
||||
|
||||
private handleComposeClick() {
|
||||
private renderClosableOverlay() {
|
||||
const { searchTerm, searchResults } = this.props;
|
||||
|
||||
return (
|
||||
<SessionClosableOverlay overlayMode="message" onChangeSessionID={this.handleOnPasteSessionID} onCloseClick={this.handleToggleOverlay} onButtonClick={this.handleMessageButtonClick} searchTerm={searchTerm} searchResults={searchResults} updateSearch={this.updateSearchBound} />
|
||||
);
|
||||
}
|
||||
|
||||
private handleToggleOverlay() {
|
||||
this.setState((state: any) => {
|
||||
return { showComposeView: !state.showComposeView };
|
||||
});
|
||||
|
@ -249,7 +249,9 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
|
|||
private handleOnPasteSessionID(e: any) {
|
||||
// reset our search, we can either have a pasted sessionID or a sessionID got from a search
|
||||
this.updateSearch('');
|
||||
this.setState({ pubKeyPasted: e.target.innerHTML });
|
||||
const cleanText = e.target.innerHTML.replace(/<\/?[^>]+(>|$)/g, '');
|
||||
|
||||
this.setState({ pubKeyPasted: cleanText });
|
||||
}
|
||||
|
||||
private handleMessageButtonClick() {
|
||||
|
|
|
@ -498,7 +498,8 @@ export class RegistrationTabs extends React.Component<{}, State> {
|
|||
|
||||
private onSecondDeviceSessionIDChanged(e: any) {
|
||||
e.preventDefault();
|
||||
const hexEncodedPubKey = e.target.innerHTML;
|
||||
const cleanText = e.target.innerHTML.replace(/<\/?[^>]+(>|$)/g, '');
|
||||
const hexEncodedPubKey = cleanText;
|
||||
this.setState({
|
||||
primaryDevicePubKey: hexEncodedPubKey,
|
||||
});
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
import React from 'react';
|
||||
|
||||
import {
|
||||
SessionIconButton,
|
||||
SessionIconSize,
|
||||
SessionIconType,
|
||||
} from './icon';
|
||||
import { SessionIdEditable } from './SessionIdEditable';
|
||||
import { UserSearchDropdown } from './UserSearchDropdown';
|
||||
import {
|
||||
SessionButton,
|
||||
SessionButtonColor,
|
||||
SessionButtonType,
|
||||
} from './SessionButton';
|
||||
import { SessionSpinner } from './SessionSpinner';
|
||||
|
||||
|
||||
interface Props {
|
||||
overlayMode: 'message' | 'contact' | 'channel';
|
||||
onChangeSessionID: any;
|
||||
onCloseClick: any;
|
||||
onButtonClick: any;
|
||||
searchTerm?: string;
|
||||
searchResults?: any;
|
||||
updateSearch?: any;
|
||||
showSpinner?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export class SessionClosableOverlay extends React.Component<Props> {
|
||||
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const { overlayMode, onCloseClick, onChangeSessionID, showSpinner, searchTerm, updateSearch, searchResults, onButtonClick } = this.props;
|
||||
|
||||
const isAddContactView = overlayMode === 'contact';
|
||||
const isMessageView = overlayMode === 'message';
|
||||
// const isChannelView = overlayMode === 'channel';
|
||||
|
||||
let title;
|
||||
let buttonText;
|
||||
let descriptionLong;
|
||||
let subtitle;
|
||||
let placeholder;
|
||||
switch (overlayMode) {
|
||||
case 'message':
|
||||
title = window.i18n('enterRecipient');
|
||||
buttonText = window.i18n('next');
|
||||
descriptionLong = window.i18n('usersCanShareTheir...');
|
||||
subtitle = window.i18n('enterSessionID');
|
||||
placeholder = window.i18n('pasteSessionIDRecipient');
|
||||
break;
|
||||
case 'contact':
|
||||
title = window.i18n('addContact');
|
||||
buttonText = window.i18n('addContact');
|
||||
descriptionLong = window.i18n('usersCanShareTheir...');
|
||||
subtitle = window.i18n('enterSessionID')
|
||||
placeholder = window.i18n('pasteSessionIDRecipient');
|
||||
break;
|
||||
case 'channel':
|
||||
default:
|
||||
title = window.i18n('addChannel');
|
||||
buttonText = window.i18n('joinChannel');
|
||||
descriptionLong = window.i18n('addChannelDescription');
|
||||
subtitle = window.i18n('enterChannelURL');
|
||||
placeholder = window.i18n('channelUrlPlaceholder');
|
||||
}
|
||||
|
||||
const ourSessionID = window.textsecure.storage.user.getNumber();
|
||||
|
||||
return (
|
||||
<div className="module-left-pane-overlay">
|
||||
<div className="exit">
|
||||
<SessionIconButton
|
||||
iconSize={SessionIconSize.Small}
|
||||
iconType={SessionIconType.Exit}
|
||||
onClick={onCloseClick}
|
||||
/>
|
||||
</div>
|
||||
<h2>{title}</h2>
|
||||
<h3>{subtitle}</h3>
|
||||
<div className="module-left-pane-overlay-border-container">
|
||||
<hr className="white" />
|
||||
<hr className="green" />
|
||||
</div>
|
||||
<SessionIdEditable
|
||||
editable={true}
|
||||
placeholder={placeholder}
|
||||
onChange={onChangeSessionID}
|
||||
/>
|
||||
{showSpinner && <SessionSpinner />}
|
||||
<div className="session-description-long">
|
||||
{descriptionLong}
|
||||
</div>
|
||||
{isMessageView && <h4>{window.i18n('or')}</h4>}
|
||||
|
||||
{isMessageView && (
|
||||
<UserSearchDropdown
|
||||
searchTerm={searchTerm || ''}
|
||||
updateSearch={updateSearch}
|
||||
placeholder={window.i18n('searchByIDOrDisplayName')}
|
||||
searchResults={searchResults}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isAddContactView && (
|
||||
<div className="panel-text-divider">
|
||||
<span>{window.i18n('yourPublicKey')}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isAddContactView && (
|
||||
<SessionIdEditable
|
||||
editable={false}
|
||||
placeholder=""
|
||||
text={ourSessionID}
|
||||
/>
|
||||
)}
|
||||
<SessionButton
|
||||
buttonColor={SessionButtonColor.Green}
|
||||
buttonType={SessionButtonType.BrandOutline}
|
||||
text={buttonText}
|
||||
onClick={onButtonClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue