From 4416f82665080d174261c3f4fd5a84b797ad1468 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 31 Mar 2022 09:38:20 -0300 Subject: [PATCH] Chore: Properly type MessageActions (#3930) --- app/containers/ActionSheet/Provider.tsx | 2 +- app/containers/EmojiPicker/CustomEmoji.tsx | 1 + app/containers/EmojiPicker/EmojiCategory.tsx | 1 + app/containers/EmojiPicker/index.tsx | 1 + app/containers/MessageActions/Header.tsx | 88 +++++++------ app/containers/MessageActions/index.tsx | 129 ++++++++++--------- app/definitions/IEmoji.ts | 9 +- app/definitions/IFrequentlyUsedEmoji.ts | 1 + app/dimensions.tsx | 2 +- 9 files changed, 129 insertions(+), 105 deletions(-) diff --git a/app/containers/ActionSheet/Provider.tsx b/app/containers/ActionSheet/Provider.tsx index 112242263..8ad0e7c27 100644 --- a/app/containers/ActionSheet/Provider.tsx +++ b/app/containers/ActionSheet/Provider.tsx @@ -2,7 +2,7 @@ import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react'; import ActionSheet from './ActionSheet'; -export type TActionSheetOptionsItem = { title: string; icon: string; onPress: () => void }; +export type TActionSheetOptionsItem = { title: string; icon: string; onPress: () => void; danger?: boolean }; export type TActionSheetOptions = { options: TActionSheetOptionsItem[]; diff --git a/app/containers/EmojiPicker/CustomEmoji.tsx b/app/containers/EmojiPicker/CustomEmoji.tsx index c96ee7ae5..aeebbe21c 100644 --- a/app/containers/EmojiPicker/CustomEmoji.tsx +++ b/app/containers/EmojiPicker/CustomEmoji.tsx @@ -8,6 +8,7 @@ const CustomEmoji = React.memo( > { contentContainerStyle={{ marginHorizontal }} // rerender FlatList in case of width changes key={`emoji-category-${width}`} + // @ts-ignore keyExtractor={item => (item && item.isCustom && item.content) || item} data={emojis} extraData={this.props} diff --git a/app/containers/EmojiPicker/index.tsx b/app/containers/EmojiPicker/index.tsx index 5577fd190..5c67b06df 100644 --- a/app/containers/EmojiPicker/index.tsx +++ b/app/containers/EmojiPicker/index.tsx @@ -109,6 +109,7 @@ class EmojiPicker extends Component { const freqEmojiCollection = db.get('frequently_used_emojis'); let freqEmojiRecord: any; try { + // @ts-ignore freqEmojiRecord = await freqEmojiCollection.find(emoji.content); } catch (error) { // Do nothing diff --git a/app/containers/MessageActions/Header.tsx b/app/containers/MessageActions/Header.tsx index 52165b8b7..82cbee2cc 100644 --- a/app/containers/MessageActions/Header.tsx +++ b/app/containers/MessageActions/Header.tsx @@ -1,7 +1,7 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { FlatList, StyleSheet, Text, View } from 'react-native'; -import { withTheme } from '../../theme'; +import { useTheme } from '../../theme'; import { themes } from '../../constants/colors'; import { CustomIcon } from '../../lib/Icons'; import shortnameToUnicode from '../../utils/shortnameToUnicode'; @@ -10,26 +10,30 @@ import database from '../../lib/database'; import { Button } from '../ActionSheet'; import { useDimensions } from '../../dimensions'; import sharedStyles from '../../views/Styles'; -import { IEmoji } from '../../definitions/IEmoji'; import { TFrequentlyUsedEmojiModel } from '../../definitions/IFrequentlyUsedEmoji'; +import { TAnyMessageModel } from '../../definitions'; +import { IEmoji } from '../../definitions/IEmoji'; -interface IHeader { - handleReaction: Function; +type TItem = TFrequentlyUsedEmojiModel | string; + +export interface IHeader { + handleReaction: (emoji: TItem, message: TAnyMessageModel) => void; server: string; - message: object; + message: TAnyMessageModel; isMasterDetail: boolean; - theme?: string; } +type TOnReaction = ({ emoji }: { emoji: TItem }) => void; + interface THeaderItem { - item: IEmoji; - onReaction: Function; + item: TItem; + onReaction: TOnReaction; server: string; theme: string; } interface THeaderFooter { - onReaction: any; + onReaction: TOnReaction; theme: string; } @@ -62,25 +66,32 @@ const styles = StyleSheet.create({ } }); -const keyExtractor = (item: any) => item?.id || item; +const keyExtractor = (item: TItem) => { + const emojiModel = item as TFrequentlyUsedEmojiModel; + return (emojiModel.id ? emojiModel.content : item) as string; +}; const DEFAULT_EMOJIS = ['clap', '+1', 'heart_eyes', 'grinning', 'thinking_face', 'smiley']; -const HeaderItem = React.memo(({ item, onReaction, server, theme }: THeaderItem) => ( - -)); +const HeaderItem = ({ item, onReaction, server, theme }: THeaderItem) => { + const emojiModel = item as TFrequentlyUsedEmojiModel; + const emoji = (emojiModel.id ? emojiModel.content : item) as string; + return ( + + ); +}; -const HeaderFooter = React.memo(({ onReaction, theme }: THeaderFooter) => ( +const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => ( -)); +); -const Header = React.memo(({ handleReaction, server, message, isMasterDetail, theme }: IHeader) => { - const [items, setItems] = useState<(TFrequentlyUsedEmojiModel | string)[]>([]); - const { width, height }: any = useDimensions(); +const Header = React.memo(({ handleReaction, server, message, isMasterDetail }: IHeader) => { + const [items, setItems] = useState([]); + const { width, height } = useDimensions(); + const { theme } = useTheme(); + // TODO: create custom hook to re-render based on screen size const setEmojis = async () => { try { const db = database.active; const freqEmojiCollection = db.get('frequently_used_emojis'); - let freqEmojis: (TFrequentlyUsedEmojiModel | string)[] = await freqEmojiCollection.query().fetch(); + let freqEmojis: TItem[] = await freqEmojiCollection.query().fetch(); const isLandscape = width > height; const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2; @@ -115,22 +128,21 @@ const Header = React.memo(({ handleReaction, server, message, isMasterDetail, th setEmojis(); }, []); - const onReaction = ({ emoji }: { emoji: IEmoji }) => handleReaction(emoji, message); + const onReaction: TOnReaction = ({ emoji }) => handleReaction(emoji, message); - const renderItem = useCallback( - ({ item }) => , - [] + const renderItem = ({ item }: { item: TItem }) => ( + ); - const renderFooter = useCallback(() => , []); + const renderFooter = () => ; return ( - + ; - editInit: Function; - reactionInit: Function; - onReactionPress: Function; - replyInit: Function; + editInit: (message: TAnyMessageModel) => void; + reactionInit: (message: TAnyMessageModel) => void; + onReactionPress: (shortname: string, messageId: string) => void; + replyInit: (message: TAnyMessageModel, mention: boolean) => void; isMasterDetail: boolean; isReadOnly: boolean; - Message_AllowDeleting: boolean; - Message_AllowDeleting_BlockDeleteInMinutes: number; - Message_AllowEditing: boolean; - Message_AllowEditing_BlockEditInMinutes: number; - Message_AllowPinning: boolean; - Message_AllowStarring: boolean; - Message_Read_Receipt_Store_Users: boolean; + Message_AllowDeleting?: boolean; + Message_AllowDeleting_BlockDeleteInMinutes?: number; + Message_AllowEditing?: boolean; + Message_AllowEditing_BlockEditInMinutes?: number; + Message_AllowPinning?: boolean; + Message_AllowStarring?: boolean; + Message_Read_Receipt_Store_Users?: boolean; server: string; - editMessagePermission: []; - deleteMessagePermission: []; - forceDeleteMessagePermission: []; - pinMessagePermission: []; + editMessagePermission?: string[]; + deleteMessagePermission?: string[]; + forceDeleteMessagePermission?: string[]; + pinMessagePermission?: string[]; } const MessageActions = React.memo( @@ -69,9 +69,14 @@ const MessageActions = React.memo( pinMessagePermission }: IMessageActions, ref - ): any => { - let permissions: any = {}; - const { showActionSheet, hideActionSheet }: any = useActionSheet(); + ) => { + let permissions = { + hasEditPermission: false, + hasDeletePermission: false, + hasForceDeletePermission: false, + hasPinPermission: false + }; + const { showActionSheet, hideActionSheet } = useActionSheet(); const getPermissions = async () => { try { @@ -88,9 +93,9 @@ const MessageActions = React.memo( } }; - const isOwn = (message: any) => message.u && message.u._id === user.id; + const isOwn = (message: TAnyMessageModel) => message.u && message.u._id === user.id; - const allowEdit = (message: any) => { + const allowEdit = (message: TAnyMessageModel) => { if (isReadOnly) { return false; } @@ -105,7 +110,7 @@ const MessageActions = React.memo( if (message.ts != null) { msgTs = moment(message.ts); } - let currentTsDiff: any; + let currentTsDiff = 0; if (msgTs != null) { currentTsDiff = moment().diff(msgTs, 'minutes'); } @@ -114,7 +119,7 @@ const MessageActions = React.memo( return true; }; - const allowDelete = (message: any) => { + const allowDelete = (message: TAnyMessageModel) => { if (isReadOnly) { return false; } @@ -136,7 +141,7 @@ const MessageActions = React.memo( if (message.ts != null) { msgTs = moment(message.ts); } - let currentTsDiff: any; + let currentTsDiff = 0; if (msgTs != null) { currentTsDiff = moment().diff(msgTs, 'minutes'); } @@ -145,19 +150,19 @@ const MessageActions = React.memo( return true; }; - const getPermalink = (message: any) => RocketChat.getPermalinkMessage(message); + const getPermalink = (message: TAnyMessageModel) => RocketChat.getPermalinkMessage(message); - const handleReply = (message: any) => { + const handleReply = (message: TAnyMessageModel) => { logEvent(events.ROOM_MSG_ACTION_REPLY); replyInit(message, true); }; - const handleEdit = (message: any) => { + const handleEdit = (message: TAnyMessageModel) => { logEvent(events.ROOM_MSG_ACTION_EDIT); editInit(message); }; - const handleCreateDiscussion = (message: any) => { + const handleCreateDiscussion = (message: TAnyMessageModel) => { logEvent(events.ROOM_MSG_ACTION_DISCUSSION); const params = { message, channel: room, showCloseModal: true }; if (isMasterDetail) { @@ -167,7 +172,7 @@ const MessageActions = React.memo( } }; - const handleUnread = async (message: any) => { + const handleUnread = async (message: TAnyMessageModel) => { logEvent(events.ROOM_MSG_ACTION_UNREAD); const { id: messageId, ts } = message; const { rid } = room; @@ -179,7 +184,7 @@ const MessageActions = React.memo( const subRecord = await subCollection.find(rid); await db.write(async () => { try { - await subRecord.update(sub => (sub.lastOpen = ts)); + await subRecord.update(sub => (sub.lastOpen = ts as Date)); // TODO: reevaluate IMessage } catch { // do nothing } @@ -192,42 +197,44 @@ const MessageActions = React.memo( } }; - const handlePermalink = async (message: any) => { + const handlePermalink = async (message: TAnyMessageModel) => { logEvent(events.ROOM_MSG_ACTION_PERMALINK); try { - const permalink: any = await getPermalink(message); - Clipboard.setString(permalink); + const permalink = await getPermalink(message); + Clipboard.setString(permalink ?? ''); EventEmitter.emit(LISTENER, { message: I18n.t('Permalink_copied_to_clipboard') }); } catch { logEvent(events.ROOM_MSG_ACTION_PERMALINK_F); } }; - const handleCopy = async (message: any) => { + const handleCopy = async (message: TAnyMessageModel) => { logEvent(events.ROOM_MSG_ACTION_COPY); - await Clipboard.setString(message?.attachments?.[0]?.description || message.msg); + await Clipboard.setString((message?.attachments?.[0]?.description || message.msg) ?? ''); EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); }; - const handleShare = async (message: any) => { + const handleShare = async (message: TAnyMessageModel) => { logEvent(events.ROOM_MSG_ACTION_SHARE); try { - const permalink: any = await getPermalink(message); - Share.share({ message: permalink }); + const permalink = await getPermalink(message); + if (permalink) { + Share.share({ message: permalink }); + } } catch { logEvent(events.ROOM_MSG_ACTION_SHARE_F); } }; - const handleQuote = (message: any) => { + const handleQuote = (message: TAnyMessageModel) => { logEvent(events.ROOM_MSG_ACTION_QUOTE); replyInit(message, false); }; - const handleStar = async (message: any) => { + const handleStar = async (message: TAnyMessageModel) => { logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR); try { - await RocketChat.toggleStarMessage(message.id, message.starred); + await RocketChat.toggleStarMessage(message.id, message.starred as boolean); // TODO: reevaluate `message.starred` type on IMessage EventEmitter.emit(LISTENER, { message: message.starred ? I18n.t('Message_unstarred') : I18n.t('Message_starred') }); } catch (e) { logEvent(events.ROOM_MSG_ACTION_STAR_F); @@ -235,20 +242,21 @@ const MessageActions = React.memo( } }; - const handlePin = async (message: any) => { + const handlePin = async (message: TAnyMessageModel) => { logEvent(events.ROOM_MSG_ACTION_PIN); try { - await RocketChat.togglePinMessage(message.id, message.pinned); + await RocketChat.togglePinMessage(message.id, message.pinned as boolean); // TODO: reevaluate `message.pinned` type on IMessage } catch (e) { logEvent(events.ROOM_MSG_ACTION_PIN_F); log(e); } }; - const handleReaction = (shortname: any, message: any) => { + const handleReaction: IHeader['handleReaction'] = (shortname, message) => { logEvent(events.ROOM_MSG_ACTION_REACTION); if (shortname) { - onReactionPress(shortname, message.id); + // TODO: evaluate unification with IEmoji + onReactionPress(shortname as any, message.id); } else { reactionInit(message); } @@ -256,7 +264,7 @@ const MessageActions = React.memo( hideActionSheet(); }; - const handleReadReceipt = (message: any) => { + const handleReadReceipt = (message: TAnyMessageModel) => { if (isMasterDetail) { Navigation.navigate('ModalStackNavigator', { screen: 'ReadReceiptsView', params: { messageId: message.id } }); } else { @@ -291,7 +299,7 @@ const MessageActions = React.memo( } }; - const handleReport = async (message: any) => { + const handleReport = async (message: TAnyMessageModel) => { logEvent(events.ROOM_MSG_ACTION_REPORT); try { await RocketChat.reportMessage(message.id); @@ -302,14 +310,14 @@ const MessageActions = React.memo( } }; - const handleDelete = (message: any) => { + const handleDelete = (message: TAnyMessageModel) => { showConfirmationAlert({ message: I18n.t('You_will_not_be_able_to_recover_this_message'), confirmationText: I18n.t('Delete'), onPress: async () => { try { logEvent(events.ROOM_MSG_ACTION_DELETE); - await RocketChat.deleteMessage(message.id, message.subscription.id); + await RocketChat.deleteMessage(message.id, message.subscription ? message.subscription.id : ''); } catch (e) { logEvent(events.ROOM_MSG_ACTION_DELETE_F); log(e); @@ -319,7 +327,7 @@ const MessageActions = React.memo( }; const getOptions = (message: TAnyMessageModel) => { - let options: any = []; + let options: TActionSheetOptionsItem[] = []; // Reply if (!isReadOnly) { @@ -463,16 +471,15 @@ const MessageActions = React.memo( } ) ); - -const mapStateToProps = (state: any) => ({ +const mapStateToProps = (state: IApplicationState) => ({ server: state.server.server, - Message_AllowDeleting: state.settings.Message_AllowDeleting, - Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes, - Message_AllowEditing: state.settings.Message_AllowEditing, - Message_AllowEditing_BlockEditInMinutes: state.settings.Message_AllowEditing_BlockEditInMinutes, - Message_AllowPinning: state.settings.Message_AllowPinning, - Message_AllowStarring: state.settings.Message_AllowStarring, - Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users, + Message_AllowDeleting: state.settings.Message_AllowDeleting as boolean, + Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes as number, + Message_AllowEditing: state.settings.Message_AllowEditing as boolean, + Message_AllowEditing_BlockEditInMinutes: state.settings.Message_AllowEditing_BlockEditInMinutes as number, + Message_AllowPinning: state.settings.Message_AllowPinning as boolean, + Message_AllowStarring: state.settings.Message_AllowStarring as boolean, + Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users as boolean, isMasterDetail: state.app.isMasterDetail, editMessagePermission: state.permissions['edit-message'], deleteMessagePermission: state.permissions['delete-message'], diff --git a/app/definitions/IEmoji.ts b/app/definitions/IEmoji.ts index 94841aab1..3896aa5e2 100644 --- a/app/definitions/IEmoji.ts +++ b/app/definitions/IEmoji.ts @@ -1,8 +1,9 @@ +// TODO: evaluate unification with IEmoji export interface IEmoji { - content: any; - name: string; - extension: any; - isCustom: boolean; + content?: string; + name?: string; + extension?: string; + isCustom?: boolean; } export interface ICustomEmoji { diff --git a/app/definitions/IFrequentlyUsedEmoji.ts b/app/definitions/IFrequentlyUsedEmoji.ts index 87e46cf33..2602466ce 100644 --- a/app/definitions/IFrequentlyUsedEmoji.ts +++ b/app/definitions/IFrequentlyUsedEmoji.ts @@ -1,5 +1,6 @@ import Model from '@nozbe/watermelondb/Model'; +// TODO: evaluate unification with IEmoji export interface IFrequentlyUsedEmoji { content?: string; extension?: string; diff --git a/app/dimensions.tsx b/app/dimensions.tsx index 92b4ee9b3..afa86d38b 100644 --- a/app/dimensions.tsx +++ b/app/dimensions.tsx @@ -7,7 +7,7 @@ import { TNavigationOptions } from './definitions/navigationTypes'; export interface IDimensionsContextProps { width: number; height: number; - scale?: number; + scale: number; fontScale: number; setDimensions?: ({ width,