From 775a951bb418227a0bd2adeb9d9273870588adbd Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 26 Mar 2024 19:03:24 +0000 Subject: [PATCH 1/2] refactor(typescript): migrate PDFView --- .../BaseAttachmentViewPdf.tsx | 1 - .../AttachmentViewPdf/index.tsx | 1 - .../AttachmentView/AttachmentViewPdf/types.ts | 10 +- .../{PDFInfoMessage.js => PDFInfoMessage.tsx} | 26 ++-- ...PDFPasswordForm.js => PDFPasswordForm.tsx} | 53 +++---- src/components/PDFView/index.js | 147 ------------------ .../{index.native.js => index.native.tsx} | 34 ++-- src/components/PDFView/index.tsx | 133 ++++++++++++++++ src/components/PDFView/types.ts | 47 ++++++ src/components/TextLink.tsx | 2 +- 10 files changed, 235 insertions(+), 219 deletions(-) rename src/components/PDFView/{PDFInfoMessage.js => PDFInfoMessage.tsx} (55%) rename src/components/PDFView/{PDFPasswordForm.js => PDFPasswordForm.tsx} (77%) delete mode 100644 src/components/PDFView/index.js rename src/components/PDFView/{index.native.js => index.native.tsx} (88%) create mode 100644 src/components/PDFView/index.tsx create mode 100644 src/components/PDFView/types.ts diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx index 44aeb2a58b81..fe20f7590f33 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx @@ -67,7 +67,6 @@ function BaseAttachmentViewPdf({ return ( & { encryptedSourceUrl: string; onLoadComplete: (path: string) => void; - /** Additional style props */ - style?: StyleProp; + /** (web only) Additional style props */ + style?: CSSProperties; /** Styles for the error label */ - errorLabelStyles?: StyleProp; + errorLabelStyles?: StyleProp; /** Triggered when the PDF's onScaleChanged event is triggered */ onScaleChanged?: (scale: number) => void; diff --git a/src/components/PDFView/PDFInfoMessage.js b/src/components/PDFView/PDFInfoMessage.tsx similarity index 55% rename from src/components/PDFView/PDFInfoMessage.js rename to src/components/PDFView/PDFInfoMessage.tsx index 9b26ac1db170..03daa7930290 100644 --- a/src/components/PDFView/PDFInfoMessage.js +++ b/src/components/PDFView/PDFInfoMessage.tsx @@ -1,25 +1,24 @@ -import PropTypes from 'prop-types'; import React from 'react'; +import type {GestureResponderEvent} from 'react-native'; import {View} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; -const propTypes = { +type PDFInfoMessageProps = { /** Callback function to indicate that PDF password form should be shown */ - onShowForm: PropTypes.func.isRequired, - - ...withLocalizePropTypes, + onShowForm: () => void | ((event: GestureResponderEvent) => void); }; -function PDFInfoMessage(props) { +function PDFInfoMessage({onShowForm}: PDFInfoMessageProps) { const theme = useTheme(); const styles = useThemeStyles(); + const {translate} = useLocalize(); return ( - {props.translate('attachmentView.pdfPasswordForm.title')} - {props.translate('attachmentView.pdfPasswordForm.infoText')} + {translate('attachmentView.pdfPasswordForm.title')} + {translate('attachmentView.pdfPasswordForm.infoText')} - {props.translate('attachmentView.pdfPasswordForm.beforeLinkText')} - {` ${props.translate('attachmentView.pdfPasswordForm.linkText')} `} - {props.translate('attachmentView.pdfPasswordForm.afterLinkText')} + {translate('attachmentView.pdfPasswordForm.beforeLinkText')} + {` ${translate('attachmentView.pdfPasswordForm.linkText')} `} + {translate('attachmentView.pdfPasswordForm.afterLinkText')} ); } -PDFInfoMessage.propTypes = propTypes; PDFInfoMessage.displayName = 'PDFInfoMessage'; -export default withLocalize(PDFInfoMessage); +export default PDFInfoMessage; diff --git a/src/components/PDFView/PDFPasswordForm.js b/src/components/PDFView/PDFPasswordForm.tsx similarity index 77% rename from src/components/PDFView/PDFPasswordForm.js rename to src/components/PDFView/PDFPasswordForm.tsx index 2abf5a1236e0..e474108e4c86 100644 --- a/src/components/PDFView/PDFPasswordForm.js +++ b/src/components/PDFView/PDFPasswordForm.tsx @@ -1,11 +1,10 @@ -import PropTypes from 'prop-types'; import React, {useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; import Button from '@components/Button'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; +import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -14,35 +13,27 @@ import shouldDelayFocus from '@libs/shouldDelayFocus'; import CONST from '@src/CONST'; import PDFInfoMessage from './PDFInfoMessage'; -const propTypes = { +type PDFPasswordFormProps = { /** If the submitted password is invalid (show an error message) */ - isPasswordInvalid: PropTypes.bool, + isPasswordInvalid?: boolean; /** If loading indicator should be shown */ - shouldShowLoadingIndicator: PropTypes.bool, + shouldShowLoadingIndicator?: boolean; /** Notify parent that the password form has been submitted */ - onSubmit: PropTypes.func, + onSubmit?: (password: string) => void; /** Notify parent that the password has been updated/edited */ - onPasswordUpdated: PropTypes.func, + onPasswordUpdated?: (newPassword: string) => void; /** Notify parent that a text field has been focused or blurred */ - onPasswordFieldFocused: PropTypes.func, + onPasswordFieldFocused?: (isFocused: boolean) => void; /** Should focus to the password input */ - isFocused: PropTypes.bool.isRequired, + isFocused: boolean; }; -const defaultProps = { - isPasswordInvalid: false, - shouldShowLoadingIndicator: false, - onSubmit: () => {}, - onPasswordUpdated: () => {}, - onPasswordFieldFocused: () => {}, -}; - -function PDFPasswordForm({isFocused, isPasswordInvalid, shouldShowLoadingIndicator, onSubmit, onPasswordUpdated, onPasswordFieldFocused}) { +function PDFPasswordForm({isFocused, isPasswordInvalid = false, shouldShowLoadingIndicator = false, onSubmit, onPasswordUpdated, onPasswordFieldFocused}: PDFPasswordFormProps) { const styles = useThemeStyles(); const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); @@ -50,15 +41,15 @@ function PDFPasswordForm({isFocused, isPasswordInvalid, shouldShowLoadingIndicat const [password, setPassword] = useState(''); const [validationErrorText, setValidationErrorText] = useState(''); const [shouldShowForm, setShouldShowForm] = useState(false); - const textInputRef = useRef(null); + const textInputRef = useRef(null); - const focusTimeoutRef = useRef(null); + const focusTimeoutRef = useRef(); const errorText = useMemo(() => { if (isPasswordInvalid) { return 'attachmentView.passwordIncorrect'; } - if (!_.isEmpty(validationErrorText)) { + if (validationErrorText) { return validationErrorText; } return ''; @@ -76,7 +67,7 @@ function PDFPasswordForm({isFocused, isPasswordInvalid, shouldShowLoadingIndicat * Relevant thread: https://expensify.slack.com/archives/C01GTK53T8Q/p1694660990479979 */ focusTimeoutRef.current = setTimeout(() => { - textInputRef.current.focus(); + textInputRef.current?.focus(); }, CONST.ANIMATED_TRANSITION); return () => { if (!focusTimeoutRef.current) { @@ -86,19 +77,19 @@ function PDFPasswordForm({isFocused, isPasswordInvalid, shouldShowLoadingIndicat }; }, [isFocused]); - const updatePassword = (newPassword) => { - onPasswordUpdated(newPassword); - if (!_.isEmpty(newPassword) && validationErrorText) { + const updatePassword = (newPassword: string) => { + onPasswordUpdated?.(newPassword); + if (newPassword && validationErrorText) { setValidationErrorText(''); } setPassword(newPassword); }; const validate = () => { - if (!isPasswordInvalid && !_.isEmpty(password)) { + if (!isPasswordInvalid && password) { return true; } - if (_.isEmpty(password)) { + if (!password) { setValidationErrorText('attachmentView.passwordRequired'); } return false; @@ -108,7 +99,7 @@ function PDFPasswordForm({isFocused, isPasswordInvalid, shouldShowLoadingIndicat if (!validate()) { return; } - onSubmit(password); + onSubmit?.(password); }; return shouldShowForm ? ( @@ -136,8 +127,8 @@ function PDFPasswordForm({isFocused, isPasswordInvalid, shouldShowLoadingIndicat enterKeyHint="done" onSubmitEditing={submitPassword} errorText={errorText} - onFocus={() => onPasswordFieldFocused(true)} - onBlur={() => onPasswordFieldFocused(false)} + onFocus={() => onPasswordFieldFocused?.(true)} + onBlur={() => onPasswordFieldFocused?.(false)} autoFocus shouldDelayFocus={shouldDelayFocus} secureTextEntry @@ -160,8 +151,6 @@ function PDFPasswordForm({isFocused, isPasswordInvalid, shouldShowLoadingIndicat ); } -PDFPasswordForm.propTypes = propTypes; -PDFPasswordForm.defaultProps = defaultProps; PDFPasswordForm.displayName = 'PDFPasswordForm'; export default PDFPasswordForm; diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js deleted file mode 100644 index e69b52b74e95..000000000000 --- a/src/components/PDFView/index.js +++ /dev/null @@ -1,147 +0,0 @@ -import 'core-js/features/array/at'; -import React, {Component} from 'react'; -import {PDFPreviewer} from 'react-fast-pdf'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; -import Text from '@components/Text'; -import withLocalize from '@components/withLocalize'; -import withThemeStyles from '@components/withThemeStyles'; -import withWindowDimensions from '@components/withWindowDimensions'; -import compose from '@libs/compose'; -import variables from '@styles/variables'; -import * as CanvasSize from '@userActions/CanvasSize'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import PDFPasswordForm from './PDFPasswordForm'; -import * as pdfViewPropTypes from './pdfViewPropTypes'; - -class PDFView extends Component { - constructor(props) { - super(props); - this.state = { - isKeyboardOpen: false, - }; - this.toggleKeyboardOnSmallScreens = this.toggleKeyboardOnSmallScreens.bind(this); - this.retrieveCanvasLimits(); - } - - shouldComponentUpdate(nextProps, nextState) { - return !_.isEqual(this.state, nextState) || !_.isEqual(this.props, nextProps); - } - - componentDidUpdate(prevProps) { - // Use window height changes to toggle the keyboard. To maintain keyboard state - // on all platforms we also use focus/blur events. So we need to make sure here - // that we avoid redundant keyboard toggling. - // Minus 100px is needed to make sure that when the internet connection is - // disabled in android chrome and a small 'No internet connection' text box appears, - // we do not take it as a sign to open the keyboard - if (!this.state.isKeyboardOpen && this.props.windowHeight < prevProps.windowHeight - 100) { - this.toggleKeyboardOnSmallScreens(true); - } else if (this.state.isKeyboardOpen && this.props.windowHeight > prevProps.windowHeight) { - this.toggleKeyboardOnSmallScreens(false); - } - } - - /** - * On small screens notify parent that the keyboard has opened or closed. - * - * @param {Boolean} isKeyboardOpen True if keyboard is open - */ - toggleKeyboardOnSmallScreens(isKeyboardOpen) { - if (!this.props.isSmallScreenWidth) { - return; - } - this.setState({isKeyboardOpen}); - this.props.onToggleKeyboard(isKeyboardOpen); - } - - /** - * Verify that the canvas limits have been calculated already, if not calculate them and put them in Onyx - */ - retrieveCanvasLimits() { - if (!this.props.maxCanvasArea) { - CanvasSize.retrieveMaxCanvasArea(); - } - - if (!this.props.maxCanvasHeight) { - CanvasSize.retrieveMaxCanvasHeight(); - } - - if (!this.props.maxCanvasWidth) { - CanvasSize.retrieveMaxCanvasWidth(); - } - } - - renderPDFView() { - const styles = this.props.themeStyles; - const outerContainerStyle = [styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]; - - return ( - - } - ErrorComponent={{this.props.translate('attachmentView.failedToLoadPDF')}} - renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( - - )} - /> - - ); - } - - render() { - const styles = this.props.themeStyles; - return this.props.onPress ? ( - - {this.renderPDFView()} - - ) : ( - this.renderPDFView() - ); - } -} - -PDFView.propTypes = pdfViewPropTypes.propTypes; -PDFView.defaultProps = pdfViewPropTypes.defaultProps; - -export default compose( - withLocalize, - withWindowDimensions, - withThemeStyles, - withOnyx({ - maxCanvasArea: { - key: ONYXKEYS.MAX_CANVAS_AREA, - }, - maxCanvasHeight: { - key: ONYXKEYS.MAX_CANVAS_HEIGHT, - }, - maxCanvasWidth: { - key: ONYXKEYS.MAX_CANVAS_WIDTH, - }, - }), -)(PDFView); diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.tsx similarity index 88% rename from src/components/PDFView/index.native.js rename to src/components/PDFView/index.native.tsx index 558f6636a325..f0edba5de7d0 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.tsx @@ -1,4 +1,5 @@ import React, {useCallback, useEffect, useState} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import PDF from 'react-native-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; @@ -12,11 +13,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import CONST from '@src/CONST'; import PDFPasswordForm from './PDFPasswordForm'; -import {defaultProps, propTypes as pdfViewPropTypes} from './pdfViewPropTypes'; - -const propTypes = { - ...pdfViewPropTypes, -}; +import type {PDFViewNativeProps} from './types'; /** * On the native layer, we use react-native-pdf/PDF to display PDFs. If a PDF is @@ -32,8 +29,7 @@ const propTypes = { * so that PDFPasswordForm doesn't bounce when react-native-pdf/PDF * is (temporarily) rendered. */ - -function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, errorLabelStyles}) { +function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, errorLabelStyles}: PDFViewNativeProps) { const [shouldRequestPassword, setShouldRequestPassword] = useState(false); const [shouldAttemptPDFLoad, setShouldAttemptPDFLoad] = useState(true); const [shouldShowLoadingIndicator, setShouldShowLoadingIndicator] = useState(true); @@ -48,7 +44,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused const StyleUtils = useStyleUtils(); useEffect(() => { - onToggleKeyboard(isKeyboardShown); + onToggleKeyboard?.(isKeyboardShown); }); /** @@ -75,7 +71,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused } }, [password]); - const handleFailureToLoadPDF = (error) => { + const handleFailureToLoadPDF = ((error: Error) => { if (error.message.match(/password/i)) { initiatePasswordChallenge(); return; @@ -84,16 +80,17 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused setShouldShowLoadingIndicator(false); setShouldRequestPassword(false); setShouldAttemptPDFLoad(false); - }; + // eslint-disable-next-line @typescript-eslint/ban-types + }) as (error: object) => void; /** * When the password is submitted via PDFPasswordForm, save the password * in state and attempt to load the PDF. Also show the loading indicator * since react-native-pdf/PDF will need to reload the PDF. * - * @param {String} pdfPassword Password submitted via PDFPasswordForm + * @param pdfPassword Password submitted via PDFPasswordForm */ - const attemptPDFLoadWithPassword = (pdfPassword) => { + const attemptPDFLoadWithPassword = (pdfPassword: string) => { // Render react-native-pdf/PDF so that it can validate the password. // Note that at this point in the password challenge, shouldRequestPassword is true. // Thus react-native-pdf/PDF will be rendered - but not visible. @@ -104,10 +101,10 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused /** * After the PDF is successfully loaded hide PDFPasswordForm and the loading * indicator. - * @param {Number} numberOfPages - * @param {Number} path - Path to cache location + * @param numberOfPages + * @param path - Path to cache location */ - const finishPDFLoad = (numberOfPages, path) => { + const finishPDFLoad = (numberOfPages: number, path: string) => { setShouldRequestPassword(false); setShouldShowLoadingIndicator(false); setSuccessToLoadPDF(true); @@ -115,7 +112,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused }; function renderPDFView() { - const pdfStyles = [themeStyles.imageModalPDF, StyleUtils.getWidthAndHeightStyle(windowWidth, windowHeight)]; + const pdfStyles: StyleProp = [themeStyles.imageModalPDF, StyleUtils.getWidthAndHeightStyle(windowWidth, windowHeight)]; // If we haven't yet successfully validated the password and loaded the PDF, // then we need to hide the react-native-pdf/PDF component so that PDFPasswordForm @@ -151,7 +148,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused {shouldRequestPassword && ( setIsPasswordInvalid(false)} isPasswordInvalid={isPasswordInvalid} @@ -168,6 +165,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused onPress={onPress} style={[themeStyles.flex1, themeStyles.alignSelfStretch, !failedToLoadPDF && themeStyles.flexRow]} accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing accessibilityLabel={fileName || translate('attachmentView.unknownFilename')} > {renderPDFView()} @@ -178,7 +176,5 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused } PDFView.displayName = 'PDFView'; -PDFView.propTypes = propTypes; -PDFView.defaultProps = defaultProps; export default PDFView; diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx new file mode 100644 index 000000000000..034ba76267f0 --- /dev/null +++ b/src/components/PDFView/index.tsx @@ -0,0 +1,133 @@ +import 'core-js/features/array/at'; +import React, {memo, useCallback, useEffect, useRef, useState} from 'react'; +import {PDFPreviewer} from 'react-fast-pdf'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import variables from '@styles/variables'; +import * as CanvasSize from '@userActions/CanvasSize'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import PDFPasswordForm from './PDFPasswordForm'; +import type {PDFViewOnyxProps, PDFViewProps} from './types'; + +function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, errorLabelStyles, maxCanvasArea, maxCanvasHeight, maxCanvasWidth, style}: PDFViewProps) { + const [isKeyboardOpen, setIsKeyboardOpen] = useState(false); + const styles = useThemeStyles(); + const {windowHeight, isSmallScreenWidth} = useWindowDimensions(); + const prevWindowHeight = useRef(windowHeight); + const {translate} = useLocalize(); + + /** + * On small screens notify parent that the keyboard has opened or closed. + * + * @param {Boolean} isKeyboardOpen True if keyboard is open + */ + const toggleKeyboardOnSmallScreens = useCallback( + (isKBOpen: boolean) => { + if (!isSmallScreenWidth) { + return; + } + setIsKeyboardOpen(isKBOpen); + onToggleKeyboard?.(isKeyboardOpen); + }, + [isKeyboardOpen, isSmallScreenWidth, onToggleKeyboard], + ); + + /** + * Verify that the canvas limits have been calculated already, if not calculate them and put them in Onyx + */ + const retrieveCanvasLimits = () => { + if (!maxCanvasArea) { + CanvasSize.retrieveMaxCanvasArea(); + } + + if (!maxCanvasHeight) { + CanvasSize.retrieveMaxCanvasHeight(); + } + + if (!maxCanvasWidth) { + CanvasSize.retrieveMaxCanvasWidth(); + } + }; + + useEffect(() => { + retrieveCanvasLimits(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // shouldComponentUpdate(nextProps, nextState) { + // return !_.isEqual(state, nextState) || !_.isEqual(props, nextProps); + // } + + useEffect(() => { + if (!isKeyboardOpen && windowHeight < prevWindowHeight.current - 100) { + toggleKeyboardOnSmallScreens(true); + } else if (isKeyboardOpen && windowHeight > prevWindowHeight.current) { + toggleKeyboardOnSmallScreens(false); + } + }, [isKeyboardOpen, toggleKeyboardOnSmallScreens, windowHeight]); + + const renderPDFView = () => { + const outerContainerStyle = [styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]; + + return ( + + } + ErrorComponent={{translate('attachmentView.failedToLoadPDF')}} + renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( + + )} + /> + + ); + }; + + return onPress ? ( + + {renderPDFView()} + + ) : ( + renderPDFView() + ); +} + +export default withOnyx({ + maxCanvasArea: { + key: ONYXKEYS.MAX_CANVAS_AREA, + }, + maxCanvasHeight: { + key: ONYXKEYS.MAX_CANVAS_HEIGHT, + }, + maxCanvasWidth: { + key: ONYXKEYS.MAX_CANVAS_WIDTH, + }, +})(memo(PDFView)); diff --git a/src/components/PDFView/types.ts b/src/components/PDFView/types.ts new file mode 100644 index 000000000000..4b3228b3ce76 --- /dev/null +++ b/src/components/PDFView/types.ts @@ -0,0 +1,47 @@ +// eslint-disable-next-line no-restricted-imports +import type {CSSProperties} from 'react'; +import type {GestureResponderEvent, StyleProp, TextStyle} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; + +type PDFViewBaseProps = { + /** URL to full-sized image */ + sourceURL: string; + + /** PDF file name */ + fileName?: string; + + /** (web only) Additional style props */ + style?: CSSProperties; + + /** Notify parent that the keyboard has opened or closed */ + onToggleKeyboard?: (isKeyboardOpen: boolean) => void; + + /** Handles press events like toggling attachment arrows natively */ + onPress?: (event?: GestureResponderEvent | KeyboardEvent | undefined) => void | Promise; + + /** Handles scale changed event in PDF component */ + onScaleChanged?: (newScale: number) => void; + + /** Handles load complete event in PDF component */ + onLoadComplete: (path: string) => void; + + /** Should focus to the password input */ + isFocused?: boolean; + + /** Styles for the error label */ + errorLabelStyles: StyleProp; +}; + +type PDFViewOnyxProps = { + maxCanvasArea: OnyxEntry; + maxCanvasHeight: OnyxEntry; + maxCanvasWidth: OnyxEntry; +}; + +type PDFViewProps = PDFViewBaseProps & PDFViewOnyxProps; + +type PDFViewNativeProps = PDFViewBaseProps & { + onPress?: (page: number, x: number, y: number) => void; +}; + +export type {PDFViewNativeProps, PDFViewProps, PDFViewOnyxProps}; diff --git a/src/components/TextLink.tsx b/src/components/TextLink.tsx index affd6b046dee..8b44b2edbe8c 100644 --- a/src/components/TextLink.tsx +++ b/src/components/TextLink.tsx @@ -20,7 +20,7 @@ type PressProps = { href?: undefined; /** Overwrites the default link behavior with a custom callback */ - onPress: () => void; + onPress: () => void | ((event: GestureResponderEvent) => void); }; type TextLinkProps = (LinkProps | PressProps) & From ed98dda93b0c896f0bae766658534d87f0e85655 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 28 Mar 2024 22:58:55 +0000 Subject: [PATCH 2/2] refactor(typescript): apply pull request suggestions --- .../AttachmentView/AttachmentViewPdf/types.ts | 8 ++---- src/components/PDFView/PDFInfoMessage.tsx | 4 ++- src/components/PDFView/index.tsx | 28 +++++++++++-------- src/components/PDFView/types.ts | 17 ++++++----- src/components/TextLink.tsx | 12 ++++---- 5 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts index 72dc2d76c902..605be3b25ec4 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts @@ -1,14 +1,12 @@ -// eslint-disable-next-line no-restricted-imports -import type {CSSProperties} from 'react'; -import type {StyleProp, TextStyle} from 'react-native'; +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import type {AttachmentViewProps} from '..'; type AttachmentViewPdfProps = Pick & { encryptedSourceUrl: string; onLoadComplete: (path: string) => void; - /** (web only) Additional style props */ - style?: CSSProperties; + /** Additional style props */ + style?: StyleProp; /** Styles for the error label */ errorLabelStyles?: StyleProp; diff --git a/src/components/PDFView/PDFInfoMessage.tsx b/src/components/PDFView/PDFInfoMessage.tsx index 03daa7930290..f1c6e3c8984e 100644 --- a/src/components/PDFView/PDFInfoMessage.tsx +++ b/src/components/PDFView/PDFInfoMessage.tsx @@ -1,3 +1,4 @@ +import type {KeyboardEvent} from 'react'; import React from 'react'; import type {GestureResponderEvent} from 'react-native'; import {View} from 'react-native'; @@ -12,13 +13,14 @@ import variables from '@styles/variables'; type PDFInfoMessageProps = { /** Callback function to indicate that PDF password form should be shown */ - onShowForm: () => void | ((event: GestureResponderEvent) => void); + onShowForm: (event: GestureResponderEvent | KeyboardEvent) => void; }; function PDFInfoMessage({onShowForm}: PDFInfoMessageProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); + return ( { @@ -58,20 +61,23 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, err useEffect(() => { retrieveCanvasLimits(); + // This rule needs to be applied so that this effect is executed only when the component is mounted // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - // shouldComponentUpdate(nextProps, nextState) { - // return !_.isEqual(state, nextState) || !_.isEqual(props, nextProps); - // } - useEffect(() => { - if (!isKeyboardOpen && windowHeight < prevWindowHeight.current - 100) { + // Use window height changes to toggle the keyboard. To maintain keyboard state + // on all platforms we also use focus/blur events. So we need to make sure here + // that we avoid redundant keyboard toggling. + // Minus 100px is needed to make sure that when the internet connection is + // disabled in android chrome and a small 'No internet connection' text box appears, + // we do not take it as a sign to open the keyboard + if (!isKeyboardOpen && windowHeight < prevWindowHeight - 100) { toggleKeyboardOnSmallScreens(true); - } else if (isKeyboardOpen && windowHeight > prevWindowHeight.current) { + } else if (isKeyboardOpen && windowHeight > prevWindowHeight) { toggleKeyboardOnSmallScreens(false); } - }, [isKeyboardOpen, toggleKeyboardOnSmallScreens, windowHeight]); + }, [isKeyboardOpen, prevWindowHeight, toggleKeyboardOnSmallScreens, windowHeight]); const renderPDFView = () => { const outerContainerStyle = [styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]; @@ -82,7 +88,7 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, err tabIndex={0} > ; /** Notify parent that the keyboard has opened or closed */ onToggleKeyboard?: (isKeyboardOpen: boolean) => void; /** Handles press events like toggling attachment arrows natively */ - onPress?: (event?: GestureResponderEvent | KeyboardEvent | undefined) => void | Promise; + onPress?: (event?: GestureResponderEvent | KeyboardEvent) => void; /** Handles scale changed event in PDF component */ onScaleChanged?: (newScale: number) => void; @@ -29,12 +27,17 @@ type PDFViewBaseProps = { isFocused?: boolean; /** Styles for the error label */ - errorLabelStyles: StyleProp; + errorLabelStyles?: StyleProp; }; type PDFViewOnyxProps = { + // Maximum canvas area to render the PDF preview maxCanvasArea: OnyxEntry; + + // Maximum canvas height to render the PDF preview maxCanvasHeight: OnyxEntry; + + // Maximum canvas width to render the PDF preview maxCanvasWidth: OnyxEntry; }; diff --git a/src/components/TextLink.tsx b/src/components/TextLink.tsx index 8b44b2edbe8c..b6ffb14753c1 100644 --- a/src/components/TextLink.tsx +++ b/src/components/TextLink.tsx @@ -1,4 +1,4 @@ -import type {ForwardedRef, KeyboardEventHandler, MouseEventHandler} from 'react'; +import type {ForwardedRef, KeyboardEvent, KeyboardEventHandler, MouseEventHandler} from 'react'; import React, {forwardRef} from 'react'; // eslint-disable-next-line no-restricted-imports import type {GestureResponderEvent, Text as RNText, StyleProp, TextStyle} from 'react-native'; @@ -20,7 +20,7 @@ type PressProps = { href?: undefined; /** Overwrites the default link behavior with a custom callback */ - onPress: () => void | ((event: GestureResponderEvent) => void); + onPress: (event: GestureResponderEvent | KeyboardEvent) => void; }; type TextLinkProps = (LinkProps | PressProps) & @@ -36,9 +36,9 @@ function TextLink({href, onPress, children, style, onMouseDown = (event) => even const {environmentURL} = useEnvironment(); const styles = useThemeStyles(); - const openLink = () => { + const openLink = (event: GestureResponderEvent | KeyboardEvent) => { if (onPress) { - onPress(); + onPress(event); } else { Link.openLink(href, environmentURL); } @@ -47,7 +47,7 @@ function TextLink({href, onPress, children, style, onMouseDown = (event) => even const openLinkOnTap = (event: GestureResponderEvent) => { event.preventDefault(); - openLink(); + openLink(event); }; const openLinkOnEnterKey: KeyboardEventHandler = (event) => { @@ -56,7 +56,7 @@ function TextLink({href, onPress, children, style, onMouseDown = (event) => even } event.preventDefault(); - openLink(); + openLink(event); }; return (