Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TS migration] Migrate AttachmentCarousel to typescript #36658

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a9315c4
migrate AttachmentView
SzymczakJ Feb 15, 2024
0f696ce
migrate AttachmentCarousel
SzymczakJ Feb 15, 2024
de7619a
migrate attachmentCarousel
SzymczakJ Feb 16, 2024
4146104
Merge branch 'main' into @szymczak/AttachmentCarousel
SzymczakJ Feb 16, 2024
e8ab36f
fix merge bugs
SzymczakJ Feb 16, 2024
b46b1c0
clean up code
SzymczakJ Feb 16, 2024
ca34bcc
Merge branch 'main' into @szymczak/AttachmentCarousel
SzymczakJ Feb 26, 2024
b51d1c9
fix AttachmentView memo
SzymczakJ Feb 26, 2024
e6d06bf
fix PR comments
SzymczakJ Feb 26, 2024
eba12a9
Merge branch 'main' into @szymczak/AttachmentCarousel
SzymczakJ Feb 26, 2024
d60d76b
fix ts errors
SzymczakJ Feb 27, 2024
5a29704
Merge branch 'main' into @szymczak/AttachmentCarousel
SzymczakJ Feb 27, 2024
7560f8b
fix AttachmentView crash
SzymczakJ Feb 27, 2024
adbfc65
Merge branch 'main' into @szymczak/AttachmentCarousel
SzymczakJ Feb 28, 2024
ca76ff6
fix lint error
SzymczakJ Feb 28, 2024
f188c8c
merge main
SzymczakJ Mar 11, 2024
7a9853a
fix type errors
SzymczakJ Mar 11, 2024
a8b03df
fix lint
SzymczakJ Mar 11, 2024
b7ad3e2
fix comments
SzymczakJ Mar 13, 2024
f51a489
fix comments
SzymczakJ Mar 13, 2024
d69d776
Merge branch 'main' into @szymczak/AttachmentCarousel
SzymczakJ Mar 14, 2024
8c8ddf3
fix pr comments
SzymczakJ Mar 14, 2024
d746b6c
fix pr comments
SzymczakJ Mar 14, 2024
1f71572
fix PR comments
SzymczakJ Mar 15, 2024
eaef7fb
fix pr comments
SzymczakJ Mar 18, 2024
8629609
fix pr comments
SzymczakJ Mar 18, 2024
7c9015b
fix Pr commments
SzymczakJ Mar 19, 2024
7805777
fix PR comments
SzymczakJ Mar 19, 2024
8a5c9c5
fix type error
SzymczakJ Mar 19, 2024
efbdf5c
fix pr comments
SzymczakJ Mar 20, 2024
b8d4a61
fix type errors
SzymczakJ Mar 20, 2024
b63dcdc
Merge branch 'main' into @szymczak/AttachmentCarousel
SzymczakJ Mar 21, 2024
ad79213
Merge branch 'main' into @szymczak/AttachmentCarousel
SzymczakJ Mar 21, 2024
40a3688
Merge branch 'main' into @szymczak/AttachmentCarousel
SzymczakJ Mar 24, 2024
0844b82
fi pr comments
SzymczakJ Mar 24, 2024
f5a6712
Merge branch 'main' into @szymczak/AttachmentCarousel
SzymczakJ Mar 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow
role={CONST.ROLE.BUTTON}
>
<AttachmentView
// @ts-expect-error TODO: Remove this once AttachmentView (https://github.com/Expensify/App/issues/25150) is migrated to TypeScript.
source={sourceURLWithAuth}
file={{name: displayName}}
shouldShowDownloadIcon={!isOffline}
Expand Down
30 changes: 10 additions & 20 deletions src/components/AttachmentModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type ModalType from '@src/types/utils/ModalType';
import AttachmentCarousel from './Attachments/AttachmentCarousel';
import AttachmentCarouselPagerContext from './Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext';
import AttachmentView from './Attachments/AttachmentView';
import type {Attachment} from './Attachments/types';
import BlockingView from './BlockingViews/BlockingView';
import Button from './Button';
import ConfirmModal from './ConfirmModal';
Expand Down Expand Up @@ -61,15 +62,6 @@ type AttachmentModalOnyxProps = {
parentReportActions: OnyxEntry<OnyxTypes.ReportActions>;
};

type Attachment = {
source: AvatarSource;
isAuthTokenRequired: boolean;
file: FileObject;
isReceipt: boolean;
hasBeenFlagged?: boolean;
reportActionID?: string;
};

type ImagePickerResponse = {
height: number;
name: string;
Expand All @@ -79,7 +71,7 @@ type ImagePickerResponse = {
width: number;
};

type FileObject = File | ImagePickerResponse;
type FileObject = Partial<File | ImagePickerResponse>;

type ChildrenProps = {
displayFileInModal: (data: FileObject) => void;
Expand Down Expand Up @@ -181,7 +173,7 @@ function AttachmentModal({
const [isAuthTokenRequiredState, setIsAuthTokenRequiredState] = useState(isAuthTokenRequired);
const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState<TranslationPaths | null>(null);
const [attachmentInvalidReason, setAttachmentInvalidReason] = useState<TranslationPaths | null>(null);
const [sourceState, setSourceState] = useState(() => source);
const [sourceState, setSourceState] = useState<AvatarSource>(() => source);
const [modalType, setModalType] = useState<ModalType>(CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE);
const [isConfirmButtonDisabled, setIsConfirmButtonDisabled] = useState(false);
const [confirmButtonFadeAnimation] = useState(() => new Animated.Value(1));
Expand All @@ -190,7 +182,7 @@ function AttachmentModal({
const {windowWidth, isSmallScreenWidth} = useWindowDimensions();
const isOverlayModalVisible = (isReceiptAttachment && isDeleteReceiptConfirmModalVisible) || (!isReceiptAttachment && isAttachmentInvalid);

const [file, setFile] = useState<Partial<FileObject> | undefined>(
const [file, setFile] = useState<FileObject | undefined>(
originalFileName
? {
name: originalFileName,
Expand All @@ -211,7 +203,7 @@ function AttachmentModal({
(attachment: Attachment) => {
setSourceState(attachment.source);
setFile(attachment.file);
setIsAuthTokenRequiredState(attachment.isAuthTokenRequired);
setIsAuthTokenRequiredState(attachment.isAuthTokenRequired ?? false);
onCarouselAttachmentChange(attachment);
},
[onCarouselAttachmentChange],
Expand All @@ -222,7 +214,7 @@ function AttachmentModal({
*/
const getModalType = useCallback(
(sourceURL: string, fileObject: FileObject) =>
sourceURL && (Str.isPDF(sourceURL) || (fileObject && Str.isPDF(fileObject.name || translate('attachmentView.unknownFilename'))))
sourceURL && (Str.isPDF(sourceURL) || (fileObject && Str.isPDF(fileObject.name ?? translate('attachmentView.unknownFilename'))))
? CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE
: CONST.MODAL.MODAL_TYPE.CENTERED,
[translate],
Expand Down Expand Up @@ -292,14 +284,14 @@ function AttachmentModal({
}, [transaction, report]);

const isValidFile = useCallback((fileObject: FileObject) => {
if (fileObject.size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
if (fileObject.size !== undefined && fileObject.size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
setIsAttachmentInvalid(true);
setAttachmentInvalidReasonTitle('attachmentPicker.attachmentTooLarge');
setAttachmentInvalidReason('attachmentPicker.sizeExceeded');
return false;
}

if (fileObject.size < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) {
if (fileObject.size !== undefined && fileObject.size < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) {
setIsAttachmentInvalid(true);
setAttachmentInvalidReasonTitle('attachmentPicker.attachmentTooSmall');
setAttachmentInvalidReason('attachmentPicker.sizeNotMet');
Expand Down Expand Up @@ -352,7 +344,7 @@ function AttachmentModal({
setSourceState(inputSource);
setFile(updatedFile);
setModalType(inputModalType);
} else {
} else if (fileObject.uri) {
const inputModalType = getModalType(fileObject.uri, fileObject);
setIsModalOpen(true);
setSourceState(fileObject.uri);
Expand Down Expand Up @@ -536,7 +528,6 @@ function AttachmentModal({
onNavigate={onNavigate}
onClose={closeModal}
source={source}
onToggleKeyboard={updateConfirmButtonVisibility}
setDownloadButtonVisibility={setDownloadButtonVisibility}
/>
) : (
Expand All @@ -546,7 +537,6 @@ function AttachmentModal({
!shouldShowNotFoundPage && (
<AttachmentCarouselPagerContext.Provider value={context}>
<AttachmentView
// @ts-expect-error TODO: Remove this once Attachments (https://github.com/Expensify/App/issues/24969) is migrated to TypeScript.
containerStyles={[styles.mh5]}
source={sourceForAttachmentView}
isAuthTokenRequired={isAuthTokenRequiredState}
Expand Down Expand Up @@ -637,4 +627,4 @@ export default withOnyx<AttachmentModalProps, AttachmentModalOnyxProps>({
},
})(memo(AttachmentModal));

export type {Attachment, FileObject};
export type {FileObject};
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import PropTypes from 'prop-types';
import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {PixelRatio, View} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';

const propTypes = {
type AttachmentCarouselCellRendererProps = {
/** Cell Container styles */
style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),
style?: StyleProp<ViewStyle>;
};

const defaultProps = {
style: [],
};

function AttachmentCarouselCellRenderer(props) {
function AttachmentCarouselCellRenderer(props: AttachmentCarouselCellRendererProps) {
const styles = useThemeStyles();
const {windowWidth, isSmallScreenWidth} = useWindowDimensions();
const modalStyles = styles.centeredModalStyles(isSmallScreenWidth, true);
Expand All @@ -28,8 +24,6 @@ function AttachmentCarouselCellRenderer(props) {
);
}

AttachmentCarouselCellRenderer.propTypes = propTypes;
AttachmentCarouselCellRenderer.defaultProps = defaultProps;
AttachmentCarouselCellRenderer.displayName = 'AttachmentCarouselCellRenderer';

export default React.memo(AttachmentCarouselCellRenderer);
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import {useEffect} from 'react';
import KeyboardShortcut from '@libs/KeyboardShortcut';
import CONST from '@src/CONST';

const propTypes = {
type CarouselActionsProps = {
/** Callback to cycle through attachments */
onCycleThroughAttachments: PropTypes.func.isRequired,
onCycleThroughAttachments: (deltaSlide: number) => void;
};

function CarouselActions({onCycleThroughAttachments}) {
function CarouselActions({onCycleThroughAttachments}: CarouselActionsProps) {
useEffect(() => {
const shortcutLeftConfig = CONST.KEYBOARD_SHORTCUTS.ARROW_LEFT;
const unsubscribeLeftKey = KeyboardShortcut.subscribe(
shortcutLeftConfig.shortcutKey,
(e) => {
if (lodashGet(e, 'target.blur')) {
(event) => {
if (event?.target instanceof HTMLElement) {
// prevents focus from highlighting around the modal
e.target.blur();
event.target.blur();
}

onCycleThroughAttachments(-1);
},
shortcutLeftConfig.descriptionKey,
Expand All @@ -29,12 +26,11 @@ function CarouselActions({onCycleThroughAttachments}) {
const shortcutRightConfig = CONST.KEYBOARD_SHORTCUTS.ARROW_RIGHT;
const unsubscribeRightKey = KeyboardShortcut.subscribe(
shortcutRightConfig.shortcutKey,
(e) => {
if (lodashGet(e, 'target.blur')) {
(event) => {
if (event?.target instanceof HTMLElement) {
// prevents focus from highlighting around the modal
e.target.blur();
event.target.blur();
}

onCycleThroughAttachments(1);
},
shortcutRightConfig.descriptionKey,
Expand All @@ -50,6 +46,4 @@ function CarouselActions({onCycleThroughAttachments}) {
return null;
}

CarouselActions.propTypes = propTypes;

export default CarouselActions;
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import _ from 'underscore';
import * as AttachmentCarouselViewPropTypes from '@components/Attachments/propTypes';
import type {Attachment} from '@components/Attachments/types';
import Button from '@components/Button';
import * as Expensicons from '@components/Icon/Expensicons';
import Tooltip from '@components/Tooltip';
Expand All @@ -11,36 +9,34 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';

const propTypes = {
type CarouselButtonsProps = {
/** Where the arrows should be visible */
shouldShowArrows: PropTypes.bool.isRequired,
shouldShowArrows: boolean;

/** The current page index */
page: PropTypes.number.isRequired,
page: number;

/** The attachments from the carousel */
attachments: AttachmentCarouselViewPropTypes.attachmentsPropType.isRequired,
attachments: Attachment[];

/** Callback to go one page back */
onBack: PropTypes.func.isRequired,
onBack: () => void;

/** Callback to go one page forward */
onForward: PropTypes.func.isRequired,
onForward: () => void;

autoHideArrow: PropTypes.func,
cancelAutoHideArrow: PropTypes.func,
};
/** Callback for autohiding carousel button arrows */
autoHideArrow?: () => void;

SzymczakJ marked this conversation as resolved.
Show resolved Hide resolved
const defaultProps = {
autoHideArrow: () => {},
cancelAutoHideArrow: () => {},
/** Callback for cancelling autohiding of carousel button arrows */
cancelAutoHideArrow?: () => void;
};
SzymczakJ marked this conversation as resolved.
Show resolved Hide resolved

function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward, cancelAutoHideArrow, autoHideArrow}) {
function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward, cancelAutoHideArrow, autoHideArrow}: CarouselButtonsProps) {
const theme = useTheme();
const styles = useThemeStyles();
const isBackDisabled = page === 0;
const isForwardDisabled = page === _.size(attachments) - 1;

const isForwardDisabled = page === attachments.length - 1;
const {translate} = useLocalize();
const {isSmallScreenWidth} = useWindowDimensions();

Expand Down Expand Up @@ -80,8 +76,6 @@ function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward
) : null;
}

CarouselButtons.propTypes = propTypes;
CarouselButtons.defaultProps = defaultProps;
CarouselButtons.displayName = 'CarouselButtons';

export default CarouselButtons;
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import PropTypes from 'prop-types';
import React, {useContext, useState} from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import AttachmentView from '@components/Attachments/AttachmentView';
import * as AttachmentsPropTypes from '@components/Attachments/propTypes';
import type {Attachment} from '@components/Attachments/types';
import Button from '@components/Button';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import SafeAreaConsumer from '@components/SafeAreaConsumer';
Expand All @@ -12,55 +12,27 @@ import useThemeStyles from '@hooks/useThemeStyles';
import ReportAttachmentsContext from '@pages/home/report/ReportAttachmentsContext';
import CONST from '@src/CONST';

const propTypes = {
type CarouselItemProps = {
/** Attachment required information such as the source and file name */
item: PropTypes.shape({
/** Report action ID of the attachment */
reportActionID: PropTypes.string,

/** Whether source URL requires authentication */
isAuthTokenRequired: PropTypes.bool,

/** URL to full-sized attachment or SVG function */
source: AttachmentsPropTypes.attachmentSourcePropType.isRequired,

/** Additional information about the attachment file */
file: PropTypes.shape({
/** File name of the attachment */
name: PropTypes.string.isRequired,
}).isRequired,

/** Whether the attachment has been flagged */
hasBeenFlagged: PropTypes.bool,

/** The id of the transaction related to the attachment */
transactionID: PropTypes.string,

duration: PropTypes.number,
}).isRequired,
item: Attachment;

/** onPress callback */
onPress: PropTypes.func,
onPress?: () => void;

isModalHovered: PropTypes.bool,
/** Whether attachment carousel modal is hovered over */
isModalHovered?: boolean;

SzymczakJ marked this conversation as resolved.
Show resolved Hide resolved
/** Whether the attachment is currently being viewed in the carousel */
isFocused: PropTypes.bool.isRequired,
};

const defaultProps = {
onPress: undefined,
isModalHovered: false,
isFocused: boolean;
};

function CarouselItem({item, onPress, isFocused, isModalHovered}) {
function CarouselItem({item, onPress, isFocused, isModalHovered}: CarouselItemProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {isAttachmentHidden} = useContext(ReportAttachmentsContext);
// eslint-disable-next-line es/no-nullish-coalescing-operators
const [isHidden, setIsHidden] = useState(() => isAttachmentHidden(item.reportActionID) ?? item.hasBeenFlagged);
const [isHidden, setIsHidden] = useState(() => (item.reportActionID ? isAttachmentHidden(item.reportActionID) : item.hasBeenFlagged));

const renderButton = (style) => (
const renderButton = (style: StyleProp<ViewStyle>) => (
<Button
small
style={style}
Expand All @@ -87,7 +59,8 @@ function CarouselItem({item, onPress, isFocused, isModalHovered}) {
style={[styles.attachmentRevealButtonContainer]}
onPress={onPress}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON}
accessibilityLabel={item.file.name || translate('attachmentView.unknownFilename')}
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
accessibilityLabel={item.file?.name || translate('attachmentView.unknownFilename')}
>
{children}
</PressableWithoutFeedback>
Expand All @@ -108,7 +81,7 @@ function CarouselItem({item, onPress, isFocused, isModalHovered}) {
reportActionID={item.reportActionID}
isHovered={isModalHovered}
isFocused={isFocused}
optionalVideoDuration={item.duration}
duration={item.duration}
/>
</View>

Expand All @@ -121,8 +94,6 @@ function CarouselItem({item, onPress, isFocused, isModalHovered}) {
);
}

CarouselItem.propTypes = propTypes;
CarouselItem.defaultProps = defaultProps;
CarouselItem.displayName = 'CarouselItem';

export default CarouselItem;
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import type {ForwardedRef} from 'react';
import {createContext} from 'react';
import type PagerView from 'react-native-pager-view';
import type {SharedValue} from 'react-native-reanimated';
import type {AttachmentSource} from '@components/Attachments/types';

/** The pager items array is used within the pager to render and navigate between the images */
type AttachmentCarouselPagerItems = {
/** The source of the image is used to identify each attachment/page in the pager */
source: string;
source: AttachmentSource;

/** The index of the pager item determines the order of the images in the pager */
index: number;
Expand Down
Loading
Loading