diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index 261ab006b..63c29f78d 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -999,6 +999,8 @@ input { } .contact-selection-list { + display: flex; + flex-direction: column; width: 20vw; } diff --git a/ts/components/MemberListItem.tsx b/ts/components/MemberListItem.tsx index 8470224b1..333a23370 100644 --- a/ts/components/MemberListItem.tsx +++ b/ts/components/MemberListItem.tsx @@ -25,6 +25,7 @@ const StyledSessionMemberItem = styled.button<{ }>` cursor: pointer; flex-shrink: 0; + flex-grow: 1; font-family: var(--font-default); padding: 0px var(--margins-sm); height: ${props => (props.inMentions ? '40px' : '50px')}; diff --git a/ts/components/basic/PillContainer.tsx b/ts/components/basic/PillContainer.tsx index acb2e908a..aa32465d0 100644 --- a/ts/components/basic/PillContainer.tsx +++ b/ts/components/basic/PillContainer.tsx @@ -6,11 +6,10 @@ type PillContainerProps = { margin?: string; padding?: string; onClick?: () => void; - onMouseEnter?: () => void; - onMouseLeave?: () => void; + disableHover?: boolean; }; -const StyledPillContainerHoverable = styled.div` +export const StyledPillContainerHoverable = styled.div` background: none; position: relative; @@ -26,7 +25,6 @@ const StyledPillContainerHoverable = styled.div` `; const StyledPillInner = styled.div` - background: green; background: none; display: flex; @@ -42,18 +40,18 @@ const StyledPillInner = styled.div` padding: ${props => props.padding || ''}; margin: ${props => props.margin || ''}; border-radius: 300px; - cursor: pointer; + cursor: ${props => (props.disableHover ? 'unset' : 'pointer')}; border: 1px solid var(--color-pill-divider); transition: var(--default-duration); &:hover { - background: var(--color-clickable-hovered); + background: ${props => (props.disableHover ? 'none' : 'var(--color-clickable-hovered)')}; } `; -export const PillTooltipWrapper = (props: PillContainerProps) => { - return {props.children}; -}; - -export const PillContainerHoverable = (props: PillContainerProps) => { - return {props.children}; +export const PillContainerHoverable = (props: Omit) => { + return ( + + {props.children} + + ); }; diff --git a/ts/components/conversation/message/reactions/Reaction.tsx b/ts/components/conversation/message/reactions/Reaction.tsx index d0d6af1a7..9f2e1c38c 100644 --- a/ts/components/conversation/message/reactions/Reaction.tsx +++ b/ts/components/conversation/message/reactions/Reaction.tsx @@ -52,6 +52,7 @@ export type ReactionProps = { handlePopupReaction?: (emoji: string) => void; handlePopupClick?: () => void; }; +// tslint:disable-next-line: use-simple-attributes export const Reaction = (props: ReactionProps): ReactElement => { const { diff --git a/ts/components/leftpane/overlay/OverlayCommunity.tsx b/ts/components/leftpane/overlay/OverlayCommunity.tsx index 0a0f68cc8..527618515 100644 --- a/ts/components/leftpane/overlay/OverlayCommunity.tsx +++ b/ts/components/leftpane/overlay/OverlayCommunity.tsx @@ -7,12 +7,17 @@ import { SessionButton, SessionButtonColor, SessionButtonType } from '../../basi import { SessionIdEditable } from '../../basic/SessionIdEditable'; import { SessionSpinner } from '../../basic/SessionSpinner'; import { OverlayHeader } from './OverlayHeader'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { resetOverlayMode } from '../../../state/ducks/section'; -import { joinOpenGroupV2WithUIEvents } from '../../../session/apis/open_group_api/opengroupV2/JoinOpenGroupV2'; +import { + joinOpenGroupV2WithUIEvents, + JoinSogsRoomUICallbackArgs, +} from '../../../session/apis/open_group_api/opengroupV2/JoinOpenGroupV2'; import { openGroupV2CompleteURLRegex } from '../../../session/apis/open_group_api/utils/OpenGroupUtils'; import { ToastUtils } from '../../../session/utils'; import useKey from 'react-use/lib/useKey'; +import { getOverlayMode } from '../../../state/selectors/section'; +import { openConversationWithMessages } from '../../../state/ducks/conversations'; async function joinOpenGroup(serverUrl: string) { // guess if this is an open @@ -31,6 +36,8 @@ export const OverlayCommunity = () => { const [loading, setLoading] = useState(false); const [groupUrl, setGroupUrl] = useState(''); + const overlayModeIsCommunity = useSelector(getOverlayMode) === 'open-group'; + function closeOverlay() { dispatch(resetOverlayMode()); } @@ -52,6 +59,15 @@ export const OverlayCommunity = () => { } } + function onJoinSessionSogsRoom(args: JoinSogsRoomUICallbackArgs) { + setLoading(args.loadingState === 'started'); + + if (args.loadingState === 'finished' && overlayModeIsCommunity && args.conversationKey) { + closeOverlay(); + void openConversationWithMessages({ conversationKey: args.conversationKey, messageId: null }); // open to last unread for a session run sogs + } + } + useKey('Escape', closeOverlay); const title = window.i18n('joinOpenGroup'); @@ -84,7 +100,10 @@ export const OverlayCommunity = () => { /> - + ); }; diff --git a/ts/components/leftpane/overlay/SessionJoinableDefaultRooms.tsx b/ts/components/leftpane/overlay/SessionJoinableDefaultRooms.tsx index 9b757d141..e0ad0a916 100644 --- a/ts/components/leftpane/overlay/SessionJoinableDefaultRooms.tsx +++ b/ts/components/leftpane/overlay/SessionJoinableDefaultRooms.tsx @@ -1,9 +1,10 @@ -import React, { useCallback, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import styled from 'styled-components'; import { joinOpenGroupV2WithUIEvents, + JoinSogsRoomUICallbackArgs, parseOpenGroupV2, } from '../../../session/apis/open_group_api/opengroupV2/JoinOpenGroupV2'; import { sogsV3FetchPreviewBase64 } from '../../../session/apis/open_group_api/sogsv3/sogsV3FetchFile'; @@ -11,7 +12,7 @@ import { updateDefaultBase64RoomData } from '../../../state/ducks/defaultRooms'; import { StateType } from '../../../state/reducer'; import { Avatar, AvatarSize } from '../../avatar/Avatar'; import { Flex } from '../../basic/Flex'; -import { PillContainerHoverable, PillTooltipWrapper } from '../../basic/PillContainer'; +import { PillContainerHoverable, StyledPillContainerHoverable } from '../../basic/PillContainer'; import { SessionSpinner } from '../../basic/SessionSpinner'; import { H3 } from '../../basic/Text'; // tslint:disable: no-void-expression @@ -21,7 +22,7 @@ export type JoinableRoomProps = { name: string; roomId: string; imageId?: string; - onClick: (completeUrl: string) => void; + onClick?: (completeUrl: string) => void; base64Data?: string; }; @@ -77,7 +78,7 @@ const SessionJoinableRoomAvatar = (props: JoinableRoomProps) => { base64Data={props.base64Data} {...props} pubkey="" - onAvatarClick={() => props.onClick(props.completeUrl)} + onAvatarClick={() => props.onClick?.(props.completeUrl)} /> ); }; @@ -94,64 +95,74 @@ const SessionJoinableRoomName = (props: JoinableRoomProps) => { }; const SessionJoinableRoomRow = (props: JoinableRoomProps) => { + const { onClick, completeUrl } = props; + const onClickWithUrl = onClick + ? () => { + onClick?.(completeUrl); + } + : undefined; + return ( - - { - props.onClick(props.completeUrl); - }} - margin="5px" - padding="5px" - > + + - + ); }; -export const SessionJoinableRooms = (props: { onRoomClicked: () => void }) => { +const JoinableRooms = (props: { + onJoinSessionSogsRoom: (args: JoinSogsRoomUICallbackArgs) => void; + alreadyJoining: boolean; +}) => { const joinableRooms = useSelector((state: StateType) => state.defaultRooms); - const onRoomClicked = useCallback( - (loading: boolean) => { - if (loading) { - props.onRoomClicked(); - } - }, - [props.onRoomClicked] + const onClick = props.alreadyJoining + ? undefined + : (completeUrl: string) => { + void joinOpenGroupV2WithUIEvents(completeUrl, true, false, props.onJoinSessionSogsRoom); + }; + + return ( + <> + {joinableRooms.rooms.map(r => { + return ( + + ); + })} + ); +}; + +export const SessionJoinableRooms = (props: { + onJoinSessionSogsRoom: (args: JoinSogsRoomUICallbackArgs) => void; + alreadyJoining: boolean; +}) => { + const joinableRooms = useSelector((state: StateType) => state.defaultRooms); if (!joinableRooms.inProgress && !joinableRooms.rooms?.length) { window?.log?.info('no default joinable rooms yet and not in progress'); return null; } - const componentToRender = joinableRooms.inProgress ? ( - - ) : ( - joinableRooms.rooms.map(r => { - return ( - { - void joinOpenGroupV2WithUIEvents(completeUrl, true, false, onRoomClicked); - }} - /> - ); - }) - ); - return (

- {componentToRender} + {joinableRooms.inProgress ? ( + + ) : ( + + )} ); diff --git a/ts/components/leftpane/overlay/choose-action/ContactsListWithBreaks.tsx b/ts/components/leftpane/overlay/choose-action/ContactsListWithBreaks.tsx index 1f8969cd0..40d011d81 100644 --- a/ts/components/leftpane/overlay/choose-action/ContactsListWithBreaks.tsx +++ b/ts/components/leftpane/overlay/choose-action/ContactsListWithBreaks.tsx @@ -113,7 +113,9 @@ const ContactsTitle = () => { return null; } - return {window.i18n('contactsHeader')}; + return ( + {window.i18n('contactsHeader')} + ); }; export const ContactsListWithBreaks = () => { diff --git a/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts b/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts index e47e2151f..145339079 100644 --- a/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts +++ b/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts @@ -39,7 +39,7 @@ export type OpenGroupV2InfoJoinable = OpenGroupV2Info & { // tslint:disable: no-http-string const legacyDefaultServerIP = '116.203.70.33'; -const defaultServer = 'https://open.getsession.org'; +export const defaultServer = 'https://open.getsession.org'; const defaultServerHost = new window.URL(defaultServer).host; /** diff --git a/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts b/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts index 7b0365f35..706df0bd1 100644 --- a/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts +++ b/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts @@ -2,6 +2,8 @@ import _ from 'lodash'; import { OpenGroupV2Room } from '../../../../data/opengroups'; import { getConversationController } from '../../../conversations'; import { PromiseUtils, ToastUtils } from '../../../utils'; +import { getEventSessionSogsFirstPoll } from '../../../utils/GlobalEvents'; +import { sleepFor, waitForTask } from '../../../utils/Promise'; import { forceSyncConfigurationNowIfNeeded } from '../../../utils/syncUtils'; import { @@ -103,6 +105,11 @@ async function joinOpenGroupV2(room: OpenGroupV2Room, fromConfigMessage: boolean } } +export type JoinSogsRoomUICallbackArgs = { + loadingState: 'started' | 'finished' | 'failed'; + conversationKey: string | null; +}; + /** * This function does not throw * This function can be used to join an opengroupv2 server, from a user initiated click or from a syncMessage. @@ -121,7 +128,7 @@ export async function joinOpenGroupV2WithUIEvents( completeUrl: string, showToasts: boolean, fromConfigMessage: boolean, - uiCallback?: (loading: boolean) => void + uiCallback?: (args: JoinSogsRoomUICallbackArgs) => void ): Promise { try { const parsedRoom = parseOpenGroupV2(completeUrl); @@ -142,11 +149,23 @@ export async function joinOpenGroupV2WithUIEvents( if (showToasts) { ToastUtils.pushToastInfo('connectingToServer', window.i18n('connectingToServer')); } - if (uiCallback) { - uiCallback(true); - } + + uiCallback?.({ loadingState: 'started', conversationKey: conversationID }); + await joinOpenGroupV2(parsedRoom, fromConfigMessage); + // this is very hacky but is made so we wait for the poller to receive the first messages related to that room. + // once the poller added all the messages to the queue of jobs to be run, we still wait a bit for them to be processed. + // This won't age well, but I am not too sure how we can design better. + await waitForTask(done => { + const eventToWait = getEventSessionSogsFirstPoll(parsedRoom.roomId); + window.Whisper.events.on(eventToWait, async () => { + window.Whisper.events.off(eventToWait); + await sleepFor(5000); + done(0); + }); + }, 25000); + const isConvoCreated = getConversationController().get(conversationID); if (isConvoCreated) { if (showToasts) { @@ -155,21 +174,21 @@ export async function joinOpenGroupV2WithUIEvents( window.i18n('connectToServerSuccess') ); } + uiCallback?.({ loadingState: 'finished', conversationKey: conversationID }); + return true; } else { if (showToasts) { ToastUtils.pushToastError('connectToServerFail', window.i18n('connectToServerFail')); } } + uiCallback?.({ loadingState: 'failed', conversationKey: conversationID }); } catch (error) { window?.log?.warn('got error while joining open group:', error.message); if (showToasts) { ToastUtils.pushToastError('connectToServerFail', window.i18n('connectToServerFail')); } - } finally { - if (uiCallback) { - uiCallback(false); - } + uiCallback?.({ loadingState: 'failed', conversationKey: null }); } return false; } diff --git a/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts b/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts index 1f34bb390..439ebec60 100644 --- a/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts +++ b/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts @@ -1,6 +1,6 @@ import { AbortController } from 'abort-controller'; import { getOpenGroupV2ConversationId } from '../utils/OpenGroupUtils'; -import { OpenGroupRequestCommonType } from './ApiUtil'; +import { defaultServer, OpenGroupRequestCommonType } from './ApiUtil'; import _, { isNumber, isObject } from 'lodash'; import { OpenGroupData } from '../../../../data/opengroups'; @@ -20,6 +20,7 @@ import { roomHasBlindEnabled, } from '../sogsv3/sogsV3Capabilities'; import { OpenGroupReaction } from '../../../../types/Reaction'; +import { getEventSessionSogsFirstPoll } from '../../../utils/GlobalEvents'; export type OpenGroupMessageV4 = { /** AFAIK: indicates the number of the message in the group. e.g. 2nd message will be 1 or 2 */ @@ -317,6 +318,14 @@ export class OpenGroupServerPoller { subrequestOptions, this.roomIdsToPoll ); + + if (this.serverUrl === defaultServer) { + for (const room of subrequestOptions) { + if (room.type === 'messages' && !room.messages?.sinceSeqNo && room.messages?.roomId) { + window.Whisper.events.trigger(getEventSessionSogsFirstPoll(room.messages.roomId)); + } + } + } } catch (e) { window?.log?.warn('Got error while compact fetch:', e.message); } finally { diff --git a/ts/session/utils/GlobalEvents.ts b/ts/session/utils/GlobalEvents.ts new file mode 100644 index 000000000..e73c62bbf --- /dev/null +++ b/ts/session/utils/GlobalEvents.ts @@ -0,0 +1,3 @@ +export function getEventSessionSogsFirstPoll(roomId: string) { + return `first-poll-session-sogs:${roomId}`; +} diff --git a/ts/window.d.ts b/ts/window.d.ts index c07742648..ad4e8ad81 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -21,11 +21,7 @@ declare global { CONSTANTS: any; Events: any; Lodash: any; - SessionSnodeAPI: any; Session: any; - StubAppDotNetApi: any; - StringView: any; - StubMessageAPI: any; Whisper: any; clearLocalData: any; clipboard: any;