diff --git a/js/background.js b/js/background.js index e9f43c901..30d4d268c 100644 --- a/js/background.js +++ b/js/background.js @@ -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() diff --git a/ts/components/LeftPane.tsx b/ts/components/LeftPane.tsx index 67c300b00..88fe9b5da 100644 --- a/ts/components/LeftPane.tsx +++ b/ts/components/LeftPane.tsx @@ -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 { 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 { return ; } - - private renderChannelSection() { - const { - openConversationInternal, - conversations, - searchResults, - searchTerm, - isSecondaryDevice, - updateSearchTerm, - search, - clearSearch, - } = this.props; - - return ( - - ); - } } diff --git a/ts/components/MainViewController.tsx b/ts/components/MainViewController.tsx index e344920c2..c43950ebf 100644 --- a/ts/components/MainViewController.tsx +++ b/ts/components/MainViewController.tsx @@ -7,29 +7,14 @@ import { } from './session/settings/SessionSettings'; export const MainViewController = { - renderMessageView: () => { - if (document.getElementById('main-view')) { - ReactDOM.render(, 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( - , - 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, + 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(, 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( + ( + + ), + document.getElementById('main-view') + ); + } +} diff --git a/ts/components/session/LeftPaneChannelSection.tsx b/ts/components/session/LeftPaneChannelSection.tsx deleted file mode 100644 index 0ae41254b..000000000 --- a/ts/components/session/LeftPaneChannelSection.tsx +++ /dev/null @@ -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; - - 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 { - 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 - | 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 ( - - ); - }; - - public renderList(): JSX.Element | Array { - const { openConversationInternal, searchResults } = this.props; - - if (searchResults) { - return ( - - ); - } - - 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 = ( -
- - {({ height, width }) => ( - - )} - -
- ); - - 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 ( -
- {this.renderHeader()} - {this.state.groupAddType - ? this.renderClosableOverlay(this.state.groupAddType) - : this.renderGroups()} -
- ); - } - - public renderGroups() { - return ( -
- - {this.renderList()} - {this.renderBottomButtons()} -
- ); - } - - 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 = ( - { - this.handleToggleOverlay(undefined); - }} - onButtonClick={this.handleJoinChannelButtonClick} - searchTerm={searchTerm} - updateSearch={this.updateSearchBound} - showSpinner={loading} - /> - ); - - const closedGroupElement = ( - { - this.handleToggleOverlay(undefined); - }} - onButtonClick={async ( - groupName: string, - groupMembers: Array - ) => 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 ( -
- {showEditButton && ( - - )} - - { - this.handleToggleOverlay(SessionGroupType.Open); - }} - /> - { - this.handleToggleOverlay(SessionGroupType.Closed); - }} - /> -
- ); - } - - 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 - ) { - // 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; -} diff --git a/ts/components/session/LeftPaneContactSection.tsx b/ts/components/session/LeftPaneContactSection.tsx index 4a9e0704a..84705236d 100644 --- a/ts/components/session/LeftPaneContactSection.tsx +++ b/ts/components/session/LeftPaneContactSection.tsx @@ -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 { private renderClosableOverlay() { return ( ; - searchResults?: SearchResultsProps; updateSearchTerm: (searchTerm: string) => void; @@ -41,17 +39,40 @@ export interface Props { clearSearch: () => void; } -export class LeftPaneMessageSection extends React.Component { +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 { 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 = []; if (conversations) { @@ -64,23 +85,20 @@ export class LeftPaneMessageSection extends React.Component { }); } - 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 { null, undefined, SessionIconType.Plus, - this.handleToggleOverlay + this.handleNewSessionButtonClick ); } public render(): JSX.Element { + const { overlay } = this.state; + return (
{this.renderHeader()} - {this.state.showComposeView - ? this.renderClosableOverlay() + {overlay + ? this.renderClosableOverlay(overlay) : this.renderConversations()}
); @@ -217,90 +237,17 @@ export class LeftPaneMessageSection extends React.Component { public renderConversations() { return (
- {this.state.shouldRenderMessageOnboarding ? ( - <>{this.renderMessageOnboarding()} - ) : ( - <> - - {this.renderList()} - - )} -
- ); - } - - public renderMessageOnboarding() { - return ( -
-
- -
- -
-
-

{window.i18n('welcomeToSession')}

-
- -
- -
- -
-
- {window.i18n('noMessagesTitle')} -
-
- {window.i18n('noMessagesSubtitle')} -
-
- - <> - {this.state.loading ? ( -
- -
- ) : ( -
- - -
- )} - -
+ + {this.renderList()} + {this.renderBottomButtons()}
); } - 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 { 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 { } } - private renderClosableOverlay() { + private renderClosableOverlay(overlay: SessionComposeToType) { const { searchTerm, searchResults } = this.props; + const { loading } = this.state; - return ( + const openGroupElement = ( + { + this.handleToggleOverlay(undefined); + }} + onButtonClick={this.handleJoinChannelButtonClick} + searchTerm={searchTerm} + updateSearch={this.updateSearchBound} + showSpinner={loading} + /> + ); + + const closedGroupElement = ( + { + this.handleToggleOverlay(undefined); + }} + onButtonClick={async ( + groupName: string, + groupMembers: Array + ) => this.handleCreateClosedGroupButtonClick(groupName, groupMembers)} + searchTerm={searchTerm} + updateSearch={this.updateSearchBound} + showSpinner={loading} + /> + ); + + const messageElement = ( { + 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 ( +
+ {showEditButton && ( + + )} + + { + this.handleToggleOverlay(SessionComposeToType.OpenGroup); + }} + /> + { + this.handleToggleOverlay(SessionComposeToType.ClosedGroup); + }} + /> +
+ ); } - 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 { 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 { } } - 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 + ) { + // 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); } + } + diff --git a/ts/components/session/LeftPaneSectionHeader.tsx b/ts/components/session/LeftPaneSectionHeader.tsx index 4c9f1d737..4a01cceb6 100644 --- a/ts/components/session/LeftPaneSectionHeader.tsx +++ b/ts/components/session/LeftPaneSectionHeader.tsx @@ -94,7 +94,7 @@ export class LeftPaneSectionHeader extends React.Component { ) : ( buttonLabel ); - +; const button = ( {buttonContent} @@ -128,7 +128,7 @@ export class LeftPaneSectionHeader extends React.Component { ); } - //Create the parent and add the children + // Create the parent and add the children return
{children}
; } diff --git a/ts/components/session/SessionClosableOverlay.tsx b/ts/components/session/SessionClosableOverlay.tsx index 6a8b34a77..b585bcb04 100644 --- a/ts/components/session/SessionClosableOverlay.tsx +++ b/ts/components/session/SessionClosableOverlay.tsx @@ -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 { 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 { 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 (
@@ -201,7 +210,7 @@ export class SessionClosableOverlay extends React.Component {
) : (
- {this.renderMemberList()} + {this.renderMemberList(contacts)}
)} @@ -245,8 +254,10 @@ export class SessionClosableOverlay extends React.Component { ); } - private renderMemberList() { - const members = this.getContacts(); + private renderMemberList(members: any) { + + console.log('[vince] sdsdf:'); + console.log('[vince] members:', members); return members.map((member: ContactType) => ( { } private onGroupNameChanged(event: any) { + + console.log('[vince] event:', event); + this.setState({ groupName: event, }); + } } diff --git a/ts/components/session/SessionIdEditable.tsx b/ts/components/session/SessionIdEditable.tsx index 6fca9f63e..e2d43024c 100644 --- a/ts/components/session/SessionIdEditable.tsx +++ b/ts/components/session/SessionIdEditable.tsx @@ -55,6 +55,7 @@ export class SessionIdEditable extends React.PureComponent { private handleChange(e: any) { const { editable, onChange } = this.props; + if (editable) { onChange(e.target.value); }