sorting-tinkers

pull/1122/head
Vincent 5 years ago
parent 8a80ccc044
commit 5c02dc9371

@ -904,10 +904,6 @@
window.setSettingValue('link-preview-setting', false);
}
// Render onboarding message from LeftPaneMessageSection
// unless user turns it off during their session
window.setSettingValue('render-message-onboarding', true);
// Generates useful random ID for various purposes
window.generateID = () =>
Math.random()

@ -11,7 +11,6 @@ import { LeftPaneSectionHeader } from './session/LeftPaneSectionHeader';
import { ConversationType } from '../state/ducks/conversations';
import { LeftPaneContactSection } from './session/LeftPaneContactSection';
import { LeftPaneSettingSection } from './session/LeftPaneSettingSection';
import { LeftPaneChannelSection } from './session/LeftPaneChannelSection';
import { SessionIconType } from './session/icon';
// from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5
@ -103,8 +102,6 @@ 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:
@ -179,30 +176,4 @@ export class LeftPane extends React.Component<Props, State> {
return <LeftPaneSettingSection isSecondaryDevice={isSecondaryDevice} />;
}
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,29 +7,14 @@ import {
} from './session/settings/SessionSettings';
export const MainViewController = {
renderMessageView: () => {
if (document.getElementById('main-view')) {
ReactDOM.render(<MessageView />, document.getElementById('main-view'));
}
},
renderSettingsView: (category: SessionSettingCategory) => {
// tslint:disable-next-line: no-backbone-get-set-outside-model
const isSecondaryDevice = !!window.textsecure.storage.get(
'isSecondaryDevice'
);
if (document.getElementById('main-view')) {
ReactDOM.render(
<SettingsView
category={category}
isSecondaryDevice={isSecondaryDevice}
/>,
document.getElementById('main-view')
);
}
},
joinChannelStateManager,
onCreateClosedGroup,
renderMessageView,
renderSettingsView,
};
import { ContactType } from './session/SessionMemberListItem';
export class MessageView extends React.Component {
public render() {
return (
@ -50,3 +35,150 @@ export class MessageView extends React.Component {
);
}
}
// /////////////////////////////////////
// //////////// Management /////////////
// /////////////////////////////////////
function joinChannelStateManager(
thisRef: any,
serverURL: string,
onSuccess?: any
) {
// Any component that uses this function MUST have the keys [loading, connectSuccess]
// in their State
// TODO: Make this not hard coded
const channelId = 1;
thisRef.setState({ loading: true });
const connectionResult = window.attemptConnection(serverURL, channelId);
// Give 5s maximum for promise to revole. Else, throw error.
const connectionTimeout = setTimeout(() => {
if (!thisRef.state.connectSuccess) {
thisRef.setState({ loading: false });
window.pushToast({
title: window.i18n('connectToServerFail'),
type: 'error',
id: 'connectToServerFail',
});
return;
}
}, window.CONSTANTS.MAX_CONNECTION_DURATION);
connectionResult
.then(() => {
clearTimeout(connectionTimeout);
if (thisRef.state.loading) {
thisRef.setState({
connectSuccess: true,
loading: false,
});
window.pushToast({
title: window.i18n('connectToServerSuccess'),
id: 'connectToServerSuccess',
type: 'success',
});
if (onSuccess) {
onSuccess();
}
}
})
.catch((connectionError: string) => {
clearTimeout(connectionTimeout);
thisRef.setState({
connectSuccess: true,
loading: false,
});
window.pushToast({
title: connectionError,
id: 'connectToServerFail',
type: 'error',
});
return false;
});
return true;
}
async function onCreateClosedGroup(
groupName: string,
groupMembers: Array<ContactType>,
onSuccess: any
) {
// Validate groupName and groupMembers length
if (
groupName.length === 0 ||
groupName.length > window.CONSTANTS.MAX_GROUP_NAME_LENGTH
) {
window.pushToast({
title: window.i18n(
'invalidGroupName',
window.CONSTANTS.MAX_GROUP_NAME_LENGTH
),
type: 'error',
id: 'invalidGroupName',
});
return;
}
// >= because we add ourself as a member after this. so a 10 group is already invalid as it will be 11 with ourself
if (
groupMembers.length === 0 ||
groupMembers.length >= window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT
) {
window.pushToast({
title: window.i18n(
'invalidGroupSize',
window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT
),
type: 'error',
id: 'invalidGroupSize',
});
return;
}
const groupMemberIds = groupMembers.map(m => m.id);
await window.doCreateGroup(groupName, groupMemberIds);
if (onSuccess) {
onSuccess();
}
return true;
}
// /////////////////////////////////////
// ///////////// Rendering /////////////
// /////////////////////////////////////
function renderMessageView() {
if (document.getElementById('main-view')) {
ReactDOM.render(<MessageView />, document.getElementById('main-view'));
}
}
function renderSettingsView(category: SessionSettingCategory) {
// tslint:disable-next-line: no-backbone-get-set-outside-model
const isSecondaryDevice = !!window.textsecure.storage.get(
'isSecondaryDevice'
);
if (document.getElementById('main-view')) {
ReactDOM.render(
(
<SettingsView
category={category}
isSecondaryDevice={isSecondaryDevice}
/>
),
document.getElementById('main-view')
);
}
}

@ -1,508 +0,0 @@
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';
import { MainViewController } from '../MainViewController';
import { ContactType } from './SessionMemberListItem';
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;
}
export enum SessionGroupType {
Open = 'open-group',
Closed = 'closed-group',
}
interface State {
channelUrlPasted: string;
loading: boolean;
connectSuccess: boolean;
// The type of group that is being added. Undefined in default view.
groupAddType: SessionGroupType | undefined;
}
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 = {
channelUrlPasted: '',
loading: false,
connectSuccess: false,
groupAddType: undefined,
};
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);
}
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 && conversation.type === 'group'
);
}
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('groups')];
return LeftPane.RENDER_HEADER(labels, null);
}
public componentDidMount() {
MainViewController.renderMessageView();
}
public componentDidUpdate() {
MainViewController.renderMessageView();
}
public render(): JSX.Element {
return (
<div className="session-left-pane-section-content">
{this.renderHeader()}
{this.state.groupAddType
? this.renderClosableOverlay(this.state.groupAddType)
: 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: '' });
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(groupType?: SessionGroupType) {
// If no groupType, return to default view.
// Close the overlay with handleToggleOverlay(undefined)
switch (groupType) {
case SessionGroupType.Open:
this.setState({
groupAddType: SessionGroupType.Open,
});
break;
case SessionGroupType.Closed:
this.setState({
groupAddType: SessionGroupType.Closed,
});
break;
default:
// Exit overlay
this.setState({
groupAddType: undefined,
});
}
}
private renderClosableOverlay(groupType: SessionGroupType) {
const { searchTerm } = this.props;
const { loading } = this.state;
const openGroupElement = (
<SessionClosableOverlay
overlayMode={SessionGroupType.Open}
onChangeSessionID={this.handleOnPasteUrl}
onCloseClick={() => {
this.handleToggleOverlay(undefined);
}}
onButtonClick={this.handleJoinChannelButtonClick}
searchTerm={searchTerm}
updateSearch={this.updateSearchBound}
showSpinner={loading}
/>
);
const closedGroupElement = (
<SessionClosableOverlay
overlayMode={SessionGroupType.Closed}
onChangeSessionID={this.handleOnPasteUrl}
onCloseClick={() => {
this.handleToggleOverlay(undefined);
}}
onButtonClick={async (
groupName: string,
groupMembers: Array<ContactType>
) => this.onCreateClosedGroup(groupName, groupMembers)}
searchTerm={searchTerm}
updateSearch={this.updateSearchBound}
showSpinner={loading}
/>
);
return groupType === SessionGroupType.Open
? openGroupElement
: closedGroupElement;
}
private renderBottomButtons(): JSX.Element {
const edit = window.i18n('edit');
const joinOpenGroup = window.i18n('joinOpenGroup');
const createClosedGroup = window.i18n('createClosedGroup');
const showEditButton = false;
return (
<div className="left-pane-contact-bottom-buttons">
{showEditButton && (
<SessionButton
text={edit}
buttonType={SessionButtonType.SquareOutline}
buttonColor={SessionButtonColor.White}
/>
)}
<SessionButton
text={joinOpenGroup}
buttonType={SessionButtonType.SquareOutline}
buttonColor={SessionButtonColor.Green}
onClick={() => {
this.handleToggleOverlay(SessionGroupType.Open);
}}
/>
<SessionButton
text={createClosedGroup}
buttonType={SessionButtonType.SquareOutline}
buttonColor={SessionButtonColor.White}
onClick={() => {
this.handleToggleOverlay(SessionGroupType.Closed);
}}
/>
</div>
);
}
private handleOnPasteUrl(value: string) {
this.setState({ channelUrlPasted: value });
}
private handleJoinChannelButtonClick(groupUrl: string) {
const { loading } = this.state;
if (loading) {
return false;
}
// longest TLD is now (20/02/06) 24 characters per https://jasontucker.blog/8945/what-is-the-longest-tld-you-can-get-for-a-domain-name
const regexURL = /(http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,24}(:[0-9]{1,5})?(\/.*)?/;
if (groupUrl.length <= 0) {
window.pushToast({
title: window.i18n('noServerURL'),
type: 'error',
id: 'connectToServerFail',
});
return false;
}
if (!regexURL.test(groupUrl)) {
window.pushToast({
title: window.i18n('noServerURL'),
type: 'error',
id: 'connectToServerFail',
});
return false;
}
joinChannelStateManager(this, groupUrl, () => {
this.handleToggleOverlay(undefined);
});
return true;
}
private async onCreateClosedGroup(
groupName: string,
groupMembers: Array<ContactType>
) {
// Validate groupName and groupMembers length
if (
groupName.length === 0 ||
groupName.length > window.CONSTANTS.MAX_GROUP_NAME_LENGTH
) {
window.pushToast({
title: window.i18n(
'invalidGroupName',
window.CONSTANTS.MAX_GROUP_NAME_LENGTH
),
type: 'error',
id: 'invalidGroupName',
});
return;
}
// >= because we add ourself as a member after this. so a 10 group is already invalid as it will be 11 with ourself
if (
groupMembers.length === 0 ||
groupMembers.length >= window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT
) {
window.pushToast({
title: window.i18n(
'invalidGroupSize',
window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT
),
type: 'error',
id: 'invalidGroupSize',
});
return;
}
const groupMemberIds = groupMembers.map(m => m.id);
await window.doCreateGroup(groupName, groupMemberIds);
this.handleToggleOverlay(undefined);
window.pushToast({
title: window.i18n('closedGroupCreatedToastTitle'),
type: 'success',
});
return true;
}
}
export function joinChannelStateManager(
thisRef: any,
serverURL: string,
onSuccess?: any
) {
// Any component that uses this function MUST have the keys [loading, connectSuccess]
// in their State
// TODO: Make this not hard coded
const channelId = 1;
thisRef.setState({ loading: true });
const connectionResult = window.attemptConnection(serverURL, channelId);
// Give 5s maximum for promise to revole. Else, throw error.
const connectionTimeout = setTimeout(() => {
if (!thisRef.state.connectSuccess) {
thisRef.setState({ loading: false });
window.pushToast({
title: window.i18n('connectToServerFail'),
type: 'error',
id: 'connectToServerFail',
});
return;
}
}, window.CONSTANTS.MAX_CONNECTION_DURATION);
connectionResult
.then(() => {
clearTimeout(connectionTimeout);
if (thisRef.state.loading) {
thisRef.setState({
connectSuccess: true,
loading: false,
});
window.pushToast({
title: window.i18n('connectToServerSuccess'),
id: 'connectToServerSuccess',
type: 'success',
});
if (onSuccess) {
onSuccess();
}
}
})
.catch((connectionError: string) => {
clearTimeout(connectionTimeout);
thisRef.setState({
connectSuccess: true,
loading: false,
});
window.pushToast({
title: connectionError,
id: 'connectToServerFail',
type: 'error',
});
return false;
});
return true;
}

@ -17,9 +17,11 @@ import {
import { AutoSizer, List } from 'react-virtualized';
import { validateNumber } from '../../types/PhoneNumber';
import { ConversationType } from '../../state/ducks/conversations';
import { SessionClosableOverlay } from './SessionClosableOverlay';
import { SessionClosableOverlay, SessionClosableOverlayType } from './SessionClosableOverlay';
import { MainViewController } from '../MainViewController';
export enum SessionClosableOverlayTypeContact { Contact = 'contact'}
export interface Props {
searchTerm: string;
isSecondaryDevice: boolean;
@ -204,7 +206,7 @@ export class LeftPaneContactSection extends React.Component<Props, State> {
private renderClosableOverlay() {
return (
<SessionClosableOverlay
overlayMode="contact"
overlayMode={SessionClosableOverlayType.Contact}
onChangeSessionID={this.handleRecipientSessionIDChanged}
onCloseClick={this.handleToggleOverlay}
onButtonClick={this.handleOnAddContact}

@ -18,21 +18,19 @@ import { SearchOptions } from '../../types/Search';
import { validateNumber } from '../../types/PhoneNumber';
import { LeftPane, RowRendererParamsType } from '../LeftPane';
import { SessionClosableOverlay } from './SessionClosableOverlay';
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon';
import { SessionIconType } from './icon';
import { ContactType } from './SessionMemberListItem';
import {
SessionButton,
SessionButtonColor,
SessionButtonType,
} from './SessionButton';
import { SessionSpinner } from './SessionSpinner';
import { joinChannelStateManager } from './LeftPaneChannelSection';
export interface Props {
searchTerm: string;
isSecondaryDevice: boolean;
conversations?: Array<ConversationListItemPropsType>;
searchResults?: SearchResultsProps;
updateSearchTerm: (searchTerm: string) => void;
@ -41,17 +39,40 @@ export interface Props {
clearSearch: () => void;
}
export class LeftPaneMessageSection extends React.Component<Props, any> {
export enum SessionComposeToType {
Message = 'message',
OpenGroup = 'open-group',
ClosedGroup = 'closed-group',
}
export const SessionGroupType = {
OpenGroup: SessionComposeToType.OpenGroup,
ClosedGroup: SessionComposeToType.ClosedGroup,
};
export type SessionGroupType = SessionComposeToType;
interface State {
loading: boolean;
overlay: false | SessionComposeToType;
valuePasted: string;
connectSuccess: boolean;
}
export class LeftPaneMessageSection 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 = {
loading: false,
overlay: false,
valuePasted: '',
connectSuccess: false,
};
const conversations = this.getCurrentConversations();
const renderOnboardingSetting = window.getSettingValue(
'render-message-onboarding'
);
const realConversations: Array<ConversationListItemPropsType> = [];
if (conversations) {
@ -64,23 +85,20 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
});
}
const length = realConversations.length;
this.state = {
showComposeView: false,
pubKeyPasted: '',
shouldRenderMessageOnboarding:
length === 0 && renderOnboardingSetting && false,
connectSuccess: false,
loading: false,
};
this.updateSearchBound = this.updateSearch.bind(this);
this.handleOnPaste = this.handleOnPaste.bind(this);
this.handleToggleOverlay = this.handleToggleOverlay.bind(this);
this.handleCloseOnboarding = this.handleCloseOnboarding.bind(this);
this.handleJoinPublicChat = this.handleJoinPublicChat.bind(this);
this.handleOnPasteSessionID = this.handleOnPasteSessionID.bind(this);
this.handleMessageButtonClick = this.handleMessageButtonClick.bind(this);
this.handleNewSessionButtonClick = this.handleNewSessionButtonClick.bind(this);
this.handleJoinChannelButtonClick = this.handleJoinChannelButtonClick.bind(
this
);
this.handleCreateClosedGroupButtonClick = this.handleCreateClosedGroupButtonClick.bind(this);
this.renderClosableOverlay = this.renderClosableOverlay.bind(this);
this.debouncedSearch = debounce(this.search.bind(this), 20);
}
@ -199,16 +217,18 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
null,
undefined,
SessionIconType.Plus,
this.handleToggleOverlay
this.handleNewSessionButtonClick
);
}
public render(): JSX.Element {
const { overlay } = this.state;
return (
<div className="session-left-pane-section-content">
{this.renderHeader()}
{this.state.showComposeView
? this.renderClosableOverlay()
{overlay
? this.renderClosableOverlay(overlay)
: this.renderConversations()}
</div>
);
@ -217,90 +237,17 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
public renderConversations() {
return (
<div className="module-conversations-list-content">
{this.state.shouldRenderMessageOnboarding ? (
<>{this.renderMessageOnboarding()}</>
) : (
<>
<SessionSearchInput
searchString={this.props.searchTerm}
onChange={this.updateSearchBound}
placeholder={window.i18n('searchForAKeyPhrase')}
/>
{this.renderList()}
</>
)}
</div>
);
}
public renderMessageOnboarding() {
return (
<div className="onboarding-message-section">
<div className="onboarding-message-section__exit">
<SessionIconButton
iconType={SessionIconType.Exit}
iconSize={SessionIconSize.Medium}
onClick={this.handleCloseOnboarding}
/>
</div>
<div className="onboarding-message-section__container">
<div className="onboarding-message-section__title">
<h1>{window.i18n('welcomeToSession')}</h1>
</div>
<div className="onboarding-message-section__icons">
<img
src="./images/session/chat-bubbles.svg"
alt=""
role="presentation"
/>
</div>
<div className="onboarding-message-section__info">
<div className="onboarding-message-section__info--title">
{window.i18n('noMessagesTitle')}
</div>
<div className="onboarding-message-section__info--subtitle">
{window.i18n('noMessagesSubtitle')}
</div>
</div>
<>
{this.state.loading ? (
<div className="onboarding-message-section__spinner-container">
<SessionSpinner />
</div>
) : (
<div className="onboarding-message-section__buttons">
<SessionButton
text={window.i18n('joinPublicChat')}
buttonType={SessionButtonType.BrandOutline}
buttonColor={SessionButtonColor.Green}
onClick={this.handleJoinPublicChat}
/>
<SessionButton
text={window.i18n('noThankyou')}
buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.Secondary}
onClick={this.handleCloseOnboarding}
/>
</div>
)}
</>
</div>
<SessionSearchInput
searchString={this.props.searchTerm}
onChange={this.updateSearchBound}
placeholder={window.i18n('searchForAKeyPhrase')}
/>
{this.renderList()}
{this.renderBottomButtons()}
</div>
);
}
public handleCloseOnboarding() {
window.setSettingValue('render-message-onboarding', false);
this.setState({
shouldRenderMessageOnboarding: false,
});
}
public updateSearch(searchTerm: string) {
const { updateSearchTerm, clearSearch } = this.props;
@ -309,8 +256,10 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
return;
}
// reset our pubKeyPasted, we can either have a pasted sessionID or a sessionID got from a search
this.setState({ pubKeyPasted: '' });
this.setState({ valuePasted: '' });
if (updateSearchTerm) {
updateSearchTerm(searchTerm);
@ -346,41 +295,127 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
}
}
private renderClosableOverlay() {
private renderClosableOverlay(overlay: SessionComposeToType) {
const { searchTerm, searchResults } = this.props;
const { loading } = this.state;
return (
const openGroupElement = (
<SessionClosableOverlay
overlayMode={SessionComposeToType.OpenGroup}
onChangeSessionID={this.handleOnPaste}
onCloseClick={() => {
this.handleToggleOverlay(undefined);
}}
onButtonClick={this.handleJoinChannelButtonClick}
searchTerm={searchTerm}
updateSearch={this.updateSearchBound}
showSpinner={loading}
/>
);
const closedGroupElement = (
<SessionClosableOverlay
overlayMode={SessionComposeToType.ClosedGroup}
onChangeSessionID={this.handleOnPaste}
onCloseClick={() => {
this.handleToggleOverlay(undefined);
}}
onButtonClick={async (
groupName: string,
groupMembers: Array<ContactType>
) => this.handleCreateClosedGroupButtonClick(groupName, groupMembers)}
searchTerm={searchTerm}
updateSearch={this.updateSearchBound}
showSpinner={loading}
/>
);
const messageElement = (
<SessionClosableOverlay
overlayMode="message"
onChangeSessionID={this.handleOnPasteSessionID}
onCloseClick={this.handleToggleOverlay}
overlayMode={SessionComposeToType.Message}
onChangeSessionID={this.handleOnPaste}
onCloseClick={() => {
this.handleToggleOverlay(undefined);
}}
onButtonClick={this.handleMessageButtonClick}
searchTerm={searchTerm}
searchResults={searchResults}
updateSearch={this.updateSearchBound}
/>
);
let overlayElement;
switch (overlay) {
case SessionComposeToType.OpenGroup:
overlayElement = openGroupElement;
break;
case SessionComposeToType.ClosedGroup:
overlayElement = closedGroupElement;
break;
default:
overlayElement = messageElement;
}
return overlayElement;
}
private handleToggleOverlay() {
this.setState((state: any) => {
return { showComposeView: !state.showComposeView };
});
// empty our generalized searchedString (one for the whole app)
this.updateSearch('');
private renderBottomButtons(): JSX.Element {
const edit = window.i18n('edit');
const joinOpenGroup = window.i18n('joinOpenGroup');
const createClosedGroup = window.i18n('createClosedGroup');
const showEditButton = false;
return (
<div className="left-pane-contact-bottom-buttons">
{showEditButton && (
<SessionButton
text={edit}
buttonType={SessionButtonType.SquareOutline}
buttonColor={SessionButtonColor.White}
/>
)}
<SessionButton
text={joinOpenGroup}
buttonType={SessionButtonType.SquareOutline}
buttonColor={SessionButtonColor.Green}
onClick={() => {
this.handleToggleOverlay(SessionComposeToType.OpenGroup);
}}
/>
<SessionButton
text={createClosedGroup}
buttonType={SessionButtonType.SquareOutline}
buttonColor={SessionButtonColor.White}
onClick={() => {
this.handleToggleOverlay(SessionComposeToType.ClosedGroup);
}}
/>
</div>
);
}
private handleOnPasteSessionID(value: string) {
// reset our search, we can either have a pasted sessionID or a sessionID got from a search
private handleToggleOverlay(conversationType?: SessionComposeToType) {
const { overlay } = this.state;
const overlayState = overlay
? false
: conversationType || false;
this.setState({ overlay: overlayState});
// empty our generalized searchedString (one for the whole app)
this.updateSearch('');
}
this.setState({ pubKeyPasted: value });
private handleOnPaste(value: string) {
this.setState({ valuePasted: value });
}
private handleMessageButtonClick() {
const { openConversationInternal } = this.props;
if (!this.state.pubKeyPasted && !this.props.searchTerm) {
if (!this.state.valuePasted && !this.props.searchTerm) {
window.pushToast({
title: window.i18n('invalidNumberError'),
type: 'error',
@ -390,7 +425,7 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
return;
}
let pubkey: string;
pubkey = this.state.pubKeyPasted || this.props.searchTerm;
pubkey = this.state.valuePasted || this.props.searchTerm;
pubkey = pubkey.trim();
const error = validateNumber(pubkey);
@ -405,8 +440,64 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
}
}
private handleJoinPublicChat() {
const serverURL = window.CONSTANTS.DEFAULT_PUBLIC_CHAT_URL;
joinChannelStateManager(this, serverURL, this.handleCloseOnboarding);
private handleJoinChannelButtonClick(groupUrl: string) {
const { loading } = this.state;
if (loading) {
return false;
}
// longest TLD is now (20/02/06) 24 characters per https://jasontucker.blog/8945/what-is-the-longest-tld-you-can-get-for-a-domain-name
const regexURL = /(http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,24}(:[0-9]{1,5})?(\/.*)?/;
if (groupUrl.length <= 0) {
window.pushToast({
title: window.i18n('noServerURL'),
type: 'error',
id: 'connectToServerFail',
});
return false;
}
if (!regexURL.test(groupUrl)) {
window.pushToast({
title: window.i18n('noServerURL'),
type: 'error',
id: 'connectToServerFail',
});
return false;
}
MainViewController.joinChannelStateManager(this, groupUrl, () => {
this.handleToggleOverlay(undefined);
});
return true;
}
private async handleCreateClosedGroupButtonClick (
groupName: string,
groupMembers: Array<ContactType>
) {
// console.log('[vince] groupName:', groupName);
// console.log('[vince] groupMembers:', groupMembers);
await MainViewController.onCreateClosedGroup(groupName, groupMembers, () => {
this.handleToggleOverlay(undefined);
window.pushToast({
title: window.i18n('closedGroupCreatedToastTitle'),
type: 'success',
});
});
}
private handleNewSessionButtonClick() {
this.handleToggleOverlay(SessionComposeToType.Message);
}
}

@ -94,7 +94,7 @@ export class LeftPaneSectionHeader extends React.Component<Props, State> {
) : (
buttonLabel
);
;
const button = (
<SessionButton onClick={buttonClicked} key="compose" disabled={false}>
{buttonContent}
@ -128,7 +128,7 @@ export class LeftPaneSectionHeader extends React.Component<Props, State> {
);
}
//Create the parent and add the children
// Create the parent and add the children
return <div className="module-left-pane__header">{children}</div>;
}

@ -11,11 +11,18 @@ import {
SessionButtonType,
} from './SessionButton';
import { SessionSpinner } from './SessionSpinner';
import { SessionGroupType } from './LeftPaneChannelSection';
import { SessionClosableOverlayTypeContact } from './LeftPaneContactSection';
import { SessionComposeToType } from './LeftPaneMessageSection';
import { PillDivider } from './PillDivider';
export const SessionClosableOverlayType = {
...SessionComposeToType,
...SessionClosableOverlayTypeContact,
};
export type SessionClosableOverlayType = SessionComposeToType | SessionClosableOverlayTypeContact;
interface Props {
overlayMode: 'message' | 'contact' | SessionGroupType;
overlayMode: SessionClosableOverlayType;
onChangeSessionID: any;
onCloseClick: any;
onButtonClick: any;
@ -97,11 +104,11 @@ export class SessionClosableOverlay extends React.Component<Props, State> {
onButtonClick,
} = this.props;
const isAddContactView = overlayMode === 'contact';
const isMessageView = overlayMode === 'message';
const isAddContactView = overlayMode === SessionClosableOverlayType.Contact;
const isMessageView = overlayMode === SessionClosableOverlayType.Message;
const isOpenGroupView = overlayMode === SessionGroupType.Open;
const isClosedGroupView = overlayMode === SessionGroupType.Closed;
const isOpenGroupView = overlayMode === SessionClosableOverlayType.OpenGroup;
const isClosedGroupView = overlayMode === SessionClosableOverlayType.ClosedGroup;
let title;
let buttonText;
@ -144,8 +151,10 @@ export class SessionClosableOverlay extends React.Component<Props, State> {
const ourSessionID = window.textsecure.storage.user.getNumber();
const contacts = this.getContacts();
const noContactsForClosedGroup =
overlayMode === SessionGroupType.Closed && contacts.length === 0;
overlayMode === SessionClosableOverlayType.ClosedGroup && contacts.length === 0;
return (
<div className="module-left-pane-overlay">
@ -201,7 +210,7 @@ export class SessionClosableOverlay extends React.Component<Props, State> {
</div>
) : (
<div className="group-member-list__selection">
{this.renderMemberList()}
{this.renderMemberList(contacts)}
</div>
)}
</div>
@ -245,8 +254,10 @@ export class SessionClosableOverlay extends React.Component<Props, State> {
);
}
private renderMemberList() {
const members = this.getContacts();
private renderMemberList(members: any) {
console.log('[vince] sdsdf:');
console.log('[vince] members:', members);
return members.map((member: ContactType) => (
<SessionMemberListItem
@ -282,8 +293,12 @@ export class SessionClosableOverlay extends React.Component<Props, State> {
}
private onGroupNameChanged(event: any) {
console.log('[vince] event:', event);
this.setState({
groupName: event,
});
}
}

@ -55,6 +55,7 @@ export class SessionIdEditable extends React.PureComponent<Props> {
private handleChange(e: any) {
const { editable, onChange } = this.props;
if (editable) {
onChange(e.target.value);
}

Loading…
Cancel
Save