From 4160c871ca46d0d7200eabb87415c645c60989b9 Mon Sep 17 00:00:00 2001 From: evm97 Date: Wed, 18 Oct 2023 09:24:05 +0300 Subject: [PATCH 1/8] refactor: rename QUOTE vars to ULU --- src/components/common/Composer.tsx | 8 ++++---- src/components/middle/MessageList.tsx | 8 ++++---- src/config.ts | 2 +- src/global/helpers/chats.ts | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/common/Composer.tsx b/src/components/common/Composer.tsx index 1ed1746855..014132ae3d 100644 --- a/src/components/common/Composer.tsx +++ b/src/components/common/Composer.tsx @@ -36,10 +36,10 @@ import { EDITABLE_INPUT_MODAL_ID, HEART_REACTION, MAX_UPLOAD_FILEPART_SIZE, - QUOTE_APP as QUOTE_APP_CONSTANTS, REPLIES_USER_ID, SCHEDULED_WHEN_ONLINE, SEND_MESSAGE_ACTION_INTERVAL, + ULU_APP as ULU_APP_CONSTANTS, } from '../../config'; import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom'; import { @@ -50,7 +50,7 @@ import { isChatSuperGroup, isMainThread, isUserId, - QUOTE_APP as QUOTE_APP_UTILS, + ULU_APP as ULU_APP_UTILS, } from '../../global/helpers'; import { selectBot, @@ -922,8 +922,8 @@ const Composer: FC = ({ const shouldOpenRepliesChat = ( replyingToId - && QUOTE_APP_CONSTANTS.SHOULD_OPEN_REPLIES_CHAT_ON_REPLY - && QUOTE_APP_UTILS.doesChatSupportThreads(chat) + && ULU_APP_CONSTANTS.SHOULD_OPEN_REPLIES_CHAT_ON_REPLY + && ULU_APP_UTILS.doesChatSupportThreads(chat) ); const repliesChatToOpen = { id: chatId, diff --git a/src/components/middle/MessageList.tsx b/src/components/middle/MessageList.tsx index 25777ea8cf..e512b41043 100644 --- a/src/components/middle/MessageList.tsx +++ b/src/components/middle/MessageList.tsx @@ -32,7 +32,7 @@ import { isMainThread, isReplyToUserThreadMessage, isUserId, - QUOTE_APP as QUOTE_APP_UTILS, + ULU_APP as ULU_APP_UTILS, } from '../../global/helpers'; import { selectBot, @@ -135,7 +135,7 @@ const MESSAGE_ANIMATION_DURATION = 500; const BOTTOM_FOCUS_MARGIN = 20; const SELECT_MODE_ANIMATION_DURATION = 200; const UNREAD_DIVIDER_CLASS = 'unread-divider'; -const QUOTE_APP_WITH_REPLIES_IN_MAIN_THREAD = false; // TODO move somewhere else +const ULU_APP_WITH_REPLIES_IN_MAIN_THREAD = false; // TODO move somewhere else const runDebouncedForScroll = debounce((cb) => cb(), SCROLL_DEBOUNCE, false); @@ -202,7 +202,7 @@ const MessageList: FC = ({ const isScrollTopJustUpdatedRef = useRef(false); const shouldAnimateAppearanceRef = useRef(Boolean(lastMessage)); - const withRepliesToThreads = doesChatSupportThreads ? QUOTE_APP_WITH_REPLIES_IN_MAIN_THREAD : true; // TODO other group types + const withRepliesToThreads = doesChatSupportThreads ? ULU_APP_WITH_REPLIES_IN_MAIN_THREAD : true; // TODO other group types const areMessagesLoaded = Boolean(messageIds); @@ -695,7 +695,7 @@ export default memo(withGlobal( restrictionReason, isChannelChat: isChatChannel(chat), isGroupChat: isChatGroup(chat), - doesChatSupportThreads: QUOTE_APP_UTILS.doesChatSupportThreads(chat), + doesChatSupportThreads: ULU_APP_UTILS.doesChatSupportThreads(chat), isCreator: chat.isCreator, isChatWithSelf: selectIsChatWithSelf(global, chatId), isRepliesChat: isChatWithRepliesBot(chatId), diff --git a/src/config.ts b/src/config.ts index b3249f5b95..7b45648571 100644 --- a/src/config.ts +++ b/src/config.ts @@ -335,6 +335,6 @@ export const DEFAULT_LIMITS: Record = { export const IS_STORIES_ENABLED = false; -export const QUOTE_APP = { +export const ULU_APP = { SHOULD_OPEN_REPLIES_CHAT_ON_REPLY: true, }; diff --git a/src/global/helpers/chats.ts b/src/global/helpers/chats.ts index 91da08dfd5..c1a02d880a 100644 --- a/src/global/helpers/chats.ts +++ b/src/global/helpers/chats.ts @@ -77,7 +77,7 @@ export function isChatWithRepliesBot(chatId: string) { return chatId === REPLIES_USER_ID; } -export const QUOTE_APP = { +export const ULU_APP = { doesChatSupportThreads: (chat: ApiChat | undefined) => chat && isChatSuperGroupWithoutTopics(chat), }; From b6175c0c11d32ac6c325c617103d75a7a134682e Mon Sep 17 00:00:00 2001 From: evm97 Date: Wed, 18 Oct 2023 09:32:38 +0300 Subject: [PATCH 2/8] feat: [TG-156] add component search btn --- src/components/ui/UluSearchButton.module.scss | 32 +++++++++++++++++++ src/components/ui/UluSearchButton.tsx | 20 ++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/components/ui/UluSearchButton.module.scss create mode 100644 src/components/ui/UluSearchButton.tsx diff --git a/src/components/ui/UluSearchButton.module.scss b/src/components/ui/UluSearchButton.module.scss new file mode 100644 index 0000000000..7cbe6679ae --- /dev/null +++ b/src/components/ui/UluSearchButton.module.scss @@ -0,0 +1,32 @@ +.wrapper { + padding: 0.5625rem; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border-radius: var(--border-radius-default-tiny); + border: none; + background-color: transparent; + height: 2.25rem; + width: 2.25rem; + + :global(.icon-search) { + $icon-size: 1.125rem; + + height: $icon-size; + width: $icon-size; + color: var(--color-text-secondary); + + &::before { + font-size: $icon-size; + } + } + + &:hover { + background-color: var(--color-chat-hover); + + :global(.icon-search) { + color: var(--color-text); + } + } +} \ No newline at end of file diff --git a/src/components/ui/UluSearchButton.tsx b/src/components/ui/UluSearchButton.tsx new file mode 100644 index 0000000000..5487d0d0d9 --- /dev/null +++ b/src/components/ui/UluSearchButton.tsx @@ -0,0 +1,20 @@ +import type { FC } from '../../lib/teact/teact'; +import React, { memo } from '../../lib/teact/teact'; + +import Button from './Button'; + +import styles from './UluSearchButton.module.scss'; + +type OwnProps = { + onClick: () => void; +}; + +const UluSearchButton: FC = ({ onClick }) => { + return ( + + ); +}; + +export default memo(UluSearchButton); From 3361a5e0fb63077ad9b9f47f376489c5b577d216 Mon Sep 17 00:00:00 2001 From: evm97 Date: Wed, 18 Oct 2023 09:41:01 +0300 Subject: [PATCH 3/8] chore: [TG-156] update ProfilePhoto component --- src/components/common/ProfilePhoto.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/common/ProfilePhoto.tsx b/src/components/common/ProfilePhoto.tsx index ec15444211..4eeece94ff 100644 --- a/src/components/common/ProfilePhoto.tsx +++ b/src/components/common/ProfilePhoto.tsx @@ -36,16 +36,18 @@ type OwnProps = { isSavedMessages?: boolean; photo?: ApiPhoto; canPlayVideo: boolean; - onClick: NoneToVoidFunction; + onClick?: NoneToVoidFunction; }; +const noneToVoid: NoneToVoidFunction = () => void 0; + const ProfilePhoto: FC = ({ chat, user, photo, isSavedMessages, canPlayVideo, - onClick, + onClick = noneToVoid, }) => { // eslint-disable-next-line no-null/no-null const videoRef = useRef(null); From 0fafe107c9f2d6cd5830a963992d3f9bd32d3174 Mon Sep 17 00:00:00 2001 From: evm97 Date: Wed, 18 Oct 2023 09:48:48 +0300 Subject: [PATCH 4/8] feat: [TG-156] update components tree in sidebar --- src/components/left/UluLeftColumn.scss | 69 +++ src/components/left/UluLeftColumn.tsx | 578 ++++++++++++++++++ .../left/main/UluHeaderProfile.module.scss | 33 + src/components/left/main/UluHeaderProfile.tsx | 63 ++ src/components/left/main/UluLeftMain.scss | 82 +++ src/components/left/main/UluLeftMain.tsx | 250 ++++++++ .../left/main/UluLeftMainHeader.module.scss | 147 +++++ .../left/main/UluLeftMainHeader.tsx | 356 +++++++++++ src/components/main/Main.tsx | 4 +- 9 files changed, 1580 insertions(+), 2 deletions(-) create mode 100644 src/components/left/UluLeftColumn.scss create mode 100644 src/components/left/UluLeftColumn.tsx create mode 100644 src/components/left/main/UluHeaderProfile.module.scss create mode 100644 src/components/left/main/UluHeaderProfile.tsx create mode 100644 src/components/left/main/UluLeftMain.scss create mode 100644 src/components/left/main/UluLeftMain.tsx create mode 100644 src/components/left/main/UluLeftMainHeader.module.scss create mode 100644 src/components/left/main/UluLeftMainHeader.tsx diff --git a/src/components/left/UluLeftColumn.scss b/src/components/left/UluLeftColumn.scss new file mode 100644 index 0000000000..bb438764bd --- /dev/null +++ b/src/components/left/UluLeftColumn.scss @@ -0,0 +1,69 @@ +#NewChat { + height: 100%; +} + +.left-header { + height: var(--header-height); + padding: 0.5rem; + display: flex; + align-items: center; + flex-shrink: 0; + background-color: var(--color-background); + + h3 { + margin-bottom: 0; + font-size: 1.25rem; + font-weight: 500; + margin-left: 1.375rem; + margin-right: auto; + user-select: none; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .SearchInput { + margin-left: 0.8125rem; + max-width: calc(100% - 3.25rem); + + @media (max-width: 600px) { + max-width: calc(100% - 3rem); + } + } + + @media (max-width: 600px) { + padding: 0.5rem; + } + + .Button.smaller { + width: 2.5rem; + height: 2.5rem; + + + .DropdownMenu { + margin-left: 0.25rem; + } + } + + body.is-electron.is-macos & { + -webkit-app-region: drag; + + .SearchInput { + -webkit-app-region: no-drag; + } + } + + body.is-electron.is-macos #Main:not(.is-fullscreen) &:not(#TopicListHeader) { + justify-content: space-between; + padding: 0.5rem 0.5rem 0.5rem 4.5rem; + + .SearchInput { + margin-left: 0.5rem; + max-width: calc(100% - 2.75rem); + } + + .Menu.main-menu .bubble { + --offset-y: 100%; + --offset-x: -4.125rem; + } + } +} diff --git a/src/components/left/UluLeftColumn.tsx b/src/components/left/UluLeftColumn.tsx new file mode 100644 index 0000000000..0a4ef597b2 --- /dev/null +++ b/src/components/left/UluLeftColumn.tsx @@ -0,0 +1,578 @@ +import type { RefObject } from 'react'; +import React, { + memo, useEffect, useState, +} from '../../lib/teact/teact'; +import { getActions, withGlobal } from '../../global'; + +import type { GlobalState } from '../../global/types'; +import type { FoldersActions } from '../../hooks/reducers/useFoldersReducer'; +import type { ReducerAction } from '../../hooks/useReducer'; +import { LeftColumnContent, SettingsScreens } from '../../types'; + +import { selectCurrentChat, selectIsForumPanelOpen, selectTabState } from '../../global/selectors'; +import captureEscKeyListener from '../../util/captureEscKeyListener'; +import { IS_APP, IS_MAC_OS, LAYERS_ANIMATION_NAME } from '../../util/windowEnvironment'; + +import useFoldersReducer from '../../hooks/reducers/useFoldersReducer'; +import { useHotkeys } from '../../hooks/useHotkeys'; +import useLastCallback from '../../hooks/useLastCallback'; +import useSyncEffect from '../../hooks/useSyncEffect'; + +import Transition from '../ui/Transition'; +import ArchivedChats from './ArchivedChats.async'; +import UluLeftMain from './main/UluLeftMain'; +import NewChat from './newChat/NewChat.async'; +import Settings from './settings/Settings.async'; + +import './UluLeftColumn.scss'; + +interface OwnProps { + ref: RefObject; +} + +type StateProps = { + searchQuery?: string; + searchDate?: number; + isFirstChatFolderActive: boolean; + shouldSkipHistoryAnimations?: boolean; + currentUserId?: string; + hasPasscode?: boolean; + nextSettingsScreen?: SettingsScreens; + nextFoldersAction?: ReducerAction; + isChatOpen: boolean; + isAppUpdateAvailable?: boolean; + isElectronUpdateAvailable?: boolean; + isForumPanelOpen?: boolean; + forumPanelChatId?: string; + isClosingSearch?: boolean; + archiveSettings: GlobalState['archiveSettings']; + isArchivedStoryRibbonShown?: boolean; +}; + +enum ContentType { + Main, + // eslint-disable-next-line @typescript-eslint/no-shadow + Settings, + Archived, + // eslint-disable-next-line no-shadow + NewGroup, + // eslint-disable-next-line no-shadow + NewChannel, +} + +const RENDER_COUNT = Object.keys(ContentType).length / 2; +const RESET_TRANSITION_DELAY_MS = 250; + +function LeftColumn({ + ref, + searchQuery, + searchDate, + isFirstChatFolderActive, + shouldSkipHistoryAnimations, + currentUserId, + hasPasscode, + nextSettingsScreen, + nextFoldersAction, + isChatOpen, + isAppUpdateAvailable, + isElectronUpdateAvailable, + isForumPanelOpen, + forumPanelChatId, + isClosingSearch, + archiveSettings, + isArchivedStoryRibbonShown, +}: OwnProps & StateProps) { + const { + setGlobalSearchQuery, + setGlobalSearchClosing, + setGlobalSearchChatId, + resetChatCreation, + setGlobalSearchDate, + loadPasswordInfo, + clearTwoFaError, + openChat, + requestNextSettingsScreen, + } = getActions(); + + const [content, setContent] = useState(LeftColumnContent.ChatList); + const [settingsScreen, setSettingsScreen] = useState(SettingsScreens.Main); + const [contactsFilter, setContactsFilter] = useState(''); + const [foldersState, foldersDispatch] = useFoldersReducer(); + + // Used to reset child components in background. + const [lastResetTime, setLastResetTime] = useState(0); + + let contentType: ContentType = ContentType.Main; + switch (content) { + case LeftColumnContent.Archived: + contentType = ContentType.Archived; + break; + case LeftColumnContent.Settings: + contentType = ContentType.Settings; + break; + case LeftColumnContent.NewChannelStep1: + case LeftColumnContent.NewChannelStep2: + contentType = ContentType.NewChannel; + break; + case LeftColumnContent.NewGroupStep1: + case LeftColumnContent.NewGroupStep2: + contentType = ContentType.NewGroup; + break; + } + + const handleReset = useLastCallback((forceReturnToChatList?: true | Event) => { + function fullReset() { + setContent(LeftColumnContent.ChatList); + setSettingsScreen(SettingsScreens.Main); + setContactsFilter(''); + setGlobalSearchClosing({ isClosing: true }); + resetChatCreation(); + setTimeout(() => { + setGlobalSearchQuery({ query: '' }); + setGlobalSearchDate({ date: undefined }); + setGlobalSearchChatId({ id: undefined }); + setGlobalSearchClosing({ isClosing: false }); + setLastResetTime(Date.now()); + }, RESET_TRANSITION_DELAY_MS); + } + + if (forceReturnToChatList === true) { + fullReset(); + return; + } + + if (content === LeftColumnContent.NewGroupStep2) { + setContent(LeftColumnContent.NewGroupStep1); + return; + } + + if (content === LeftColumnContent.NewChannelStep2) { + setContent(LeftColumnContent.NewChannelStep1); + return; + } + + if (content === LeftColumnContent.NewGroupStep1) { + const pickerSearchInput = document.getElementById('new-group-picker-search'); + if (pickerSearchInput) { + pickerSearchInput.blur(); + } + } + + if (content === LeftColumnContent.Settings) { + switch (settingsScreen) { + case SettingsScreens.EditProfile: + case SettingsScreens.Folders: + case SettingsScreens.General: + case SettingsScreens.Notifications: + case SettingsScreens.DataStorage: + case SettingsScreens.Privacy: + case SettingsScreens.Performance: + case SettingsScreens.ActiveSessions: + case SettingsScreens.Language: + case SettingsScreens.Stickers: + case SettingsScreens.Experimental: + setSettingsScreen(SettingsScreens.Main); + return; + + case SettingsScreens.GeneralChatBackground: + setSettingsScreen(SettingsScreens.General); + return; + case SettingsScreens.GeneralChatBackgroundColor: + setSettingsScreen(SettingsScreens.GeneralChatBackground); + return; + + case SettingsScreens.PrivacyPhoneNumber: + case SettingsScreens.PrivacyAddByPhone: + case SettingsScreens.PrivacyLastSeen: + case SettingsScreens.PrivacyProfilePhoto: + case SettingsScreens.PrivacyBio: + case SettingsScreens.PrivacyPhoneCall: + case SettingsScreens.PrivacyPhoneP2P: + case SettingsScreens.PrivacyForwarding: + case SettingsScreens.PrivacyGroupChats: + case SettingsScreens.PrivacyVoiceMessages: + case SettingsScreens.PrivacyBlockedUsers: + case SettingsScreens.ActiveWebsites: + case SettingsScreens.TwoFaDisabled: + case SettingsScreens.TwoFaEnabled: + case SettingsScreens.TwoFaCongratulations: + case SettingsScreens.PasscodeDisabled: + case SettingsScreens.PasscodeEnabled: + case SettingsScreens.PasscodeCongratulations: + setSettingsScreen(SettingsScreens.Privacy); + return; + + case SettingsScreens.PasscodeNewPasscode: + setSettingsScreen(hasPasscode ? SettingsScreens.PasscodeEnabled : SettingsScreens.PasscodeDisabled); + return; + + case SettingsScreens.PasscodeChangePasscodeCurrent: + case SettingsScreens.PasscodeTurnOff: + setSettingsScreen(SettingsScreens.PasscodeEnabled); + return; + + case SettingsScreens.PasscodeNewPasscodeConfirm: + setSettingsScreen(SettingsScreens.PasscodeNewPasscode); + return; + + case SettingsScreens.PasscodeChangePasscodeNew: + setSettingsScreen(SettingsScreens.PasscodeChangePasscodeCurrent); + return; + + case SettingsScreens.PasscodeChangePasscodeConfirm: + setSettingsScreen(SettingsScreens.PasscodeChangePasscodeNew); + return; + + case SettingsScreens.PrivacyPhoneNumberAllowedContacts: + case SettingsScreens.PrivacyPhoneNumberDeniedContacts: + setSettingsScreen(SettingsScreens.PrivacyPhoneNumber); + return; + case SettingsScreens.PrivacyLastSeenAllowedContacts: + case SettingsScreens.PrivacyLastSeenDeniedContacts: + setSettingsScreen(SettingsScreens.PrivacyLastSeen); + return; + case SettingsScreens.PrivacyProfilePhotoAllowedContacts: + case SettingsScreens.PrivacyProfilePhotoDeniedContacts: + setSettingsScreen(SettingsScreens.PrivacyProfilePhoto); + return; + case SettingsScreens.PrivacyBioAllowedContacts: + case SettingsScreens.PrivacyBioDeniedContacts: + setSettingsScreen(SettingsScreens.PrivacyBio); + return; + case SettingsScreens.PrivacyPhoneCallAllowedContacts: + case SettingsScreens.PrivacyPhoneCallDeniedContacts: + setSettingsScreen(SettingsScreens.PrivacyPhoneCall); + return; + case SettingsScreens.PrivacyPhoneP2PAllowedContacts: + case SettingsScreens.PrivacyPhoneP2PDeniedContacts: + setSettingsScreen(SettingsScreens.PrivacyPhoneP2P); + return; + case SettingsScreens.PrivacyForwardingAllowedContacts: + case SettingsScreens.PrivacyForwardingDeniedContacts: + setSettingsScreen(SettingsScreens.PrivacyForwarding); + return; + case SettingsScreens.PrivacyVoiceMessagesAllowedContacts: + case SettingsScreens.PrivacyVoiceMessagesDeniedContacts: + setSettingsScreen(SettingsScreens.PrivacyVoiceMessages); + return; + case SettingsScreens.PrivacyGroupChatsAllowedContacts: + case SettingsScreens.PrivacyGroupChatsDeniedContacts: + setSettingsScreen(SettingsScreens.PrivacyGroupChats); + return; + case SettingsScreens.TwoFaNewPassword: + setSettingsScreen(SettingsScreens.TwoFaDisabled); + return; + case SettingsScreens.TwoFaNewPasswordConfirm: + setSettingsScreen(SettingsScreens.TwoFaNewPassword); + return; + case SettingsScreens.TwoFaNewPasswordHint: + setSettingsScreen(SettingsScreens.TwoFaNewPasswordConfirm); + return; + case SettingsScreens.TwoFaNewPasswordEmail: + setSettingsScreen(SettingsScreens.TwoFaNewPasswordHint); + return; + case SettingsScreens.TwoFaNewPasswordEmailCode: + setSettingsScreen(SettingsScreens.TwoFaNewPasswordEmail); + return; + case SettingsScreens.TwoFaChangePasswordCurrent: + case SettingsScreens.TwoFaTurnOff: + case SettingsScreens.TwoFaRecoveryEmailCurrentPassword: + setSettingsScreen(SettingsScreens.TwoFaEnabled); + return; + case SettingsScreens.TwoFaChangePasswordNew: + setSettingsScreen(SettingsScreens.TwoFaChangePasswordCurrent); + return; + case SettingsScreens.TwoFaChangePasswordConfirm: + setSettingsScreen(SettingsScreens.TwoFaChangePasswordNew); + return; + case SettingsScreens.TwoFaChangePasswordHint: + setSettingsScreen(SettingsScreens.TwoFaChangePasswordConfirm); + return; + case SettingsScreens.TwoFaRecoveryEmail: + setSettingsScreen(SettingsScreens.TwoFaRecoveryEmailCurrentPassword); + return; + case SettingsScreens.TwoFaRecoveryEmailCode: + setSettingsScreen(SettingsScreens.TwoFaRecoveryEmail); + return; + + case SettingsScreens.FoldersCreateFolder: + case SettingsScreens.FoldersEditFolder: + setSettingsScreen(SettingsScreens.Folders); + return; + + case SettingsScreens.FoldersShare: + setSettingsScreen(SettingsScreens.FoldersEditFolder); + return; + + case SettingsScreens.FoldersIncludedChatsFromChatList: + case SettingsScreens.FoldersExcludedChatsFromChatList: + setSettingsScreen(SettingsScreens.FoldersEditFolderFromChatList); + return; + + case SettingsScreens.FoldersEditFolderFromChatList: + case SettingsScreens.FoldersEditFolderInvites: + setContent(LeftColumnContent.ChatList); + setSettingsScreen(SettingsScreens.Main); + return; + + case SettingsScreens.QuickReaction: + case SettingsScreens.CustomEmoji: + setSettingsScreen(SettingsScreens.Stickers); + return; + + case SettingsScreens.DoNotTranslate: + setSettingsScreen(SettingsScreens.Language); + return; + default: + break; + } + } + + if (content === LeftColumnContent.ChatList && isFirstChatFolderActive) { + setContent(LeftColumnContent.GlobalSearch); + + return; + } + + fullReset(); + }); + + const handleSearchQuery = useLastCallback((query: string) => { + if (content === LeftColumnContent.Contacts) { + setContactsFilter(query); + return; + } + + setContent(LeftColumnContent.GlobalSearch); + + if (query !== searchQuery) { + setGlobalSearchQuery({ query }); + } + }); + + const handleTopicSearch = useLastCallback(() => { + setContent(LeftColumnContent.GlobalSearch); + setGlobalSearchQuery({ query: '' }); + setGlobalSearchChatId({ id: forumPanelChatId }); + }); + + useEffect( + () => { + const isArchived = content === LeftColumnContent.Archived; + const isChatList = content === LeftColumnContent.ChatList; + const noChatOrForumOpen = !isChatOpen && !isForumPanelOpen; + // We listen for escape key only in these cases: + // 1. When we are in archived chats and no chat or forum is open. + // 2. When we are in any other screen except chat list and archived chat list. + // 3. When we are in chat list and first chat folder is active and no chat or forum is open. + if ((isArchived && noChatOrForumOpen) || (!isChatList && !isArchived) + || (isFirstChatFolderActive && noChatOrForumOpen)) { + return captureEscKeyListener(() => { + handleReset(); + }); + } else { + return undefined; + } + }, + [isFirstChatFolderActive, content, handleReset, isChatOpen, isForumPanelOpen], + ); + + const handleHotkeySearch = useLastCallback((e: KeyboardEvent) => { + if (content === LeftColumnContent.GlobalSearch) { + return; + } + + e.preventDefault(); + setContent(LeftColumnContent.GlobalSearch); + }); + + const handleHotkeySavedMessages = useLastCallback((e: KeyboardEvent) => { + e.preventDefault(); + openChat({ id: currentUserId, shouldReplaceHistory: true }); + }); + + const handleArchivedChats = useLastCallback((e: KeyboardEvent) => { + e.preventDefault(); + setContent(LeftColumnContent.Archived); + }); + + const handleHotkeySettings = useLastCallback((e: KeyboardEvent) => { + e.preventDefault(); + setContent(LeftColumnContent.Settings); + }); + + useHotkeys({ + 'Mod+Shift+F': handleHotkeySearch, + 'Mod+Shift+S': handleHotkeySavedMessages, + ...(IS_APP && { + 'Mod+0': handleHotkeySavedMessages, + 'Mod+9': handleArchivedChats, + }), + ...(IS_MAC_OS && IS_APP && { 'Mod+,': handleHotkeySettings }), + }); + + useEffect(() => { + clearTwoFaError(); + + if (settingsScreen === SettingsScreens.Privacy) { + loadPasswordInfo(); + } + }, [clearTwoFaError, loadPasswordInfo, settingsScreen]); + + useSyncEffect(() => { + if (nextSettingsScreen !== undefined) { + setContent(LeftColumnContent.Settings); + setSettingsScreen(nextSettingsScreen); + requestNextSettingsScreen({ screen: undefined }); + } + + if (nextFoldersAction) { + foldersDispatch(nextFoldersAction); + } + }, [foldersDispatch, nextFoldersAction, nextSettingsScreen, requestNextSettingsScreen]); + + const handleSettingsScreenSelect = useLastCallback((screen: SettingsScreens) => { + setContent(LeftColumnContent.Settings); + setSettingsScreen(screen); + }); + + function renderContent(isActive: boolean) { + switch (contentType) { + case ContentType.Archived: + return ( + + ); + case ContentType.Settings: + return ( + + ); + case ContentType.NewChannel: + return ( + + ); + case ContentType.NewGroup: + return ( + + ); + default: + return ( + + ); + } + } + + return ( + + {renderContent} + + ); +} + +export default memo(withGlobal( + (global): StateProps => { + const tabState = selectTabState(global); + const { + globalSearch: { + query, + date, + }, + shouldSkipHistoryAnimations, + activeChatFolder, + nextSettingsScreen, + nextFoldersAction, + storyViewer: { + isArchivedRibbonShown, + }, + } = tabState; + const { + currentUserId, + passcode: { + hasPasscode, + }, + isAppUpdateAvailable, + isElectronUpdateAvailable, + archiveSettings, + } = global; + + const currentChat = selectCurrentChat(global); + const isChatOpen = Boolean(currentChat?.id); + const isForumPanelOpen = selectIsForumPanelOpen(global); + const forumPanelChatId = tabState.forumPanelChatId; + + return { + searchQuery: query, + searchDate: date, + isFirstChatFolderActive: activeChatFolder === 0, + shouldSkipHistoryAnimations, + currentUserId, + hasPasscode, + nextSettingsScreen, + nextFoldersAction, + isChatOpen, + isAppUpdateAvailable, + isElectronUpdateAvailable, + isForumPanelOpen, + forumPanelChatId, + isClosingSearch: tabState.globalSearch.isClosing, + archiveSettings, + isArchivedStoryRibbonShown: isArchivedRibbonShown, + }; + }, +)(LeftColumn)); diff --git a/src/components/left/main/UluHeaderProfile.module.scss b/src/components/left/main/UluHeaderProfile.module.scss new file mode 100644 index 0000000000..f605d3b25f --- /dev/null +++ b/src/components/left/main/UluHeaderProfile.module.scss @@ -0,0 +1,33 @@ +.wrapper { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem; + cursor: pointer; + border-radius: var(--border-radius-default-tiny); + + &:hover { + background-color: var(--color-chat-hover); + + .userName { + color: var(--color-text); + } + } +} + +.photoWrapper { + height: 1.5rem; + width: 1.5rem; + + :global(.avatar-media) { + border-radius: 0.5rem; + vertical-align: inherit; + } +} + +.userName { + color: var(--color-text-secondary); + font-weight: 600; + font-size: 0.875rem; + line-height: 1.5rem; +} \ No newline at end of file diff --git a/src/components/left/main/UluHeaderProfile.tsx b/src/components/left/main/UluHeaderProfile.tsx new file mode 100644 index 0000000000..47807eb71b --- /dev/null +++ b/src/components/left/main/UluHeaderProfile.tsx @@ -0,0 +1,63 @@ +import type { MouseEvent as ReactMouseEvent } from 'react'; +import type { FC } from '../../../lib/teact/teact'; +import React, { memo } from '../../../lib/teact/teact'; +import { withGlobal } from '../../../global'; + +import type { ApiPhoto, ApiUser } from '../../../api/types'; + +import { selectUser, selectUserFullInfo } from '../../../global/selectors'; + +import ProfilePhoto from '../../common/ProfilePhoto'; + +import styles from './UluHeaderProfile.module.scss'; + +type OwnProps = { + onClick?: (e: ReactMouseEvent) => void; +}; + +type StateProps = { + user?: ApiUser; + userPersonalPhoto?: ApiPhoto; + userProfilePhoto?: ApiPhoto; + userFallbackPhoto?: ApiPhoto; +}; + +const UluHeaderProfile: FC = ({ + user, userFallbackPhoto, userPersonalPhoto, userProfilePhoto, onClick, +}) => { + function renderPhoto() { + const profilePhoto = userPersonalPhoto || userProfilePhoto || userFallbackPhoto; + + return ( + + ); + } + + return ( +
+
+ {renderPhoto()} +
+
+ { `${user!.firstName} ${user!.lastName}` } +
+
+ ); +}; + +export default memo(withGlobal((global) => { + const { currentUserId } = global; + const user = selectUser(global, currentUserId!); + const userFullInfo = selectUserFullInfo(global, currentUserId!); + + return { + user, + userPersonalPhoto: userFullInfo?.personalPhoto, + userProfilePhoto: userFullInfo?.profilePhoto, + userFallbackPhoto: userFullInfo?.fallbackPhoto, + }; +})(UluHeaderProfile)); diff --git a/src/components/left/main/UluLeftMain.scss b/src/components/left/main/UluLeftMain.scss new file mode 100644 index 0000000000..6058d28893 --- /dev/null +++ b/src/components/left/main/UluLeftMain.scss @@ -0,0 +1,82 @@ +#UluLeftColumn-main { + height: 100%; + position: relative; + display: flex; + flex-direction: column; + // gap: 1.25rem; + overflow: hidden; + background: var(--color-background); + + > .Transition { + flex: 1; + overflow: hidden; + } + + .ChatFolders { + height: 100%; + display: flex; + flex-direction: column; + overflow: hidden; + + .tabs-placeholder { + height: 2.625rem; + /* stylelint-disable-next-line plugin/no-low-performance-animation-properties */ + transition: height 150ms ease; + + &:not(.open) { + height: 0; + } + } + + .TabList { + justify-content: flex-start; + padding-left: 0.5625rem; + padding-bottom: 1px; + border-bottom: 0; + z-index: 1; + + opacity: 1; + transition: opacity var(--layer-transition); + } + + &--tabs-hidden .TabList { + pointer-events: none; + opacity: 0.25; + } + + .Tab { + flex: 0 0 auto; + padding-left: 0.625rem; + padding-right: 0.625rem; + + /* stylelint-disable-next-line */ + > span { + padding-left: 0.5rem; + padding-right: 0.5rem; + white-space: pre; + } + } + + > .Transition { + flex: 1; + overflow: hidden; + } + } + + .RecentContacts, + .LeftSearch, + .search-content { + height: 100%; + overflow-y: auto; + } + + .btn-update { + position: absolute; + bottom: 1rem; + left: 50%; + margin: 0 auto; + transform: translateX(-50%); + + white-space: nowrap; + } +} diff --git a/src/components/left/main/UluLeftMain.tsx b/src/components/left/main/UluLeftMain.tsx new file mode 100644 index 0000000000..a847a51203 --- /dev/null +++ b/src/components/left/main/UluLeftMain.tsx @@ -0,0 +1,250 @@ +import type { FC } from '../../../lib/teact/teact'; +import React, { + memo, useEffect, useRef, useState, +} from '../../../lib/teact/teact'; +import { getActions } from '../../../global'; + +import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer'; +import type { SettingsScreens } from '../../../types'; +import { LeftColumnContent } from '../../../types'; + +import { PRODUCTION_URL } from '../../../config'; +import buildClassName from '../../../util/buildClassName'; +import { IS_ELECTRON, IS_TOUCH_ENV } from '../../../util/windowEnvironment'; + +import useForumPanelRender from '../../../hooks/useForumPanelRender'; +import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; +import useShowTransition from '../../../hooks/useShowTransition'; + +import Button from '../../ui/Button'; +import Transition from '../../ui/Transition'; +import NewChatButton from '../NewChatButton'; +import LeftSearch from '../search/LeftSearch.async'; +import ChatFolders from './ChatFolders'; +import ContactList from './ContactList.async'; +import ForumPanel from './ForumPanel'; +import UluLeftMainHeader from './UluLeftMainHeader'; + +import './UluLeftMain.scss'; + +type OwnProps = { + content: LeftColumnContent; + searchQuery?: string; + searchDate?: number; + contactsFilter: string; + shouldSkipTransition?: boolean; + foldersDispatch: FolderEditDispatch; + isAppUpdateAvailable?: boolean; + isElectronUpdateAvailable?: boolean; + isForumPanelOpen?: boolean; + isClosingSearch?: boolean; + onSearchQuery: (query: string) => void; + onContentChange: (content: LeftColumnContent) => void; + onSettingsScreenSelect: (screen: SettingsScreens) => void; + onTopicSearch: NoneToVoidFunction; + onReset: () => void; +}; + +const TRANSITION_RENDER_COUNT = Object.keys(LeftColumnContent).length / 2; +const BUTTON_CLOSE_DELAY_MS = 250; + +let closeTimeout: number | undefined; + +const LeftMain: FC = ({ + content, + searchQuery, + searchDate, + isClosingSearch, + contactsFilter, + shouldSkipTransition, + foldersDispatch, + isAppUpdateAvailable, + isElectronUpdateAvailable, + isForumPanelOpen, + onSearchQuery, + onContentChange, + onSettingsScreenSelect, + onReset, + onTopicSearch, +}) => { + const { closeForumPanel } = getActions(); + const [isNewChatButtonShown, setIsNewChatButtonShown] = useState(IS_TOUCH_ENV); + const [isElectronAutoUpdateEnabled, setIsElectronAutoUpdateEnabled] = useState(false); + + useEffect(() => { + window.electron?.getIsAutoUpdateEnabled().then(setIsElectronAutoUpdateEnabled); + }, []); + + const { + shouldRenderForumPanel, handleForumPanelAnimationEnd, + handleForumPanelAnimationStart, isAnimationStarted, + } = useForumPanelRender(isForumPanelOpen); + const isForumPanelRendered = isForumPanelOpen && content === LeftColumnContent.ChatList; + const isForumPanelVisible = isForumPanelRendered && isAnimationStarted; + + const { + shouldRender: shouldRenderUpdateButton, + transitionClassNames: updateButtonClassNames, + } = useShowTransition(isAppUpdateAvailable || isElectronUpdateAvailable); + + const isMouseInside = useRef(false); + + const handleMouseEnter = useLastCallback(() => { + if (content !== LeftColumnContent.ChatList) { + return; + } + isMouseInside.current = true; + setIsNewChatButtonShown(true); + }); + + const handleMouseLeave = useLastCallback(() => { + isMouseInside.current = false; + + if (closeTimeout) { + clearTimeout(closeTimeout); + closeTimeout = undefined; + } + + closeTimeout = window.setTimeout(() => { + if (!isMouseInside.current) { + setIsNewChatButtonShown(false); + } + }, BUTTON_CLOSE_DELAY_MS); + }); + + const handleSelectSettings = useLastCallback(() => { + onContentChange(LeftColumnContent.Settings); + }); + + const handleSelectContacts = useLastCallback(() => { + onContentChange(LeftColumnContent.Contacts); + }); + + const handleSelectArchived = useLastCallback(() => { + onContentChange(LeftColumnContent.Archived); + closeForumPanel(); + }); + + const handleUpdateClick = useLastCallback(() => { + if (IS_ELECTRON && !isElectronAutoUpdateEnabled) { + window.open(`${PRODUCTION_URL}/get`, '_blank', 'noopener'); + } else if (isElectronUpdateAvailable) { + window.electron?.installUpdate(); + } else { + window.location.reload(); + } + }); + + const handleSelectNewChannel = useLastCallback(() => { + onContentChange(LeftColumnContent.NewChannelStep1); + }); + + const handleSelectNewGroup = useLastCallback(() => { + onContentChange(LeftColumnContent.NewGroupStep1); + }); + + useEffect(() => { + let autoCloseTimeout: number | undefined; + if (content !== LeftColumnContent.ChatList) { + autoCloseTimeout = window.setTimeout(() => { + setIsNewChatButtonShown(false); + }, BUTTON_CLOSE_DELAY_MS); + } else if (isMouseInside.current || IS_TOUCH_ENV) { + setIsNewChatButtonShown(true); + } + + return () => { + if (autoCloseTimeout) { + clearTimeout(autoCloseTimeout); + autoCloseTimeout = undefined; + } + }; + }, [content]); + + const lang = useLang(); + + return ( +
+ + + {(isActive) => { + switch (content) { + case LeftColumnContent.ChatList: + return ( + + ); + case LeftColumnContent.GlobalSearch: + return ( + + ); + case LeftColumnContent.Contacts: + return ; + default: + return undefined; + } + }} + + {shouldRenderUpdateButton && ( + + )} + {shouldRenderForumPanel && ( + + )} + +
+ ); +}; + +export default memo(LeftMain); diff --git a/src/components/left/main/UluLeftMainHeader.module.scss b/src/components/left/main/UluLeftMainHeader.module.scss new file mode 100644 index 0000000000..f621f51a83 --- /dev/null +++ b/src/components/left/main/UluLeftMainHeader.module.scss @@ -0,0 +1,147 @@ +@import "../../../styles/mixins"; + +#LeftMainHeader { + position: relative; + + .DropdownMenuFiller { + width: 2.5rem; + height: 2.5rem; + } + + .DropdownMenu.rtl { + transition: var(--slide-transition) transform; + position: absolute; + z-index: 2; + + &.right-aligned { + transform: translateX(calc(clamp( + var(--left-column-min-width), + var(--left-column-width), + var(--left-column-max-width) + ) - 4.375rem)); + } + + &.disable-transition { + transition: none; + } + } + + .animated-menu-icon { + position: absolute; + + &, + &::before, + &::after { + width: 1.125rem; + height: 0.125rem; + border-radius: 0.125rem; + background-color: var(--color-text-secondary); + transition: transform 0.25s; + transform: rotate(0); + } + + &::before, + &::after { + position: absolute; + left: 0; + content: ""; + } + + &::before { + top: -0.3125rem; + } + + &::after { + top: 0.3125rem; + } + + &.state-back { + transform: rotate(180deg); + + &::before { + transform: rotate(45deg) scaleX(0.75) translate(0.375rem, -0.1875rem); + } + + &::after { + transform: rotate(-45deg) scaleX(0.75) translate(0.375rem, 0.1875rem); + } + } + + &.no-animation { + transition: none; + + &::before, + &::after { + transition: none; + } + } + } + + .MenuItem .Toggle { + margin-inline-start: auto; + } + + .MenuItem.compact .Toggle { + transform: scale(0.75); + margin-inline-end: -0.125rem; + } + + .MenuItem.compact .Switcher { + transform: scale(0.75); + } + + + .Menu .bubble { + min-width: 17rem; + max-height: calc(100 * var(--vh) - 3.75rem); + + overflow-y: auto; + } + + .extra-spacing { + position: relative; + margin-left: 0.8125rem; + + body.is-electron.is-macos #Main:not(.is-fullscreen) & { + margin-left: 0.5rem; + } + } + + .emoji-status-effect { + top: 50%; + left: 50%; + } + + .emoji-status { + --custom-emoji-size: 1.5rem; + color: var(--color-primary); + } + + .PremiumIcon { + width: 1.5rem; + height: 1.5rem; + } + + // @optimization + @include while-transition() { + .Menu .bubble { + transition: none !important; + } + } + + .SearchInput { + transition: opacity var(--layer-transition); + + &--hidden { + opacity: 0; + pointer-events: none; + } + } +} + +.profileWrapper { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +} diff --git a/src/components/left/main/UluLeftMainHeader.tsx b/src/components/left/main/UluLeftMainHeader.tsx new file mode 100644 index 0000000000..1132f370ae --- /dev/null +++ b/src/components/left/main/UluLeftMainHeader.tsx @@ -0,0 +1,356 @@ +import type { FC } from '../../../lib/teact/teact'; +import React, { + memo, useEffect, useMemo, useRef, +} from '../../../lib/teact/teact'; +import { getActions, withGlobal } from '../../../global'; + +import type { GlobalState } from '../../../global/types'; +import type { ISettings } from '../../../types'; +import { LeftColumnContent, SettingsScreens } from '../../../types'; + +import { + APP_NAME, + DEBUG, + IS_BETA, + IS_STORIES_ENABLED, +} from '../../../config'; +import { + selectCanSetPasscode, + selectCurrentMessageList, + selectIsCurrentUserPremium, + selectTabState, + selectTheme, +} from '../../../global/selectors'; +import buildClassName from '../../../util/buildClassName'; +import captureEscKeyListener from '../../../util/captureEscKeyListener'; +import { formatDateToString } from '../../../util/dateFormat'; +import { IS_APP, IS_ELECTRON, IS_MAC_OS } from '../../../util/windowEnvironment'; + +import useAppLayout from '../../../hooks/useAppLayout'; +import useConnectionStatus from '../../../hooks/useConnectionStatus'; +import useElectronDrag from '../../../hooks/useElectronDrag'; +import useFlag from '../../../hooks/useFlag'; +import { useFullscreenStatus } from '../../../hooks/useFullscreen'; +import { useHotkeys } from '../../../hooks/useHotkeys'; +import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; +import useLeftHeaderButtonRtlForumTransition from './hooks/useLeftHeaderButtonRtlForumTransition'; + +import PickerSelectedItem from '../../common/PickerSelectedItem'; +import StoryToggler from '../../story/StoryToggler'; +import Button from '../../ui/Button'; +import DropdownMenu from '../../ui/DropdownMenu'; +import SearchInput from '../../ui/SearchInput'; +import ShowTransition from '../../ui/ShowTransition'; +import UluSearchButton from '../../ui/UluSearchButton'; +import ConnectionStatusOverlay from '../ConnectionStatusOverlay'; +import LeftSideMenuItems from './LeftSideMenuItems'; +import StatusButton from './StatusButton'; +import UluHeaderProfile from './UluHeaderProfile'; + +import styles from './UluLeftMainHeader.module.scss'; + +type OwnProps = { + shouldHideSearch?: boolean; + content: LeftColumnContent; + contactsFilter: string; + isClosingSearch?: boolean; + onSearchQuery: (query: string) => void; + onSelectSettings: NoneToVoidFunction; + onSelectContacts: NoneToVoidFunction; + onSelectArchived: NoneToVoidFunction; + onReset: NoneToVoidFunction; +}; + +type StateProps = + { + searchQuery?: string; + isLoading: boolean; + globalSearchChatId?: string; + searchDate?: number; + theme: ISettings['theme']; + isMessageListOpen: boolean; + isCurrentUserPremium?: boolean; + isConnectionStatusMinimized: ISettings['isConnectionStatusMinimized']; + areChatsLoaded?: boolean; + hasPasscode?: boolean; + canSetPasscode?: boolean; + } + & Pick; + +const CLEAR_DATE_SEARCH_PARAM = { date: undefined }; +const CLEAR_CHAT_SEARCH_PARAM = { id: undefined }; + +const LeftMainHeader: FC = ({ + shouldHideSearch, + content, + contactsFilter, + isClosingSearch, + searchQuery, + isLoading, + isCurrentUserPremium, + globalSearchChatId, + searchDate, + theme, + connectionState, + isSyncing, + isFetchingDifference, + isMessageListOpen, + isConnectionStatusMinimized, + areChatsLoaded, + hasPasscode, + canSetPasscode, + onSearchQuery, + onSelectSettings, + onSelectContacts, + onSelectArchived, + onReset, +}) => { + const { + setGlobalSearchDate, + setSettingOption, + setGlobalSearchChatId, + lockScreen, + requestNextSettingsScreen, + } = getActions(); + + const lang = useLang(); + const { isMobile } = useAppLayout(); + + const [isBotMenuOpen, markBotMenuOpen, unmarkBotMenuOpen] = useFlag(); + + const hasMenu = content === LeftColumnContent.ChatList; + const selectedSearchDate = useMemo(() => { + return searchDate + ? formatDateToString(new Date(searchDate * 1000)) + : undefined; + }, [searchDate]); + + const { connectionStatus, connectionStatusText, connectionStatusPosition } = useConnectionStatus( + lang, + connectionState, + isSyncing || isFetchingDifference, + isMessageListOpen, + isConnectionStatusMinimized, + !areChatsLoaded, + ); + + const handleLockScreenHotkey = useLastCallback((e: KeyboardEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (hasPasscode) { + lockScreen(); + } else { + requestNextSettingsScreen({ screen: SettingsScreens.PasscodeDisabled }); + } + }); + + useHotkeys(canSetPasscode ? { + 'Ctrl+Shift+L': handleLockScreenHotkey, + 'Alt+Shift+L': handleLockScreenHotkey, + 'Meta+Shift+L': handleLockScreenHotkey, + ...(IS_APP && { 'Mod+L': handleLockScreenHotkey }), + } : undefined); + + const MainButton: FC<{ onTrigger: () => void }> = useMemo(() => { + return ({ onTrigger }) => ( + onReset()} + /> + ); + }, [hasMenu, onReset]); + + const handleSearchFocus = useLastCallback(() => { + if (!searchQuery) { + onSearchQuery(''); + } + }); + + const toggleConnectionStatus = useLastCallback(() => { + setSettingOption({ isConnectionStatusMinimized: !isConnectionStatusMinimized }); + }); + + const handleLockScreen = useLastCallback(() => { + lockScreen(); + }); + + const isSearchFocused = ( + Boolean(globalSearchChatId) + || content === LeftColumnContent.GlobalSearch + || content === LeftColumnContent.Contacts + ); + + useEffect(() => (isSearchFocused ? captureEscKeyListener(() => onReset()) : undefined), [isSearchFocused, onReset]); + + const searchInputPlaceholder = content === LeftColumnContent.Contacts + ? lang('SearchFriends') + : lang('Search'); + + const versionString = IS_BETA ? `${APP_VERSION} Beta (${APP_REVISION})` : (DEBUG ? APP_REVISION : APP_VERSION); + + const isFullscreen = useFullscreenStatus(); + + // Disable dropdown menu RTL animation for resize + const { + shouldDisableDropdownMenuTransitionRef, + handleDropdownMenuTransitionEnd, + } = useLeftHeaderButtonRtlForumTransition(shouldHideSearch); + + // eslint-disable-next-line no-null/no-null + const headerRef = useRef(null); + useElectronDrag(headerRef); + + const searchContent = useMemo(() => { + return ( + <> + {selectedSearchDate && ( + + )} + {globalSearchChatId && ( + + )} + + ); + }, [globalSearchChatId, selectedSearchDate]); + + return ( +
+
+ {lang.isRtl &&
} + { isSearchFocused ? ( + <> + + + {searchContent} + {IS_STORIES_ENABLED && ( + + )} + + {isCurrentUserPremium && } + + ) : ( +
+ + + + +
+ ) } + {hasPasscode && ( + + )} + + + +
+
+ ); +}; + +export default memo(withGlobal( + (global): StateProps => { + const tabState = selectTabState(global); + const { + query: searchQuery, fetchingStatus, chatId, date, + } = tabState.globalSearch; + const { + connectionState, isSyncing, isFetchingDifference, + } = global; + const { isConnectionStatusMinimized } = global.settings.byKey; + + return { + searchQuery, + isLoading: fetchingStatus ? Boolean(fetchingStatus.chats || fetchingStatus.messages) : false, + globalSearchChatId: chatId, + searchDate: date, + theme: selectTheme(global), + connectionState, + isSyncing, + isFetchingDifference, + isMessageListOpen: Boolean(selectCurrentMessageList(global)), + isConnectionStatusMinimized, + isCurrentUserPremium: selectIsCurrentUserPremium(global), + areChatsLoaded: Boolean(global.chats.listIds.active), + hasPasscode: Boolean(global.passcode.hasPasscode), + canSetPasscode: selectCanSetPasscode(global), + }; + }, +)(LeftMainHeader)); diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index 3858251b51..2bf59d0971 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -69,7 +69,7 @@ import RatePhoneCallModal from '../calls/phone/RatePhoneCallModal.async'; import CustomEmojiSetsModal from '../common/CustomEmojiSetsModal.async'; import StickerSetModal from '../common/StickerSetModal.async'; import UnreadCount from '../common/UnreadCounter'; -import LeftColumn from '../left/LeftColumn'; +import UluLeftColumn from '../left/UluLeftColumn'; import MediaViewer from '../mediaViewer/MediaViewer.async'; import AudioPlayer from '../middle/AudioPlayer'; import ReactionPicker from '../middle/message/ReactionPicker.async'; @@ -527,7 +527,7 @@ const Main: FC = ({ return (
- + From f7d5a5a3cddb7466e76e5a495730bbf50a0aa359 Mon Sep 17 00:00:00 2001 From: evm97 Date: Wed, 18 Oct 2023 10:20:06 +0300 Subject: [PATCH 5/8] fix: [TG-156] fix no avatar case --- src/components/left/main/UluHeaderProfile.module.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/left/main/UluHeaderProfile.module.scss b/src/components/left/main/UluHeaderProfile.module.scss index f605d3b25f..b3540ed997 100644 --- a/src/components/left/main/UluHeaderProfile.module.scss +++ b/src/components/left/main/UluHeaderProfile.module.scss @@ -18,11 +18,18 @@ .photoWrapper { height: 1.5rem; width: 1.5rem; + border-radius: 0.5rem; :global(.avatar-media) { border-radius: 0.5rem; vertical-align: inherit; } + + :global(.no-photo) { + border-radius: inherit; + font-size: 0.6875rem; + white-space: nowrap; + } } .userName { From db0f7468f87b9def33d3fbfdce416084ab5683fa Mon Sep 17 00:00:00 2001 From: evm97 Date: Wed, 18 Oct 2023 11:31:12 +0300 Subject: [PATCH 6/8] fix: [TG-156] fix padding --- src/components/left/main/UluHeaderProfile.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/left/main/UluHeaderProfile.module.scss b/src/components/left/main/UluHeaderProfile.module.scss index b3540ed997..729fb3b435 100644 --- a/src/components/left/main/UluHeaderProfile.module.scss +++ b/src/components/left/main/UluHeaderProfile.module.scss @@ -2,7 +2,7 @@ display: flex; align-items: center; gap: 0.5rem; - padding: 0.75rem; + padding: 0.375rem 0.75rem 0.375rem 0.375rem; cursor: pointer; border-radius: var(--border-radius-default-tiny); From 176b89afe4b0109a2cbdbf0ae3f967a7e46a7c0c Mon Sep 17 00:00:00 2001 From: evm97 Date: Wed, 18 Oct 2023 11:46:02 +0300 Subject: [PATCH 7/8] feat: [TG-156] add cmd + k search open/close --- .../left/main/UluLeftMainHeader.tsx | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/components/left/main/UluLeftMainHeader.tsx b/src/components/left/main/UluLeftMainHeader.tsx index 1132f370ae..21f5ec4f27 100644 --- a/src/components/left/main/UluLeftMainHeader.tsx +++ b/src/components/left/main/UluLeftMainHeader.tsx @@ -152,6 +152,32 @@ const LeftMainHeader: FC = ({ ...(IS_APP && { 'Mod+L': handleLockScreenHotkey }), } : undefined); + const handleSearchFocus = useLastCallback(() => { + if (!searchQuery) { + onSearchQuery(''); + } + }); + + // Cmd+K to open search + useEffect(() => { + function handleKeyDown(e: KeyboardEvent) { + if (((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && e.key === 'k') { + if (hasMenu) { + handleSearchFocus(); + return; + } + + onReset(); + } + } + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [hasMenu, onReset]); + const MainButton: FC<{ onTrigger: () => void }> = useMemo(() => { return ({ onTrigger }) => ( = ({ ); }, [hasMenu, onReset]); - const handleSearchFocus = useLastCallback(() => { - if (!searchQuery) { - onSearchQuery(''); - } - }); - const toggleConnectionStatus = useLastCallback(() => { setSettingOption({ isConnectionStatusMinimized: !isConnectionStatusMinimized }); }); From a02adde5fec924f019c54b48b6e151571d899408 Mon Sep 17 00:00:00 2001 From: evm97 Date: Wed, 18 Oct 2023 14:02:06 +0300 Subject: [PATCH 8/8] refactor: [TG-156] merge ulu components into upstream components --- src/components/left/LeftColumn.scss | 2 +- src/components/left/UluLeftColumn.scss | 69 --- src/components/left/UluLeftColumn.tsx | 578 ------------------ src/components/left/main/LeftMain.scss | 1 + src/components/left/main/LeftMain.tsx | 1 - ...module.scss => LeftMainHeader.module.scss} | 0 src/components/left/main/LeftMainHeader.scss | 140 ----- src/components/left/main/LeftMainHeader.tsx | 170 +++--- src/components/left/main/UluLeftMain.scss | 82 --- src/components/left/main/UluLeftMain.tsx | 250 -------- .../left/main/UluLeftMainHeader.tsx | 376 ------------ src/components/main/Main.tsx | 4 +- 12 files changed, 101 insertions(+), 1572 deletions(-) delete mode 100644 src/components/left/UluLeftColumn.scss delete mode 100644 src/components/left/UluLeftColumn.tsx rename src/components/left/main/{UluLeftMainHeader.module.scss => LeftMainHeader.module.scss} (100%) delete mode 100644 src/components/left/main/LeftMainHeader.scss delete mode 100644 src/components/left/main/UluLeftMain.scss delete mode 100644 src/components/left/main/UluLeftMain.tsx delete mode 100644 src/components/left/main/UluLeftMainHeader.tsx diff --git a/src/components/left/LeftColumn.scss b/src/components/left/LeftColumn.scss index 53078c0aca..bb438764bd 100644 --- a/src/components/left/LeftColumn.scss +++ b/src/components/left/LeftColumn.scss @@ -4,7 +4,7 @@ .left-header { height: var(--header-height); - padding: 0.375rem 0.8125rem 0.5rem 0.8125rem; + padding: 0.5rem; display: flex; align-items: center; flex-shrink: 0; diff --git a/src/components/left/UluLeftColumn.scss b/src/components/left/UluLeftColumn.scss deleted file mode 100644 index bb438764bd..0000000000 --- a/src/components/left/UluLeftColumn.scss +++ /dev/null @@ -1,69 +0,0 @@ -#NewChat { - height: 100%; -} - -.left-header { - height: var(--header-height); - padding: 0.5rem; - display: flex; - align-items: center; - flex-shrink: 0; - background-color: var(--color-background); - - h3 { - margin-bottom: 0; - font-size: 1.25rem; - font-weight: 500; - margin-left: 1.375rem; - margin-right: auto; - user-select: none; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - } - - .SearchInput { - margin-left: 0.8125rem; - max-width: calc(100% - 3.25rem); - - @media (max-width: 600px) { - max-width: calc(100% - 3rem); - } - } - - @media (max-width: 600px) { - padding: 0.5rem; - } - - .Button.smaller { - width: 2.5rem; - height: 2.5rem; - - + .DropdownMenu { - margin-left: 0.25rem; - } - } - - body.is-electron.is-macos & { - -webkit-app-region: drag; - - .SearchInput { - -webkit-app-region: no-drag; - } - } - - body.is-electron.is-macos #Main:not(.is-fullscreen) &:not(#TopicListHeader) { - justify-content: space-between; - padding: 0.5rem 0.5rem 0.5rem 4.5rem; - - .SearchInput { - margin-left: 0.5rem; - max-width: calc(100% - 2.75rem); - } - - .Menu.main-menu .bubble { - --offset-y: 100%; - --offset-x: -4.125rem; - } - } -} diff --git a/src/components/left/UluLeftColumn.tsx b/src/components/left/UluLeftColumn.tsx deleted file mode 100644 index 0a4ef597b2..0000000000 --- a/src/components/left/UluLeftColumn.tsx +++ /dev/null @@ -1,578 +0,0 @@ -import type { RefObject } from 'react'; -import React, { - memo, useEffect, useState, -} from '../../lib/teact/teact'; -import { getActions, withGlobal } from '../../global'; - -import type { GlobalState } from '../../global/types'; -import type { FoldersActions } from '../../hooks/reducers/useFoldersReducer'; -import type { ReducerAction } from '../../hooks/useReducer'; -import { LeftColumnContent, SettingsScreens } from '../../types'; - -import { selectCurrentChat, selectIsForumPanelOpen, selectTabState } from '../../global/selectors'; -import captureEscKeyListener from '../../util/captureEscKeyListener'; -import { IS_APP, IS_MAC_OS, LAYERS_ANIMATION_NAME } from '../../util/windowEnvironment'; - -import useFoldersReducer from '../../hooks/reducers/useFoldersReducer'; -import { useHotkeys } from '../../hooks/useHotkeys'; -import useLastCallback from '../../hooks/useLastCallback'; -import useSyncEffect from '../../hooks/useSyncEffect'; - -import Transition from '../ui/Transition'; -import ArchivedChats from './ArchivedChats.async'; -import UluLeftMain from './main/UluLeftMain'; -import NewChat from './newChat/NewChat.async'; -import Settings from './settings/Settings.async'; - -import './UluLeftColumn.scss'; - -interface OwnProps { - ref: RefObject; -} - -type StateProps = { - searchQuery?: string; - searchDate?: number; - isFirstChatFolderActive: boolean; - shouldSkipHistoryAnimations?: boolean; - currentUserId?: string; - hasPasscode?: boolean; - nextSettingsScreen?: SettingsScreens; - nextFoldersAction?: ReducerAction; - isChatOpen: boolean; - isAppUpdateAvailable?: boolean; - isElectronUpdateAvailable?: boolean; - isForumPanelOpen?: boolean; - forumPanelChatId?: string; - isClosingSearch?: boolean; - archiveSettings: GlobalState['archiveSettings']; - isArchivedStoryRibbonShown?: boolean; -}; - -enum ContentType { - Main, - // eslint-disable-next-line @typescript-eslint/no-shadow - Settings, - Archived, - // eslint-disable-next-line no-shadow - NewGroup, - // eslint-disable-next-line no-shadow - NewChannel, -} - -const RENDER_COUNT = Object.keys(ContentType).length / 2; -const RESET_TRANSITION_DELAY_MS = 250; - -function LeftColumn({ - ref, - searchQuery, - searchDate, - isFirstChatFolderActive, - shouldSkipHistoryAnimations, - currentUserId, - hasPasscode, - nextSettingsScreen, - nextFoldersAction, - isChatOpen, - isAppUpdateAvailable, - isElectronUpdateAvailable, - isForumPanelOpen, - forumPanelChatId, - isClosingSearch, - archiveSettings, - isArchivedStoryRibbonShown, -}: OwnProps & StateProps) { - const { - setGlobalSearchQuery, - setGlobalSearchClosing, - setGlobalSearchChatId, - resetChatCreation, - setGlobalSearchDate, - loadPasswordInfo, - clearTwoFaError, - openChat, - requestNextSettingsScreen, - } = getActions(); - - const [content, setContent] = useState(LeftColumnContent.ChatList); - const [settingsScreen, setSettingsScreen] = useState(SettingsScreens.Main); - const [contactsFilter, setContactsFilter] = useState(''); - const [foldersState, foldersDispatch] = useFoldersReducer(); - - // Used to reset child components in background. - const [lastResetTime, setLastResetTime] = useState(0); - - let contentType: ContentType = ContentType.Main; - switch (content) { - case LeftColumnContent.Archived: - contentType = ContentType.Archived; - break; - case LeftColumnContent.Settings: - contentType = ContentType.Settings; - break; - case LeftColumnContent.NewChannelStep1: - case LeftColumnContent.NewChannelStep2: - contentType = ContentType.NewChannel; - break; - case LeftColumnContent.NewGroupStep1: - case LeftColumnContent.NewGroupStep2: - contentType = ContentType.NewGroup; - break; - } - - const handleReset = useLastCallback((forceReturnToChatList?: true | Event) => { - function fullReset() { - setContent(LeftColumnContent.ChatList); - setSettingsScreen(SettingsScreens.Main); - setContactsFilter(''); - setGlobalSearchClosing({ isClosing: true }); - resetChatCreation(); - setTimeout(() => { - setGlobalSearchQuery({ query: '' }); - setGlobalSearchDate({ date: undefined }); - setGlobalSearchChatId({ id: undefined }); - setGlobalSearchClosing({ isClosing: false }); - setLastResetTime(Date.now()); - }, RESET_TRANSITION_DELAY_MS); - } - - if (forceReturnToChatList === true) { - fullReset(); - return; - } - - if (content === LeftColumnContent.NewGroupStep2) { - setContent(LeftColumnContent.NewGroupStep1); - return; - } - - if (content === LeftColumnContent.NewChannelStep2) { - setContent(LeftColumnContent.NewChannelStep1); - return; - } - - if (content === LeftColumnContent.NewGroupStep1) { - const pickerSearchInput = document.getElementById('new-group-picker-search'); - if (pickerSearchInput) { - pickerSearchInput.blur(); - } - } - - if (content === LeftColumnContent.Settings) { - switch (settingsScreen) { - case SettingsScreens.EditProfile: - case SettingsScreens.Folders: - case SettingsScreens.General: - case SettingsScreens.Notifications: - case SettingsScreens.DataStorage: - case SettingsScreens.Privacy: - case SettingsScreens.Performance: - case SettingsScreens.ActiveSessions: - case SettingsScreens.Language: - case SettingsScreens.Stickers: - case SettingsScreens.Experimental: - setSettingsScreen(SettingsScreens.Main); - return; - - case SettingsScreens.GeneralChatBackground: - setSettingsScreen(SettingsScreens.General); - return; - case SettingsScreens.GeneralChatBackgroundColor: - setSettingsScreen(SettingsScreens.GeneralChatBackground); - return; - - case SettingsScreens.PrivacyPhoneNumber: - case SettingsScreens.PrivacyAddByPhone: - case SettingsScreens.PrivacyLastSeen: - case SettingsScreens.PrivacyProfilePhoto: - case SettingsScreens.PrivacyBio: - case SettingsScreens.PrivacyPhoneCall: - case SettingsScreens.PrivacyPhoneP2P: - case SettingsScreens.PrivacyForwarding: - case SettingsScreens.PrivacyGroupChats: - case SettingsScreens.PrivacyVoiceMessages: - case SettingsScreens.PrivacyBlockedUsers: - case SettingsScreens.ActiveWebsites: - case SettingsScreens.TwoFaDisabled: - case SettingsScreens.TwoFaEnabled: - case SettingsScreens.TwoFaCongratulations: - case SettingsScreens.PasscodeDisabled: - case SettingsScreens.PasscodeEnabled: - case SettingsScreens.PasscodeCongratulations: - setSettingsScreen(SettingsScreens.Privacy); - return; - - case SettingsScreens.PasscodeNewPasscode: - setSettingsScreen(hasPasscode ? SettingsScreens.PasscodeEnabled : SettingsScreens.PasscodeDisabled); - return; - - case SettingsScreens.PasscodeChangePasscodeCurrent: - case SettingsScreens.PasscodeTurnOff: - setSettingsScreen(SettingsScreens.PasscodeEnabled); - return; - - case SettingsScreens.PasscodeNewPasscodeConfirm: - setSettingsScreen(SettingsScreens.PasscodeNewPasscode); - return; - - case SettingsScreens.PasscodeChangePasscodeNew: - setSettingsScreen(SettingsScreens.PasscodeChangePasscodeCurrent); - return; - - case SettingsScreens.PasscodeChangePasscodeConfirm: - setSettingsScreen(SettingsScreens.PasscodeChangePasscodeNew); - return; - - case SettingsScreens.PrivacyPhoneNumberAllowedContacts: - case SettingsScreens.PrivacyPhoneNumberDeniedContacts: - setSettingsScreen(SettingsScreens.PrivacyPhoneNumber); - return; - case SettingsScreens.PrivacyLastSeenAllowedContacts: - case SettingsScreens.PrivacyLastSeenDeniedContacts: - setSettingsScreen(SettingsScreens.PrivacyLastSeen); - return; - case SettingsScreens.PrivacyProfilePhotoAllowedContacts: - case SettingsScreens.PrivacyProfilePhotoDeniedContacts: - setSettingsScreen(SettingsScreens.PrivacyProfilePhoto); - return; - case SettingsScreens.PrivacyBioAllowedContacts: - case SettingsScreens.PrivacyBioDeniedContacts: - setSettingsScreen(SettingsScreens.PrivacyBio); - return; - case SettingsScreens.PrivacyPhoneCallAllowedContacts: - case SettingsScreens.PrivacyPhoneCallDeniedContacts: - setSettingsScreen(SettingsScreens.PrivacyPhoneCall); - return; - case SettingsScreens.PrivacyPhoneP2PAllowedContacts: - case SettingsScreens.PrivacyPhoneP2PDeniedContacts: - setSettingsScreen(SettingsScreens.PrivacyPhoneP2P); - return; - case SettingsScreens.PrivacyForwardingAllowedContacts: - case SettingsScreens.PrivacyForwardingDeniedContacts: - setSettingsScreen(SettingsScreens.PrivacyForwarding); - return; - case SettingsScreens.PrivacyVoiceMessagesAllowedContacts: - case SettingsScreens.PrivacyVoiceMessagesDeniedContacts: - setSettingsScreen(SettingsScreens.PrivacyVoiceMessages); - return; - case SettingsScreens.PrivacyGroupChatsAllowedContacts: - case SettingsScreens.PrivacyGroupChatsDeniedContacts: - setSettingsScreen(SettingsScreens.PrivacyGroupChats); - return; - case SettingsScreens.TwoFaNewPassword: - setSettingsScreen(SettingsScreens.TwoFaDisabled); - return; - case SettingsScreens.TwoFaNewPasswordConfirm: - setSettingsScreen(SettingsScreens.TwoFaNewPassword); - return; - case SettingsScreens.TwoFaNewPasswordHint: - setSettingsScreen(SettingsScreens.TwoFaNewPasswordConfirm); - return; - case SettingsScreens.TwoFaNewPasswordEmail: - setSettingsScreen(SettingsScreens.TwoFaNewPasswordHint); - return; - case SettingsScreens.TwoFaNewPasswordEmailCode: - setSettingsScreen(SettingsScreens.TwoFaNewPasswordEmail); - return; - case SettingsScreens.TwoFaChangePasswordCurrent: - case SettingsScreens.TwoFaTurnOff: - case SettingsScreens.TwoFaRecoveryEmailCurrentPassword: - setSettingsScreen(SettingsScreens.TwoFaEnabled); - return; - case SettingsScreens.TwoFaChangePasswordNew: - setSettingsScreen(SettingsScreens.TwoFaChangePasswordCurrent); - return; - case SettingsScreens.TwoFaChangePasswordConfirm: - setSettingsScreen(SettingsScreens.TwoFaChangePasswordNew); - return; - case SettingsScreens.TwoFaChangePasswordHint: - setSettingsScreen(SettingsScreens.TwoFaChangePasswordConfirm); - return; - case SettingsScreens.TwoFaRecoveryEmail: - setSettingsScreen(SettingsScreens.TwoFaRecoveryEmailCurrentPassword); - return; - case SettingsScreens.TwoFaRecoveryEmailCode: - setSettingsScreen(SettingsScreens.TwoFaRecoveryEmail); - return; - - case SettingsScreens.FoldersCreateFolder: - case SettingsScreens.FoldersEditFolder: - setSettingsScreen(SettingsScreens.Folders); - return; - - case SettingsScreens.FoldersShare: - setSettingsScreen(SettingsScreens.FoldersEditFolder); - return; - - case SettingsScreens.FoldersIncludedChatsFromChatList: - case SettingsScreens.FoldersExcludedChatsFromChatList: - setSettingsScreen(SettingsScreens.FoldersEditFolderFromChatList); - return; - - case SettingsScreens.FoldersEditFolderFromChatList: - case SettingsScreens.FoldersEditFolderInvites: - setContent(LeftColumnContent.ChatList); - setSettingsScreen(SettingsScreens.Main); - return; - - case SettingsScreens.QuickReaction: - case SettingsScreens.CustomEmoji: - setSettingsScreen(SettingsScreens.Stickers); - return; - - case SettingsScreens.DoNotTranslate: - setSettingsScreen(SettingsScreens.Language); - return; - default: - break; - } - } - - if (content === LeftColumnContent.ChatList && isFirstChatFolderActive) { - setContent(LeftColumnContent.GlobalSearch); - - return; - } - - fullReset(); - }); - - const handleSearchQuery = useLastCallback((query: string) => { - if (content === LeftColumnContent.Contacts) { - setContactsFilter(query); - return; - } - - setContent(LeftColumnContent.GlobalSearch); - - if (query !== searchQuery) { - setGlobalSearchQuery({ query }); - } - }); - - const handleTopicSearch = useLastCallback(() => { - setContent(LeftColumnContent.GlobalSearch); - setGlobalSearchQuery({ query: '' }); - setGlobalSearchChatId({ id: forumPanelChatId }); - }); - - useEffect( - () => { - const isArchived = content === LeftColumnContent.Archived; - const isChatList = content === LeftColumnContent.ChatList; - const noChatOrForumOpen = !isChatOpen && !isForumPanelOpen; - // We listen for escape key only in these cases: - // 1. When we are in archived chats and no chat or forum is open. - // 2. When we are in any other screen except chat list and archived chat list. - // 3. When we are in chat list and first chat folder is active and no chat or forum is open. - if ((isArchived && noChatOrForumOpen) || (!isChatList && !isArchived) - || (isFirstChatFolderActive && noChatOrForumOpen)) { - return captureEscKeyListener(() => { - handleReset(); - }); - } else { - return undefined; - } - }, - [isFirstChatFolderActive, content, handleReset, isChatOpen, isForumPanelOpen], - ); - - const handleHotkeySearch = useLastCallback((e: KeyboardEvent) => { - if (content === LeftColumnContent.GlobalSearch) { - return; - } - - e.preventDefault(); - setContent(LeftColumnContent.GlobalSearch); - }); - - const handleHotkeySavedMessages = useLastCallback((e: KeyboardEvent) => { - e.preventDefault(); - openChat({ id: currentUserId, shouldReplaceHistory: true }); - }); - - const handleArchivedChats = useLastCallback((e: KeyboardEvent) => { - e.preventDefault(); - setContent(LeftColumnContent.Archived); - }); - - const handleHotkeySettings = useLastCallback((e: KeyboardEvent) => { - e.preventDefault(); - setContent(LeftColumnContent.Settings); - }); - - useHotkeys({ - 'Mod+Shift+F': handleHotkeySearch, - 'Mod+Shift+S': handleHotkeySavedMessages, - ...(IS_APP && { - 'Mod+0': handleHotkeySavedMessages, - 'Mod+9': handleArchivedChats, - }), - ...(IS_MAC_OS && IS_APP && { 'Mod+,': handleHotkeySettings }), - }); - - useEffect(() => { - clearTwoFaError(); - - if (settingsScreen === SettingsScreens.Privacy) { - loadPasswordInfo(); - } - }, [clearTwoFaError, loadPasswordInfo, settingsScreen]); - - useSyncEffect(() => { - if (nextSettingsScreen !== undefined) { - setContent(LeftColumnContent.Settings); - setSettingsScreen(nextSettingsScreen); - requestNextSettingsScreen({ screen: undefined }); - } - - if (nextFoldersAction) { - foldersDispatch(nextFoldersAction); - } - }, [foldersDispatch, nextFoldersAction, nextSettingsScreen, requestNextSettingsScreen]); - - const handleSettingsScreenSelect = useLastCallback((screen: SettingsScreens) => { - setContent(LeftColumnContent.Settings); - setSettingsScreen(screen); - }); - - function renderContent(isActive: boolean) { - switch (contentType) { - case ContentType.Archived: - return ( - - ); - case ContentType.Settings: - return ( - - ); - case ContentType.NewChannel: - return ( - - ); - case ContentType.NewGroup: - return ( - - ); - default: - return ( - - ); - } - } - - return ( - - {renderContent} - - ); -} - -export default memo(withGlobal( - (global): StateProps => { - const tabState = selectTabState(global); - const { - globalSearch: { - query, - date, - }, - shouldSkipHistoryAnimations, - activeChatFolder, - nextSettingsScreen, - nextFoldersAction, - storyViewer: { - isArchivedRibbonShown, - }, - } = tabState; - const { - currentUserId, - passcode: { - hasPasscode, - }, - isAppUpdateAvailable, - isElectronUpdateAvailable, - archiveSettings, - } = global; - - const currentChat = selectCurrentChat(global); - const isChatOpen = Boolean(currentChat?.id); - const isForumPanelOpen = selectIsForumPanelOpen(global); - const forumPanelChatId = tabState.forumPanelChatId; - - return { - searchQuery: query, - searchDate: date, - isFirstChatFolderActive: activeChatFolder === 0, - shouldSkipHistoryAnimations, - currentUserId, - hasPasscode, - nextSettingsScreen, - nextFoldersAction, - isChatOpen, - isAppUpdateAvailable, - isElectronUpdateAvailable, - isForumPanelOpen, - forumPanelChatId, - isClosingSearch: tabState.globalSearch.isClosing, - archiveSettings, - isArchivedStoryRibbonShown: isArchivedRibbonShown, - }; - }, -)(LeftColumn)); diff --git a/src/components/left/main/LeftMain.scss b/src/components/left/main/LeftMain.scss index 2e2962fc2c..bacef1e7f3 100644 --- a/src/components/left/main/LeftMain.scss +++ b/src/components/left/main/LeftMain.scss @@ -3,6 +3,7 @@ position: relative; display: flex; flex-direction: column; + // gap: 1.25rem; overflow: hidden; background: var(--color-background); diff --git a/src/components/left/main/LeftMain.tsx b/src/components/left/main/LeftMain.tsx index 353d5ff586..609dd03c29 100644 --- a/src/components/left/main/LeftMain.tsx +++ b/src/components/left/main/LeftMain.tsx @@ -179,7 +179,6 @@ const LeftMain: FC = ({ onSelectContacts={handleSelectContacts} onSelectArchived={handleSelectArchived} onReset={onReset} - shouldSkipTransition={shouldSkipTransition} isClosingSearch={isClosingSearch} /> void; onSelectSettings: NoneToVoidFunction; onSelectContacts: NoneToVoidFunction; @@ -88,7 +89,6 @@ const LeftMainHeader: FC = ({ searchQuery, isLoading, isCurrentUserPremium, - shouldSkipTransition, globalSearchChatId, searchDate, theme, @@ -152,34 +152,41 @@ const LeftMainHeader: FC = ({ ...(IS_APP && { 'Mod+L': handleLockScreenHotkey }), } : undefined); - const MainButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => { - return ({ onTrigger, isOpen }) => ( - - ); - }, [hasMenu, isMobile, lang, onReset, shouldSkipTransition]); - const handleSearchFocus = useLastCallback(() => { if (!searchQuery) { onSearchQuery(''); } }); + // Cmd+K to open search + useEffect(() => { + function handleKeyDown(e: KeyboardEvent) { + if (((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && e.key === 'k') { + if (hasMenu) { + handleSearchFocus(); + return; + } + + onReset(); + } + } + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [hasMenu, onReset]); + + const MainButton: FC<{ onTrigger: () => void }> = useMemo(() => { + return ({ onTrigger }) => ( + onReset()} + /> + ); + }, [hasMenu, onReset]); + const toggleConnectionStatus = useLastCallback(() => { setSettingOption({ isConnectionStatusMinimized: !isConnectionStatusMinimized }); }); @@ -244,54 +251,71 @@ const LeftMainHeader: FC = ({
{lang.isRtl &&
} - - - - - {searchContent} - {IS_STORIES_ENABLED && ( - - )} - - {isCurrentUserPremium && } + { isSearchFocused ? ( + <> + + + {searchContent} + {IS_STORIES_ENABLED && ( + + )} + + {isCurrentUserPremium && } + + ) : ( +
+ + + + +
+ ) } {hasPasscode && ( - )} - {shouldRenderForumPanel && ( - - )} - -
- ); -}; - -export default memo(LeftMain); diff --git a/src/components/left/main/UluLeftMainHeader.tsx b/src/components/left/main/UluLeftMainHeader.tsx deleted file mode 100644 index 21f5ec4f27..0000000000 --- a/src/components/left/main/UluLeftMainHeader.tsx +++ /dev/null @@ -1,376 +0,0 @@ -import type { FC } from '../../../lib/teact/teact'; -import React, { - memo, useEffect, useMemo, useRef, -} from '../../../lib/teact/teact'; -import { getActions, withGlobal } from '../../../global'; - -import type { GlobalState } from '../../../global/types'; -import type { ISettings } from '../../../types'; -import { LeftColumnContent, SettingsScreens } from '../../../types'; - -import { - APP_NAME, - DEBUG, - IS_BETA, - IS_STORIES_ENABLED, -} from '../../../config'; -import { - selectCanSetPasscode, - selectCurrentMessageList, - selectIsCurrentUserPremium, - selectTabState, - selectTheme, -} from '../../../global/selectors'; -import buildClassName from '../../../util/buildClassName'; -import captureEscKeyListener from '../../../util/captureEscKeyListener'; -import { formatDateToString } from '../../../util/dateFormat'; -import { IS_APP, IS_ELECTRON, IS_MAC_OS } from '../../../util/windowEnvironment'; - -import useAppLayout from '../../../hooks/useAppLayout'; -import useConnectionStatus from '../../../hooks/useConnectionStatus'; -import useElectronDrag from '../../../hooks/useElectronDrag'; -import useFlag from '../../../hooks/useFlag'; -import { useFullscreenStatus } from '../../../hooks/useFullscreen'; -import { useHotkeys } from '../../../hooks/useHotkeys'; -import useLang from '../../../hooks/useLang'; -import useLastCallback from '../../../hooks/useLastCallback'; -import useLeftHeaderButtonRtlForumTransition from './hooks/useLeftHeaderButtonRtlForumTransition'; - -import PickerSelectedItem from '../../common/PickerSelectedItem'; -import StoryToggler from '../../story/StoryToggler'; -import Button from '../../ui/Button'; -import DropdownMenu from '../../ui/DropdownMenu'; -import SearchInput from '../../ui/SearchInput'; -import ShowTransition from '../../ui/ShowTransition'; -import UluSearchButton from '../../ui/UluSearchButton'; -import ConnectionStatusOverlay from '../ConnectionStatusOverlay'; -import LeftSideMenuItems from './LeftSideMenuItems'; -import StatusButton from './StatusButton'; -import UluHeaderProfile from './UluHeaderProfile'; - -import styles from './UluLeftMainHeader.module.scss'; - -type OwnProps = { - shouldHideSearch?: boolean; - content: LeftColumnContent; - contactsFilter: string; - isClosingSearch?: boolean; - onSearchQuery: (query: string) => void; - onSelectSettings: NoneToVoidFunction; - onSelectContacts: NoneToVoidFunction; - onSelectArchived: NoneToVoidFunction; - onReset: NoneToVoidFunction; -}; - -type StateProps = - { - searchQuery?: string; - isLoading: boolean; - globalSearchChatId?: string; - searchDate?: number; - theme: ISettings['theme']; - isMessageListOpen: boolean; - isCurrentUserPremium?: boolean; - isConnectionStatusMinimized: ISettings['isConnectionStatusMinimized']; - areChatsLoaded?: boolean; - hasPasscode?: boolean; - canSetPasscode?: boolean; - } - & Pick; - -const CLEAR_DATE_SEARCH_PARAM = { date: undefined }; -const CLEAR_CHAT_SEARCH_PARAM = { id: undefined }; - -const LeftMainHeader: FC = ({ - shouldHideSearch, - content, - contactsFilter, - isClosingSearch, - searchQuery, - isLoading, - isCurrentUserPremium, - globalSearchChatId, - searchDate, - theme, - connectionState, - isSyncing, - isFetchingDifference, - isMessageListOpen, - isConnectionStatusMinimized, - areChatsLoaded, - hasPasscode, - canSetPasscode, - onSearchQuery, - onSelectSettings, - onSelectContacts, - onSelectArchived, - onReset, -}) => { - const { - setGlobalSearchDate, - setSettingOption, - setGlobalSearchChatId, - lockScreen, - requestNextSettingsScreen, - } = getActions(); - - const lang = useLang(); - const { isMobile } = useAppLayout(); - - const [isBotMenuOpen, markBotMenuOpen, unmarkBotMenuOpen] = useFlag(); - - const hasMenu = content === LeftColumnContent.ChatList; - const selectedSearchDate = useMemo(() => { - return searchDate - ? formatDateToString(new Date(searchDate * 1000)) - : undefined; - }, [searchDate]); - - const { connectionStatus, connectionStatusText, connectionStatusPosition } = useConnectionStatus( - lang, - connectionState, - isSyncing || isFetchingDifference, - isMessageListOpen, - isConnectionStatusMinimized, - !areChatsLoaded, - ); - - const handleLockScreenHotkey = useLastCallback((e: KeyboardEvent) => { - e.preventDefault(); - e.stopPropagation(); - if (hasPasscode) { - lockScreen(); - } else { - requestNextSettingsScreen({ screen: SettingsScreens.PasscodeDisabled }); - } - }); - - useHotkeys(canSetPasscode ? { - 'Ctrl+Shift+L': handleLockScreenHotkey, - 'Alt+Shift+L': handleLockScreenHotkey, - 'Meta+Shift+L': handleLockScreenHotkey, - ...(IS_APP && { 'Mod+L': handleLockScreenHotkey }), - } : undefined); - - const handleSearchFocus = useLastCallback(() => { - if (!searchQuery) { - onSearchQuery(''); - } - }); - - // Cmd+K to open search - useEffect(() => { - function handleKeyDown(e: KeyboardEvent) { - if (((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && e.key === 'k') { - if (hasMenu) { - handleSearchFocus(); - return; - } - - onReset(); - } - } - - document.addEventListener('keydown', handleKeyDown); - - return () => { - document.removeEventListener('keydown', handleKeyDown); - }; - }, [hasMenu, onReset]); - - const MainButton: FC<{ onTrigger: () => void }> = useMemo(() => { - return ({ onTrigger }) => ( - onReset()} - /> - ); - }, [hasMenu, onReset]); - - const toggleConnectionStatus = useLastCallback(() => { - setSettingOption({ isConnectionStatusMinimized: !isConnectionStatusMinimized }); - }); - - const handleLockScreen = useLastCallback(() => { - lockScreen(); - }); - - const isSearchFocused = ( - Boolean(globalSearchChatId) - || content === LeftColumnContent.GlobalSearch - || content === LeftColumnContent.Contacts - ); - - useEffect(() => (isSearchFocused ? captureEscKeyListener(() => onReset()) : undefined), [isSearchFocused, onReset]); - - const searchInputPlaceholder = content === LeftColumnContent.Contacts - ? lang('SearchFriends') - : lang('Search'); - - const versionString = IS_BETA ? `${APP_VERSION} Beta (${APP_REVISION})` : (DEBUG ? APP_REVISION : APP_VERSION); - - const isFullscreen = useFullscreenStatus(); - - // Disable dropdown menu RTL animation for resize - const { - shouldDisableDropdownMenuTransitionRef, - handleDropdownMenuTransitionEnd, - } = useLeftHeaderButtonRtlForumTransition(shouldHideSearch); - - // eslint-disable-next-line no-null/no-null - const headerRef = useRef(null); - useElectronDrag(headerRef); - - const searchContent = useMemo(() => { - return ( - <> - {selectedSearchDate && ( - - )} - {globalSearchChatId && ( - - )} - - ); - }, [globalSearchChatId, selectedSearchDate]); - - return ( -
-
- {lang.isRtl &&
} - { isSearchFocused ? ( - <> - - - {searchContent} - {IS_STORIES_ENABLED && ( - - )} - - {isCurrentUserPremium && } - - ) : ( -
- - - - -
- ) } - {hasPasscode && ( - - )} - - - -
-
- ); -}; - -export default memo(withGlobal( - (global): StateProps => { - const tabState = selectTabState(global); - const { - query: searchQuery, fetchingStatus, chatId, date, - } = tabState.globalSearch; - const { - connectionState, isSyncing, isFetchingDifference, - } = global; - const { isConnectionStatusMinimized } = global.settings.byKey; - - return { - searchQuery, - isLoading: fetchingStatus ? Boolean(fetchingStatus.chats || fetchingStatus.messages) : false, - globalSearchChatId: chatId, - searchDate: date, - theme: selectTheme(global), - connectionState, - isSyncing, - isFetchingDifference, - isMessageListOpen: Boolean(selectCurrentMessageList(global)), - isConnectionStatusMinimized, - isCurrentUserPremium: selectIsCurrentUserPremium(global), - areChatsLoaded: Boolean(global.chats.listIds.active), - hasPasscode: Boolean(global.passcode.hasPasscode), - canSetPasscode: selectCanSetPasscode(global), - }; - }, -)(LeftMainHeader)); diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index 2bf59d0971..3858251b51 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -69,7 +69,7 @@ import RatePhoneCallModal from '../calls/phone/RatePhoneCallModal.async'; import CustomEmojiSetsModal from '../common/CustomEmojiSetsModal.async'; import StickerSetModal from '../common/StickerSetModal.async'; import UnreadCount from '../common/UnreadCounter'; -import UluLeftColumn from '../left/UluLeftColumn'; +import LeftColumn from '../left/LeftColumn'; import MediaViewer from '../mediaViewer/MediaViewer.async'; import AudioPlayer from '../middle/AudioPlayer'; import ReactionPicker from '../middle/message/ReactionPicker.async'; @@ -527,7 +527,7 @@ const Main: FC = ({ return (
- +