From 8ca73e273aaa1456d1c741e56a83b2096bce610d Mon Sep 17 00:00:00 2001 From: Alex Junior Date: Fri, 6 May 2022 22:06:08 -0300 Subject: [PATCH] Chore: Evaluate emoji picker - TypeScript (#4069) --- app/actions/customEmojis.ts | 2 +- app/containers/EmojiPicker/CustomEmoji.tsx | 1 - app/containers/EmojiPicker/EmojiCategory.tsx | 10 ++-- app/containers/EmojiPicker/TabBar.tsx | 24 ++++---- app/{ => containers/EmojiPicker}/emojis.ts | 2 +- app/containers/EmojiPicker/index.tsx | 58 ++++++++++---------- app/containers/MessageActions/Header.tsx | 6 +- app/containers/MessageBox/EmojiKeyboard.tsx | 5 +- app/containers/MessageBox/index.tsx | 8 +-- app/containers/message/index.tsx | 3 +- app/definitions/ICustomEmoji.ts | 11 ---- app/definitions/IEmoji.ts | 44 +++++++++------ app/definitions/IFrequentlyUsedEmoji.ts | 11 ---- app/definitions/index.ts | 3 +- app/lib/methods/getCustomEmojis.ts | 5 +- app/reducers/customEmojis.test.ts | 3 +- app/reducers/customEmojis.ts | 12 +--- app/views/MessagesView/index.tsx | 4 +- app/views/RoomView/ReactionPicker.tsx | 10 +--- app/views/RoomView/index.tsx | 9 ++- 20 files changed, 100 insertions(+), 131 deletions(-) rename app/{ => containers/EmojiPicker}/emojis.ts (99%) delete mode 100644 app/definitions/ICustomEmoji.ts delete mode 100644 app/definitions/IFrequentlyUsedEmoji.ts diff --git a/app/actions/customEmojis.ts b/app/actions/customEmojis.ts index 261fbd241..a870d21fb 100644 --- a/app/actions/customEmojis.ts +++ b/app/actions/customEmojis.ts @@ -1,7 +1,7 @@ import { Action } from 'redux'; -import { ICustomEmojis } from '../reducers/customEmojis'; import { SET_CUSTOM_EMOJIS } from './actionsTypes'; +import { ICustomEmojis } from '../definitions'; export interface ISetCustomEmojis extends Action { emojis: ICustomEmojis; diff --git a/app/containers/EmojiPicker/CustomEmoji.tsx b/app/containers/EmojiPicker/CustomEmoji.tsx index aeebbe21c..c96ee7ae5 100644 --- a/app/containers/EmojiPicker/CustomEmoji.tsx +++ b/app/containers/EmojiPicker/CustomEmoji.tsx @@ -8,7 +8,6 @@ const CustomEmoji = React.memo( { ); }; -class EmojiCategory extends React.Component> { - renderItem(emoji: any) { +class EmojiCategory extends React.Component { + renderItem(emoji: IEmoji) { const { baseUrl, onEmojiSelected } = this.props; return ( onEmojiSelected!(emoji)} + onPress={() => onEmojiSelected(emoji)} testID={`reaction-picker-${emoji && emoji.isCustom ? emoji.content : emoji}`}> - {renderEmoji(emoji, EMOJI_SIZE, baseUrl!)} + {renderEmoji(emoji, EMOJI_SIZE, baseUrl)} ); } @@ -51,7 +52,6 @@ class EmojiCategory extends React.Component> { const marginHorizontal = (width - numColumns * EMOJI_SIZE) / 2; return ( - // @ts-ignore void; + activeTab?: number; + tabs?: string[]; + tabEmojiStyle: StyleProp; theme: TSupportedThemes; } -export default class TabBar extends React.Component> { - shouldComponentUpdate(nextProps: any) { +export default class TabBar extends React.Component { + shouldComponentUpdate(nextProps: ITabBarProps) { const { activeTab, theme } = this.props; if (nextProps.activeTab !== activeTab) { return true; @@ -30,16 +30,20 @@ export default class TabBar extends React.Component> { return ( - {tabs!.map((tab, i) => ( + {tabs?.map((tab, i) => ( goToPage!(i)} + onPress={() => { + if (goToPage) { + goToPage(i); + } + }} style={styles.tab} testID={`reaction-picker-${tab}`}> {tab} {activeTab === i ? ( - + ) : ( )} diff --git a/app/emojis.ts b/app/containers/EmojiPicker/emojis.ts similarity index 99% rename from app/emojis.ts rename to app/containers/EmojiPicker/emojis.ts index 172d15adf..b5ed98f52 100644 --- a/app/emojis.ts +++ b/app/containers/EmojiPicker/emojis.ts @@ -1,4 +1,4 @@ -export const emojisByCategory: any = { +export const emojisByCategory = { people: [ 'grinning', 'grimacing', diff --git a/app/containers/EmojiPicker/index.tsx b/app/containers/EmojiPicker/index.tsx index 5884b12a1..08ef6a609 100644 --- a/app/containers/EmojiPicker/index.tsx +++ b/app/containers/EmojiPicker/index.tsx @@ -1,38 +1,34 @@ import React, { Component } from 'react'; -import { View } from 'react-native'; +import { StyleProp, TextStyle, View } from 'react-native'; import ScrollableTabView from 'react-native-scrollable-tab-view'; import { dequal } from 'dequal'; import { connect } from 'react-redux'; import orderBy from 'lodash/orderBy'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; +import { ImageStyle } from '@rocket.chat/react-native-fast-image'; import TabBar from './TabBar'; import EmojiCategory from './EmojiCategory'; import styles from './styles'; import categories from './categories'; import database from '../../lib/database'; -import { emojisByCategory } from '../../emojis'; +import { emojisByCategory } from './emojis'; import protectedFunction from '../../lib/methods/helpers/protectedFunction'; import shortnameToUnicode from '../../utils/shortnameToUnicode'; import log from '../../utils/log'; import { themes } from '../../lib/constants'; import { TSupportedThemes, withTheme } from '../../theme'; -import { IEmoji } from '../../definitions/IEmoji'; - -const scrollProps = { - keyboardShouldPersistTaps: 'always', - keyboardDismissMode: 'none' -}; +import { IEmoji, TGetCustomEmoji, IApplicationState, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definitions'; interface IEmojiPickerProps { - isMessageContainsOnlyEmoji: boolean; - getCustomEmoji?: Function; + isMessageContainsOnlyEmoji?: boolean; + getCustomEmoji?: TGetCustomEmoji; baseUrl: string; - customEmojis?: any; - style: object; + customEmojis: ICustomEmojis; + style?: StyleProp; theme: TSupportedThemes; - onEmojiSelected?: ((emoji: any) => void) | ((keyboardId: any, params?: any) => void); - tabEmojiStyle?: object; + onEmojiSelected: (emoji: string, shortname?: string) => void; + tabEmojiStyle?: StyleProp; } interface IEmojiPickerState { @@ -65,7 +61,7 @@ class EmojiPicker extends Component { this.setState({ show: true }); } - shouldComponentUpdate(nextProps: any, nextState: any) { + shouldComponentUpdate(nextProps: IEmojiPickerProps, nextState: IEmojiPickerState) { const { frequentlyUsed, show, width } = this.state; const { theme } = this.props; if (nextProps.theme !== theme) { @@ -92,12 +88,12 @@ class EmojiPicker extends Component { extension: emoji.extension, isCustom: true }); - onEmojiSelected!(`:${emoji.content}:`); + onEmojiSelected(`:${emoji.content}:`); } else { const content = emoji; this._addFrequentlyUsed({ content, isCustom: false }); const shortname = `:${emoji}:`; - onEmojiSelected!(shortnameToUnicode(shortname), shortname); + onEmojiSelected(shortnameToUnicode(shortname), shortname); } } catch (e) { log(e); @@ -107,9 +103,8 @@ class EmojiPicker extends Component { _addFrequentlyUsed = protectedFunction(async (emoji: IEmoji) => { const db = database.active; const freqEmojiCollection = db.get('frequently_used_emojis'); - let freqEmojiRecord: any; + let freqEmojiRecord: TFrequentlyUsedEmojiModel; try { - // @ts-ignore freqEmojiRecord = await freqEmojiCollection.find(emoji.content); } catch (error) { // Do nothing @@ -117,11 +112,13 @@ class EmojiPicker extends Component { await db.write(async () => { if (freqEmojiRecord) { - await freqEmojiRecord.update((f: any) => { - f.count += 1; + await freqEmojiRecord.update(f => { + if (f.count) { + f.count += 1; + } }); } else { - await freqEmojiCollection.create((f: any) => { + await freqEmojiCollection.create(f => { f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema); Object.assign(f, emoji); f.count = 1; @@ -149,7 +146,7 @@ class EmojiPicker extends Component { } }: any) => this.setState({ width }); - renderCategory(category: any, i: number, label: string) { + renderCategory(category: keyof typeof emojisByCategory, i: number, label: string) { const { frequentlyUsed, customEmojis, width } = this.state; const { baseUrl } = this.props; @@ -166,7 +163,7 @@ class EmojiPicker extends Component { emojis={emojis} onEmojiSelected={(emoji: IEmoji) => this.onEmojiSelected(emoji)} style={styles.categoryContainer} - width={width!} + width={width} baseUrl={baseUrl} tabLabel={label} /> @@ -184,10 +181,12 @@ class EmojiPicker extends Component { } - /* @ts-ignore*/ - contentProps={scrollProps} + contentProps={{ + keyboardShouldPersistTaps: 'always', + keyboardDismissMode: 'none' + }} style={{ backgroundColor: themes[theme].focusedBackground }}> - {categories.tabs.map((tab, i) => + {categories.tabs.map((tab: any, i) => i === 0 && frequentlyUsed.length === 0 ? null // when no frequentlyUsed don't show the tab : this.renderCategory(tab.category, i, tab.tabLabel) @@ -198,9 +197,8 @@ class EmojiPicker extends Component { } } -const mapStateToProps = (state: any) => ({ +const mapStateToProps = (state: IApplicationState) => ({ customEmojis: state.customEmojis }); -// TODO - remove this as any, at the new PR to fix the HOC erros -export default connect(mapStateToProps)(withTheme(EmojiPicker)) as any; +export default connect(mapStateToProps)(withTheme(EmojiPicker)); diff --git a/app/containers/MessageActions/Header.tsx b/app/containers/MessageActions/Header.tsx index 75e149fd9..cf4c71a92 100644 --- a/app/containers/MessageActions/Header.tsx +++ b/app/containers/MessageActions/Header.tsx @@ -10,9 +10,7 @@ import database from '../../lib/database'; import { Button } from '../ActionSheet'; import { useDimensions } from '../../dimensions'; import sharedStyles from '../../views/Styles'; -import { TFrequentlyUsedEmojiModel } from '../../definitions/IFrequentlyUsedEmoji'; -import { TAnyMessageModel } from '../../definitions'; -import { IEmoji } from '../../definitions/IEmoji'; +import { TAnyMessageModel, TFrequentlyUsedEmojiModel } from '../../definitions'; type TItem = TFrequentlyUsedEmojiModel | string; @@ -83,7 +81,7 @@ const HeaderItem = ({ item, onReaction, server, theme }: THeaderItem) => { style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]} theme={theme}> {emojiModel?.isCustom ? ( - + ) : ( {shortnameToUnicode(`:${emoji}:`)} )} diff --git a/app/containers/MessageBox/EmojiKeyboard.tsx b/app/containers/MessageBox/EmojiKeyboard.tsx index 599caa498..172b7f4b8 100644 --- a/app/containers/MessageBox/EmojiKeyboard.tsx +++ b/app/containers/MessageBox/EmojiKeyboard.tsx @@ -7,7 +7,6 @@ import EmojiPicker from '../EmojiPicker'; import styles from './styles'; import { themes } from '../../lib/constants'; import { TSupportedThemes, withTheme } from '../../theme'; -import { IEmoji } from '../../definitions/IEmoji'; interface IMessageBoxEmojiKeyboard { theme: TSupportedThemes; @@ -22,7 +21,7 @@ export default class EmojiKeyboard extends React.PureComponent { + onEmojiSelected = (emoji: string) => { KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji }); }; @@ -32,7 +31,7 @@ export default class EmojiKeyboard extends React.PureComponent - + ); } diff --git a/app/containers/MessageBox/index.tsx b/app/containers/MessageBox/index.tsx index 91f334a5f..83f219c6e 100644 --- a/app/containers/MessageBox/index.tsx +++ b/app/containers/MessageBox/index.tsx @@ -13,7 +13,7 @@ import TextInput, { IThemedTextInput } from '../../presentation/TextInput'; import { userTyping as userTypingAction } from '../../actions/room'; import styles from './styles'; import database from '../../lib/database'; -import { emojis } from '../../emojis'; +import { emojis } from '../EmojiPicker/emojis'; import log, { events, logEvent } from '../../utils/log'; import RecordAudio from './RecordAudio'; import I18n from '../../i18n'; @@ -49,7 +49,7 @@ import { sanitizeLikeString } from '../../lib/database/utils'; import { CustomIcon } from '../CustomIcon'; import { IMessage } from '../../definitions/IMessage'; import { forceJpgExtension } from './forceJpgExtension'; -import { IBaseScreen, IPreviewItem, IUser, TSubscriptionModel, TThreadModel } from '../../definitions'; +import { IBaseScreen, IPreviewItem, IUser, TGetCustomEmoji, TSubscriptionModel, TThreadModel } from '../../definitions'; import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types'; import { getPermalinkMessage, hasPermission, search, sendFileMessage } from '../../lib/methods'; import { Services } from '../../lib/services'; @@ -92,7 +92,7 @@ export interface IMessageBoxProps extends IBaseScreen { } }; - onEmojiSelected = (keyboardId: any, params: any) => { + onEmojiSelected = (keyboardId: string, params: { emoji: string }) => { const { text } = this; const { emoji } = params; let newText = ''; diff --git a/app/containers/message/index.tsx b/app/containers/message/index.tsx index c3b544622..a389d3127 100644 --- a/app/containers/message/index.tsx +++ b/app/containers/message/index.tsx @@ -8,8 +8,7 @@ import debounce from '../../utils/debounce'; import { getMessageTranslation } from './utils'; import { TSupportedThemes, withTheme } from '../../theme'; import openLink from '../../utils/openLink'; -import { TGetCustomEmoji } from '../../definitions/IEmoji'; -import { IAttachment, TAnyMessageModel } from '../../definitions'; +import { IAttachment, TAnyMessageModel, TGetCustomEmoji } from '../../definitions'; import { IRoomInfoParam } from '../../views/SearchMessagesView'; import { E2E_MESSAGE_TYPE, E2E_STATUS, messagesStatus } from '../../lib/constants'; diff --git a/app/definitions/ICustomEmoji.ts b/app/definitions/ICustomEmoji.ts deleted file mode 100644 index 348e66098..000000000 --- a/app/definitions/ICustomEmoji.ts +++ /dev/null @@ -1,11 +0,0 @@ -import Model from '@nozbe/watermelondb/Model'; - -export interface ICustomEmoji { - _id: string; - name?: string; - aliases?: string[]; - extension: string; - _updatedAt: Date; -} - -export type TCustomEmojiModel = ICustomEmoji & Model; diff --git a/app/definitions/IEmoji.ts b/app/definitions/IEmoji.ts index 3896aa5e2..38bbf2351 100644 --- a/app/definitions/IEmoji.ts +++ b/app/definitions/IEmoji.ts @@ -1,31 +1,43 @@ -// TODO: evaluate unification with IEmoji +import Model from '@nozbe/watermelondb/Model'; +import { StyleProp } from 'react-native'; +import { ImageStyle } from '@rocket.chat/react-native-fast-image'; + export interface IEmoji { - content?: string; - name?: string; - extension?: string; - isCustom?: boolean; + content: string; + name: string; + extension: string; + isCustom: boolean; + count?: number; +} + +export interface ICustomEmojis { + [key: string]: Pick; } export interface ICustomEmoji { baseUrl?: string; emoji: IEmoji; - style: any; + style: StyleProp; +} + +export interface ICustomEmojiModel { + _id: string; + name?: string; + aliases?: string[]; + extension: string; + _updatedAt: Date; } export interface IEmojiCategory { baseUrl: string; emojis: IEmoji[]; - onEmojiSelected: Function; - emojisPerRow: number; - width: number; - style: any; + onEmojiSelected: (emoji: IEmoji) => void; + width: number | null; + style: StyleProp; tabLabel: string; } -// TODO: copied from reducers/customEmojis. We can unify later. -export interface IReduxEmoji { - name: string; - extension: any; -} - export type TGetCustomEmoji = (name: string) => any; + +export type TFrequentlyUsedEmojiModel = IEmoji & Model; +export type TCustomEmojiModel = ICustomEmojiModel & Model; diff --git a/app/definitions/IFrequentlyUsedEmoji.ts b/app/definitions/IFrequentlyUsedEmoji.ts deleted file mode 100644 index 2602466ce..000000000 --- a/app/definitions/IFrequentlyUsedEmoji.ts +++ /dev/null @@ -1,11 +0,0 @@ -import Model from '@nozbe/watermelondb/Model'; - -// TODO: evaluate unification with IEmoji -export interface IFrequentlyUsedEmoji { - content?: string; - extension?: string; - isCustom: boolean; - count: number; -} - -export type TFrequentlyUsedEmojiModel = IFrequentlyUsedEmoji & Model; diff --git a/app/definitions/index.ts b/app/definitions/index.ts index 9064babf4..8576c9493 100644 --- a/app/definitions/index.ts +++ b/app/definitions/index.ts @@ -12,8 +12,7 @@ export * from './IRoom'; export * from './IMessage'; export * from './IThread'; export * from './IThreadMessage'; -export * from './ICustomEmoji'; -export * from './IFrequentlyUsedEmoji'; +export * from './IEmoji'; export * from './IUpload'; export * from './ISettings'; export * from './IRole'; diff --git a/app/lib/methods/getCustomEmojis.ts b/app/lib/methods/getCustomEmojis.ts index 4f664efaa..c2a431105 100644 --- a/app/lib/methods/getCustomEmojis.ts +++ b/app/lib/methods/getCustomEmojis.ts @@ -1,12 +1,11 @@ import orderBy from 'lodash/orderBy'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; -import { ICustomEmojis } from '../../reducers/customEmojis'; import { store as reduxStore } from '../store/auxStore'; import database from '../database'; import log from '../../utils/log'; import { setCustomEmojis as setCustomEmojisAction } from '../../actions/customEmojis'; -import { ICustomEmoji, TCustomEmojiModel } from '../../definitions'; +import { ICustomEmojiModel, TCustomEmojiModel, ICustomEmojis } from '../../definitions'; import sdk from '../services/sdk'; import { compareServerVersion } from './helpers/compareServerVersion'; @@ -16,7 +15,7 @@ interface IUpdateEmojis { allRecords: TCustomEmojiModel[]; } -const getUpdatedSince = (allEmojis: ICustomEmoji[]) => { +const getUpdatedSince = (allEmojis: ICustomEmojiModel[]) => { if (!allEmojis.length) { return null; } diff --git a/app/reducers/customEmojis.test.ts b/app/reducers/customEmojis.test.ts index 3f507f04c..b45e79b9b 100644 --- a/app/reducers/customEmojis.test.ts +++ b/app/reducers/customEmojis.test.ts @@ -1,6 +1,7 @@ import { setCustomEmojis } from '../actions/customEmojis'; -import { ICustomEmojis, initialState } from './customEmojis'; +import { initialState } from './customEmojis'; import { mockedStore } from './mockedStore'; +import { ICustomEmojis } from '../definitions'; describe('test reducer', () => { it('should return initial state', () => { diff --git a/app/reducers/customEmojis.ts b/app/reducers/customEmojis.ts index 859d634d4..b462dd5a2 100644 --- a/app/reducers/customEmojis.ts +++ b/app/reducers/customEmojis.ts @@ -1,15 +1,5 @@ import { SET_CUSTOM_EMOJIS } from '../actions/actionsTypes'; -import { TApplicationActions } from '../definitions'; - -// There are at least three interfaces for emoji, but none of them includes only this data. -interface IEmoji { - name: string; - extension: string; -} - -export interface ICustomEmojis { - [key: string]: IEmoji; -} +import { ICustomEmojis, TApplicationActions } from '../definitions'; export const initialState: ICustomEmojis = {}; diff --git a/app/views/MessagesView/index.tsx b/app/views/MessagesView/index.tsx index 714e4ef3d..042cdddda 100644 --- a/app/views/MessagesView/index.tsx +++ b/app/views/MessagesView/index.tsx @@ -19,10 +19,8 @@ import SafeAreaView from '../../containers/SafeAreaView'; import getThreadName from '../../lib/methods/getThreadName'; import styles from './styles'; import { ChatsStackParamList } from '../../stacks/types'; -import { ISubscription, SubscriptionType } from '../../definitions/ISubscription'; -import { IEmoji } from '../../definitions/IEmoji'; import { IRoomInfoParam } from '../SearchMessagesView'; -import { TMessageModel, IUrl } from '../../definitions'; +import { TMessageModel, IEmoji, ISubscription, SubscriptionType, IUrl } from '../../definitions'; import { Services } from '../../lib/services'; interface IMessagesViewProps { diff --git a/app/views/RoomView/ReactionPicker.tsx b/app/views/RoomView/ReactionPicker.tsx index e49c3f181..f4ed4378d 100644 --- a/app/views/RoomView/ReactionPicker.tsx +++ b/app/views/RoomView/ReactionPicker.tsx @@ -19,7 +19,7 @@ interface IReactionPickerProps { show: boolean; isMasterDetail: boolean; reactionClose: () => void; - onEmojiSelected: Function; // TODO: properly type this + onEmojiSelected: (shortname: string, id: string) => void; width: number; height: number; theme: TSupportedThemes; @@ -31,7 +31,7 @@ class ReactionPicker extends React.Component { return nextProps.show !== show || width !== nextProps.width || height !== nextProps.height; } - onEmojiSelected = (emoji: string, shortname: string) => { + onEmojiSelected = (emoji: string, shortname?: string) => { // standard emojis: `emoji` is unicode and `shortname` is :joy: // custom emojis: only `emoji` is returned with shortname type (:joy:) // to set reactions, we need shortname type @@ -70,11 +70,7 @@ class ReactionPicker extends React.Component { } ]} testID='reaction-picker'> - + ) : null; diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index 3f64e2eca..a98b902ad 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -9,7 +9,6 @@ import { dequal } from 'dequal'; import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context'; import { Subscription } from 'rxjs'; -import { IReduxEmoji } from '../../definitions/IEmoji'; import Touch from '../../utils/touch'; import { replyBroadcast } from '../../actions/messages'; import database from '../../lib/database'; @@ -76,9 +75,10 @@ import { TAnyMessageModel, TMessageModel, TSubscriptionModel, - TThreadModel + TThreadModel, + IEmoji, + ICustomEmojis } from '../../definitions'; -import { ICustomEmojis } from '../../reducers/customEmojis'; import { E2E_MESSAGE_TYPE, E2E_STATUS, MESSAGE_TYPE_ANY_LOAD, MessageTypeLoad, themes } from '../../lib/constants'; import { TListRef } from './List/List'; import { ModalStackParamList } from '../../stacks/MasterDetailStack/types'; @@ -931,8 +931,7 @@ class RoomView extends React.Component { }); }; - // TODO: We need to unify - getCustomEmoji = (name: string): IReduxEmoji | null => { + getCustomEmoji = (name: string): Pick | null => { const { customEmojis } = this.props; const emoji = customEmojis[name]; if (emoji) {