From 38c390355de7ad5b3f02fd0f1927b00ded92fe79 Mon Sep 17 00:00:00 2001 From: William Grant Date: Mon, 22 Apr 2024 11:58:02 +1000 Subject: [PATCH 001/223] refactor: leftpanemessagesection is now a functional component --- .../leftpane/LeftPaneMessageSection.tsx | 48 +++++++------------ 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/ts/components/leftpane/LeftPaneMessageSection.tsx b/ts/components/leftpane/LeftPaneMessageSection.tsx index f3d04411e8..5b23d8abbb 100644 --- a/ts/components/leftpane/LeftPaneMessageSection.tsx +++ b/ts/components/leftpane/LeftPaneMessageSection.tsx @@ -1,6 +1,3 @@ -import autoBind from 'auto-bind'; - -import { Component } from 'react'; import { useSelector } from 'react-redux'; import { AutoSizer, List, ListRowProps } from 'react-virtualized'; import styled from 'styled-components'; @@ -66,15 +63,10 @@ const ClosableOverlay = () => { } }; -export class LeftPaneMessageSection extends Component { - public constructor(props: Props) { - super(props); - autoBind(this); - } - - public renderRow = ({ index, key, style }: ListRowProps): JSX.Element | null => { - const { conversationIds } = this.props; +export const LeftPaneMessageSection = (props: Props) => { + const { conversationIds, hasSearchResults, leftOverlayMode } = props; + const renderRow = ({ index, key, style }: ListRowProps): JSX.Element | null => { // assume conversations that have been marked unapproved should be filtered out by selector. if (!conversationIds) { throw new Error('renderRow: Tried to render without conversations'); @@ -88,9 +80,7 @@ export class LeftPaneMessageSection extends Component { return ; }; - public renderList(): JSX.Element { - const { conversationIds, hasSearchResults } = this.props; - + const renderList = () => { if (hasSearchResults) { return ; } @@ -110,7 +100,7 @@ export class LeftPaneMessageSection extends Component { height={height} rowCount={length} rowHeight={64} - rowRenderer={this.renderRow} + rowRenderer={renderRow} width={width} autoHeight={false} conversationIds={conversationIds} @@ -119,20 +109,9 @@ export class LeftPaneMessageSection extends Component { ); - } - - public render(): JSX.Element { - const { leftOverlayMode } = this.props; - - return ( - - - {leftOverlayMode ? : this.renderConversations()} - - ); - } + }; - public renderConversations() { + const renderConversations = () => { return ( @@ -141,8 +120,15 @@ export class LeftPaneMessageSection extends Component { window.inboxStore?.dispatch(setLeftOverlayMode('message-requests')); }} /> - {this.renderList()} + {renderList()} ); - } -} + }; + + return ( + + + {leftOverlayMode ? : renderConversations()} + + ); +}; From e96519445d761b8bcbc40487fbd765a819f39f7f Mon Sep 17 00:00:00 2001 From: William Grant Date: Mon, 22 Apr 2024 15:04:01 +1000 Subject: [PATCH 002/223] feat: removed overlayheader moved some parts into the leftpanesectionheader --- _locales/en/messages.json | 3 - stylesheets/_session_left_pane.scss | 10 --- .../leftpane/LeftPaneSectionHeader.tsx | 62 +++++++++++++--- .../leftpane/overlay/OverlayClosedGroup.tsx | 4 -- .../leftpane/overlay/OverlayCommunity.tsx | 4 -- .../leftpane/overlay/OverlayHeader.tsx | 72 ------------------- .../leftpane/overlay/OverlayMessage.tsx | 5 -- ts/types/LocalizerKeys.ts | 3 - 8 files changed, 53 insertions(+), 110 deletions(-) delete mode 100644 ts/components/leftpane/overlay/OverlayHeader.tsx diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 743246b3e0..733a0ace59 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -109,10 +109,8 @@ "copiedToClipboard": "Copied", "copyErrorAndQuit": "Copy error and quit", "copyMessage": "Copy message text", - "copyOpenGroupURL": "Copy Group URL", "couldntFindServerMatching": "Couldn't find the corresponding Community server", "create": "Create", - "createClosedGroupNamePrompt": "Group Name", "createClosedGroupPlaceholder": "Enter a group name", "createConversationNewContact": "Create a conversation with a new contact", "createConversationNewGroup": "Create a group with existing contacts", @@ -371,7 +369,6 @@ "onsErrorNotRecognised": "We couldn't recognise this ONS. Please check it and try again.", "open": "Open", "openGroupInvitation": "Community invitation", - "openGroupURL": "Community URL", "openMessageRequestInbox": "Message Requests", "openMessageRequestInboxDescription": "View your Message Request inbox", "or": "or", diff --git a/stylesheets/_session_left_pane.scss b/stylesheets/_session_left_pane.scss index 35997b4e22..64425168f2 100644 --- a/stylesheets/_session_left_pane.scss +++ b/stylesheets/_session_left_pane.scss @@ -80,16 +80,6 @@ $session-compose-margin: 20px; height: 100%; } - &__header { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - height: var(--main-view-header-height); - padding-inline-end: 7px; - transition: var(--default-duration); - } - &__title { padding-inline-end: var(--margins-sm); padding-inline-start: var(--margins-sm); diff --git a/ts/components/leftpane/LeftPaneSectionHeader.tsx b/ts/components/leftpane/LeftPaneSectionHeader.tsx index f5b3f008e3..527fd72387 100644 --- a/ts/components/leftpane/LeftPaneSectionHeader.tsx +++ b/ts/components/leftpane/LeftPaneSectionHeader.tsx @@ -1,16 +1,22 @@ import { useDispatch, useSelector } from 'react-redux'; import styled from 'styled-components'; import { recoveryPhraseModal } from '../../state/ducks/modalDialog'; -import { SectionType } from '../../state/ducks/section'; +import { SectionType, setLeftOverlayMode } from '../../state/ducks/section'; import { disableRecoveryPhrasePrompt } from '../../state/ducks/userConfig'; -import { getFocusedSection, getIsMessageRequestOverlayShown } from '../../state/selectors/section'; +import { getFocusedSection, getLeftOverlayMode } from '../../state/selectors/section'; import { getShowRecoveryPhrasePrompt } from '../../state/selectors/userConfig'; import { isSignWithRecoveryPhrase } from '../../util/storage'; import { Flex } from '../basic/Flex'; import { SessionButton } from '../basic/SessionButton'; import { SpacerMD } from '../basic/Text'; import { MenuButton } from '../buttons'; -import { SessionIcon } from '../icon'; +import { SessionIcon, SessionIconButton } from '../icon'; + +const StyledLeftPaneSectionHeader = styled(Flex)` + height: var(--main-view-header-height); + padding-inline-end: 7px; + transition: var(--default-duration); +`; const SectionTitle = styled.h1` padding-top: var(--margins-xs); @@ -117,30 +123,68 @@ export const LeftPaneBanner = () => { export const LeftPaneSectionHeader = () => { const showRecoveryPhrasePrompt = useSelector(getShowRecoveryPhrasePrompt); const focusedSection = useSelector(getFocusedSection); - const isMessageRequestOverlayShown = useSelector(getIsMessageRequestOverlayShown); + const leftOverlayMode = useSelector(getLeftOverlayMode); + + const dispatch = useDispatch(); + const returnToActionChooser = () => { + dispatch(setLeftOverlayMode('choose-action')); + }; let label: string | undefined; const isMessageSection = focusedSection === SectionType.Message; + let leftOverlayHeading = ''; + + switch (leftOverlayMode) { + case 'open-group': + leftOverlayHeading = window.i18n('joinOpenGroup'); + break; + case 'closed-group': + leftOverlayHeading = window.i18n('createGroup'); + break; + case 'message': + leftOverlayHeading = window.i18n('newMessage'); + break; + case 'message-requests': + leftOverlayHeading = window.i18n('messageRequests'); + break; + case 'choose-action': + default: + leftOverlayHeading = window.i18n('messagesHeader'); + } + switch (focusedSection) { case SectionType.Settings: label = window.i18n('settingsHeader'); break; case SectionType.Message: - label = isMessageRequestOverlayShown - ? window.i18n('messageRequests') - : window.i18n('messagesHeader'); + label = leftOverlayHeading; break; default: } return ( -
+ + {leftOverlayMode && + leftOverlayMode !== 'choose-action' && + leftOverlayMode !== 'message-requests' ? ( + + ) : null} {label} {isMessageSection && } -
+ {showRecoveryPhrasePrompt && }
); diff --git a/ts/components/leftpane/overlay/OverlayClosedGroup.tsx b/ts/components/leftpane/overlay/OverlayClosedGroup.tsx index 35281d215d..50f61f8146 100644 --- a/ts/components/leftpane/overlay/OverlayClosedGroup.tsx +++ b/ts/components/leftpane/overlay/OverlayClosedGroup.tsx @@ -8,7 +8,6 @@ import { SessionButton } from '../../basic/SessionButton'; import { SessionIdEditable } from '../../basic/SessionIdEditable'; import { SessionSpinner } from '../../loading'; import { MemberListItem } from '../../MemberListItem'; -import { OverlayHeader } from './OverlayHeader'; import { useSet } from '../../../hooks/useSet'; import { VALIDATION } from '../../../session/constants'; @@ -120,9 +119,7 @@ export const OverlayClosedGroup = () => { useKey('Escape', closeOverlay); - const title = window.i18n('createGroup'); const buttonText = window.i18n('create'); - const subtitle = window.i18n('createClosedGroupNamePrompt'); const placeholder = window.i18n('createClosedGroupPlaceholder'); const noContactsForClosedGroup = privateContactsPubkeys.length === 0; @@ -133,7 +130,6 @@ export const OverlayClosedGroup = () => { return (
-
{ useKey('Escape', closeOverlay); - const title = window.i18n('joinOpenGroup'); const buttonText = window.i18n('join'); - const subtitle = window.i18n('openGroupURL'); const placeholder = window.i18n('enterAnOpenGroupURL'); return (
-
{ - const dispatch = useDispatch(); - const returnToActionChooser = () => { - dispatch(setLeftOverlayMode('choose-action')); - }; - - return ( - <> - - - - - - - {title} - - - {subtitle} - - - - - ); -}; diff --git a/ts/components/leftpane/overlay/OverlayMessage.tsx b/ts/components/leftpane/overlay/OverlayMessage.tsx index 761d78addb..1b1f429ee2 100644 --- a/ts/components/leftpane/overlay/OverlayMessage.tsx +++ b/ts/components/leftpane/overlay/OverlayMessage.tsx @@ -12,7 +12,6 @@ import { resetLeftOverlayMode } from '../../../state/ducks/section'; import { SessionButton } from '../../basic/SessionButton'; import { SessionIdEditable } from '../../basic/SessionIdEditable'; import { SessionSpinner } from '../../loading'; -import { OverlayHeader } from './OverlayHeader'; import { ONSResolve } from '../../../session/apis/snode_api/onsResolve'; import { Flex } from '../../basic/Flex'; @@ -49,9 +48,7 @@ export const OverlayMessage = () => { const [pubkeyOrOns, setPubkeyOrOns] = useState(''); const [loading, setLoading] = useState(false); - const title = window.i18n('newMessage'); const buttonText = window.i18n('next'); - const subtitle = window.i18n('accountIdEnter'); const placeholder = window.i18n('accountIdEnterYourFriends'); const disableNextButton = !pubkeyOrOns || loading; @@ -114,8 +111,6 @@ export const OverlayMessage = () => { return (
- - Date: Mon, 22 Apr 2024 18:03:22 +1000 Subject: [PATCH 003/223] feat: added monospace textarea support to sessioninput this will replace the sessionideditable eventually still needs error handling etc --- _locales/en/messages.json | 2 +- ts/components/inputs/SessionInput.tsx | 127 +++++++++++++----- .../leftpane/overlay/OverlayMessage.tsx | 26 +++- ts/types/LocalizerKeys.ts | 2 +- 4 files changed, 118 insertions(+), 39 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 733a0ace59..9247d3a5e0 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -3,7 +3,7 @@ "accept": "Accept", "accountIDCopy": "Copy Account ID", "accountIdEnter": "Enter Account ID", - "accountIdEnterYourFriends": "Enter your friend's Account ID or ONS", + "accountIdOrOnsEnter": "Enter Account ID or ONS", "accountIdYours": "Your Account ID", "accountIdErrorInvalid": "This Account ID is invalid. Please check and try again.", "activeMembers": "$count$ active members", diff --git a/ts/components/inputs/SessionInput.tsx b/ts/components/inputs/SessionInput.tsx index 96b35dee05..b328f24ed0 100644 --- a/ts/components/inputs/SessionInput.tsx +++ b/ts/components/inputs/SessionInput.tsx @@ -30,13 +30,14 @@ const StyledInputContainer = styled(Flex)<{ error: boolean }>` } } - input::placeholder { + input::placeholder, + textarea::placeholder { transition: opacity var(--default-duration) color var(--default-duration); ${props => props.error && `color: var(--danger-color); opacity: 1;`} } `; -const StyledInput = styled(motion.input)` +const StyledInput = styled(motion.input)<{ centerText?: boolean }>` border: 1px solid var(--input-border-color); border-radius: 13px; outline: 0; @@ -48,9 +49,57 @@ const StyledInput = styled(motion.input)` font-size: 12px; line-height: 14px; padding: var(--margins-lg); + ${props => props.centerText && 'text-align: center;'} ::placeholder { color: var(--input-text-placeholder-color); + ${props => props.centerText && 'text-align: center;'} + } +`; + +const StyledTextAreaContainer = styled(motion.div)<{ centerText?: boolean }>` + border: 1px solid var(--input-border-color); + border-radius: 13px; + outline: 0; + width: 100%; + background: transparent; + color: var(--input-text-color); + + font-family: var(--font-mono); + font-size: var(--font-size-md); + min-height: 100px; + line-height: 18px; + + ${props => props.centerText && 'text-align: center;'} + + textarea { + width: 100%; + outline: 0; + border: none; + background: transparent; + + resize: none; + overflow: hidden; + overflow-wrap: break-word; + user-select: all; + + display: inline-block; + padding: var(--margins-lg); + margin: var(--margins-xs) 0; + + ${props => props.centerText && 'text-align: center;'} + + :placeholder-shown { + font-family: var(--font-default); + height: 28px; + margin: var(--margins-md) 0; + padding: var(--margins-xl); + } + + ::placeholder { + color: var(--input-text-placeholder-color); + ${props => props.centerText && 'text-align: center;'} + } } `; @@ -123,6 +172,9 @@ type Props = { inputDataTestId?: string; id?: string; ctaButton?: ReactNode; + /** Gives us a textarea with a monospace font. Mostly used for joining conversations, groups or communities */ + isSpecial?: boolean; + centerText?: boolean; }; export const SessionInput = (props: Props) => { @@ -140,6 +192,8 @@ export const SessionInput = (props: Props) => { inputDataTestId, id = 'session-input-floating-label', ctaButton, + isSpecial, + centerText, } = props; const [inputValue, setInputValue] = useState(''); const [errorString, setErrorString] = useState(''); @@ -156,6 +210,38 @@ export const SessionInput = (props: Props) => { } }; + // TODO[epic=893] Type inputProps properly + const inputProps: any = { + id, + type: correctType, + placeholder, + value, + maxLength, + autoFocus, + 'data-testid': inputDataTestId, + onChange: updateInputValue, + style: { paddingInlineEnd: enableShowHide ? '48px' : undefined }, + // just in case onChange isn't triggered + onBlur: (event: ChangeEvent) => { + if (!disabledOnBlur) { + updateInputValue(event); + } + }, + onKeyDown: (event: KeyboardEvent) => { + if (event.key === 'Enter' && onEnterPressed) { + onEnterPressed(inputValue); + setErrorString(''); + } + }, + initial: { + borderColor: errorString ? 'var(--input-border-color)' : undefined, + }, + animate: { + borderColor: errorString ? 'var(--danger-color)' : undefined, + }, + transition: { duration: THEME_GLOBALS['--default-duration-seconds'] }, + }; + // if we have an error, we want to show it even if the input changes to a valid value useEffect(() => { if (error && !isEmpty(error) && !isEqual(error, errorString)) { @@ -172,36 +258,13 @@ export const SessionInput = (props: Props) => { error={Boolean(errorString)} > - ) => { - if (!disabledOnBlur) { - updateInputValue(event); - } - }} - onKeyDown={event => { - if (event.key === 'Enter' && onEnterPressed) { - onEnterPressed(inputValue); - setErrorString(''); - } - }} - initial={{ - borderColor: errorString ? 'var(--input-border-color)' : undefined, - }} - animate={{ - borderColor: errorString ? 'var(--danger-color)' : undefined, - }} - transition={{ duration: THEME_GLOBALS['--default-duration-seconds'] }} - /> + {isSpecial ? ( + +