sorting-tinkers

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

@ -904,10 +904,6 @@
window.setSettingValue('link-preview-setting', false); 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 // Generates useful random ID for various purposes
window.generateID = () => window.generateID = () =>
Math.random() Math.random()

@ -11,7 +11,6 @@ import { LeftPaneSectionHeader } from './session/LeftPaneSectionHeader';
import { ConversationType } from '../state/ducks/conversations'; import { ConversationType } from '../state/ducks/conversations';
import { LeftPaneContactSection } from './session/LeftPaneContactSection'; import { LeftPaneContactSection } from './session/LeftPaneContactSection';
import { LeftPaneSettingSection } from './session/LeftPaneSettingSection'; import { LeftPaneSettingSection } from './session/LeftPaneSettingSection';
import { LeftPaneChannelSection } from './session/LeftPaneChannelSection';
import { SessionIconType } from './session/icon'; import { SessionIconType } from './session/icon';
// from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5 // 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(); return this.renderMessageSection();
case SectionType.Contact: case SectionType.Contact:
return this.renderContactSection(); return this.renderContactSection();
case SectionType.Channel:
return this.renderChannelSection();
case SectionType.Settings: case SectionType.Settings:
return this.renderSettingSection(); return this.renderSettingSection();
case SectionType.Moon: case SectionType.Moon:
@ -179,30 +176,4 @@ export class LeftPane extends React.Component<Props, State> {
return <LeftPaneSettingSection isSecondaryDevice={isSecondaryDevice} />; 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'; } from './session/settings/SessionSettings';
export const MainViewController = { export const MainViewController = {
renderMessageView: () => { joinChannelStateManager,
if (document.getElementById('main-view')) { onCreateClosedGroup,
ReactDOM.render(<MessageView />, document.getElementById('main-view')); renderMessageView,
} renderSettingsView,
},
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')
);
}
},
}; };
import { ContactType } from './session/SessionMemberListItem';
export class MessageView extends React.Component { export class MessageView extends React.Component {
public render() { public render() {
return ( 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 { AutoSizer, List } from 'react-virtualized';
import { validateNumber } from '../../types/PhoneNumber'; import { validateNumber } from '../../types/PhoneNumber';
import { ConversationType } from '../../state/ducks/conversations'; import { ConversationType } from '../../state/ducks/conversations';
import { SessionClosableOverlay } from './SessionClosableOverlay'; import { SessionClosableOverlay, SessionClosableOverlayType } from './SessionClosableOverlay';
import { MainViewController } from '../MainViewController'; import { MainViewController } from '../MainViewController';
export enum SessionClosableOverlayTypeContact { Contact = 'contact'}
export interface Props { export interface Props {
searchTerm: string; searchTerm: string;
isSecondaryDevice: boolean; isSecondaryDevice: boolean;
@ -204,7 +206,7 @@ export class LeftPaneContactSection extends React.Component<Props, State> {
private renderClosableOverlay() { private renderClosableOverlay() {
return ( return (
<SessionClosableOverlay <SessionClosableOverlay
overlayMode="contact" overlayMode={SessionClosableOverlayType.Contact}
onChangeSessionID={this.handleRecipientSessionIDChanged} onChangeSessionID={this.handleRecipientSessionIDChanged}
onCloseClick={this.handleToggleOverlay} onCloseClick={this.handleToggleOverlay}
onButtonClick={this.handleOnAddContact} onButtonClick={this.handleOnAddContact}

@ -18,21 +18,19 @@ import { SearchOptions } from '../../types/Search';
import { validateNumber } from '../../types/PhoneNumber'; import { validateNumber } from '../../types/PhoneNumber';
import { LeftPane, RowRendererParamsType } from '../LeftPane'; import { LeftPane, RowRendererParamsType } from '../LeftPane';
import { SessionClosableOverlay } from './SessionClosableOverlay'; import { SessionClosableOverlay } from './SessionClosableOverlay';
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon'; import { SessionIconType } from './icon';
import { ContactType } from './SessionMemberListItem';
import { import {
SessionButton, SessionButton,
SessionButtonColor, SessionButtonColor,
SessionButtonType, SessionButtonType,
} from './SessionButton'; } from './SessionButton';
import { SessionSpinner } from './SessionSpinner';
import { joinChannelStateManager } from './LeftPaneChannelSection';
export interface Props { export interface Props {
searchTerm: string; searchTerm: string;
isSecondaryDevice: boolean; isSecondaryDevice: boolean;
conversations?: Array<ConversationListItemPropsType>; conversations?: Array<ConversationListItemPropsType>;
searchResults?: SearchResultsProps; searchResults?: SearchResultsProps;
updateSearchTerm: (searchTerm: string) => void; updateSearchTerm: (searchTerm: string) => void;
@ -41,17 +39,40 @@ export interface Props {
clearSearch: () => void; 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 updateSearchBound: (searchedString: string) => void;
private readonly debouncedSearch: (searchTerm: string) => void; private readonly debouncedSearch: (searchTerm: string) => void;
public constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
this.state = {
loading: false,
overlay: false,
valuePasted: '',
connectSuccess: false,
};
const conversations = this.getCurrentConversations(); const conversations = this.getCurrentConversations();
const renderOnboardingSetting = window.getSettingValue(
'render-message-onboarding'
);
const realConversations: Array<ConversationListItemPropsType> = []; const realConversations: Array<ConversationListItemPropsType> = [];
if (conversations) { 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.updateSearchBound = this.updateSearch.bind(this);
this.handleOnPaste = this.handleOnPaste.bind(this);
this.handleToggleOverlay = this.handleToggleOverlay.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.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); this.debouncedSearch = debounce(this.search.bind(this), 20);
} }
@ -199,16 +217,18 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
null, null,
undefined, undefined,
SessionIconType.Plus, SessionIconType.Plus,
this.handleToggleOverlay this.handleNewSessionButtonClick
); );
} }
public render(): JSX.Element { public render(): JSX.Element {
const { overlay } = this.state;
return ( return (
<div className="session-left-pane-section-content"> <div className="session-left-pane-section-content">
{this.renderHeader()} {this.renderHeader()}
{this.state.showComposeView {overlay
? this.renderClosableOverlay() ? this.renderClosableOverlay(overlay)
: this.renderConversations()} : this.renderConversations()}
</div> </div>
); );
@ -217,90 +237,17 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
public renderConversations() { public renderConversations() {
return ( return (
<div className="module-conversations-list-content"> <div className="module-conversations-list-content">
{this.state.shouldRenderMessageOnboarding ? (
<>{this.renderMessageOnboarding()}</>
) : (
<>
<SessionSearchInput <SessionSearchInput
searchString={this.props.searchTerm} searchString={this.props.searchTerm}
onChange={this.updateSearchBound} onChange={this.updateSearchBound}
placeholder={window.i18n('searchForAKeyPhrase')} placeholder={window.i18n('searchForAKeyPhrase')}
/> />
{this.renderList()} {this.renderList()}
</> {this.renderBottomButtons()}
)}
</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>
</div> </div>
); );
} }
public handleCloseOnboarding() {
window.setSettingValue('render-message-onboarding', false);
this.setState({
shouldRenderMessageOnboarding: false,
});
}
public updateSearch(searchTerm: string) { public updateSearch(searchTerm: string) {
const { updateSearchTerm, clearSearch } = this.props; const { updateSearchTerm, clearSearch } = this.props;
@ -309,8 +256,10 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
return; return;
} }
// reset our pubKeyPasted, we can either have a pasted sessionID or a sessionID got from a search // 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) { if (updateSearchTerm) {
updateSearchTerm(searchTerm); 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 { searchTerm, searchResults } = this.props;
const { loading } = this.state;
return ( const openGroupElement = (
<SessionClosableOverlay <SessionClosableOverlay
overlayMode="message" overlayMode={SessionComposeToType.OpenGroup}
onChangeSessionID={this.handleOnPasteSessionID} onChangeSessionID={this.handleOnPaste}
onCloseClick={this.handleToggleOverlay} 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={SessionComposeToType.Message}
onChangeSessionID={this.handleOnPaste}
onCloseClick={() => {
this.handleToggleOverlay(undefined);
}}
onButtonClick={this.handleMessageButtonClick} onButtonClick={this.handleMessageButtonClick}
searchTerm={searchTerm} searchTerm={searchTerm}
searchResults={searchResults} searchResults={searchResults}
updateSearch={this.updateSearchBound} updateSearch={this.updateSearchBound}
/> />
); );
let overlayElement;
switch (overlay) {
case SessionComposeToType.OpenGroup:
overlayElement = openGroupElement;
break;
case SessionComposeToType.ClosedGroup:
overlayElement = closedGroupElement;
break;
default:
overlayElement = messageElement;
} }
private handleToggleOverlay() { return overlayElement;
this.setState((state: any) => {
return { showComposeView: !state.showComposeView };
});
// empty our generalized searchedString (one for the whole app)
this.updateSearch('');
} }
private handleOnPasteSessionID(value: string) { private renderBottomButtons(): JSX.Element {
// reset our search, we can either have a pasted sessionID or a sessionID got from a search 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 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.updateSearch('');
}
this.setState({ pubKeyPasted: value }); private handleOnPaste(value: string) {
this.setState({ valuePasted: value });
} }
private handleMessageButtonClick() { private handleMessageButtonClick() {
const { openConversationInternal } = this.props; const { openConversationInternal } = this.props;
if (!this.state.pubKeyPasted && !this.props.searchTerm) { if (!this.state.valuePasted && !this.props.searchTerm) {
window.pushToast({ window.pushToast({
title: window.i18n('invalidNumberError'), title: window.i18n('invalidNumberError'),
type: 'error', type: 'error',
@ -390,7 +425,7 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
return; return;
} }
let pubkey: string; let pubkey: string;
pubkey = this.state.pubKeyPasted || this.props.searchTerm; pubkey = this.state.valuePasted || this.props.searchTerm;
pubkey = pubkey.trim(); pubkey = pubkey.trim();
const error = validateNumber(pubkey); 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; private handleJoinChannelButtonClick(groupUrl: string) {
joinChannelStateManager(this, serverURL, this.handleCloseOnboarding); 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 buttonLabel
); );
;
const button = ( const button = (
<SessionButton onClick={buttonClicked} key="compose" disabled={false}> <SessionButton onClick={buttonClicked} key="compose" disabled={false}>
{buttonContent} {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>; return <div className="module-left-pane__header">{children}</div>;
} }

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

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

Loading…
Cancel
Save