You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Chore: Mobile: Use stronger types in side-menu and support FontAwesome icons (#11247)
				
					
				
			This commit is contained in:
		| @@ -1,13 +1,15 @@ | ||||
| const React = require('react'); | ||||
| import * as React from 'react'; | ||||
| import { useMemo, useEffect, useCallback, useContext } from 'react'; | ||||
| const { Easing, Animated, TouchableOpacity, Text, StyleSheet, ScrollView, View, Image } = require('react-native'); | ||||
| const { connect } = require('react-redux'); | ||||
| const Icon = require('react-native-vector-icons/Ionicons').default; | ||||
| import { Easing, Animated, TouchableOpacity, Text, StyleSheet, ScrollView, View, Image, ImageStyle } from 'react-native'; | ||||
| import { Dispatch } from 'redux'; | ||||
| import { connect } from 'react-redux'; | ||||
| const IonIcon = require('react-native-vector-icons/Ionicons').default; | ||||
| import Icon from './Icon'; | ||||
| import Folder from '@joplin/lib/models/Folder'; | ||||
| import Synchronizer from '@joplin/lib/Synchronizer'; | ||||
| import NavService from '@joplin/lib/services/NavService'; | ||||
| import { _ } from '@joplin/lib/locale'; | ||||
| import { ThemeStyle, themeStyle } from './global-style'; | ||||
| import { themeStyle } from './global-style'; | ||||
| import { buildFolderTree, isFolderSelected, renderFolders } from '@joplin/lib/components/shared/side-menu-shared'; | ||||
| import { FolderEntity, FolderIcon, FolderIconType } from '@joplin/lib/services/database/types'; | ||||
| import { AppState } from '../utils/types'; | ||||
| @@ -19,26 +21,24 @@ import restoreItems from '@joplin/lib/services/trash/restoreItems'; | ||||
| import emptyTrash from '@joplin/lib/services/trash/emptyTrash'; | ||||
| import { ModelType } from '@joplin/lib/BaseModel'; | ||||
| import { DialogContext } from './DialogManager'; | ||||
| import { TextStyle, ViewStyle } from 'react-native'; | ||||
| import { StateDecryptionWorker, StateResourceFetcher } from '@joplin/lib/reducer'; | ||||
| const { TouchableRipple } = require('react-native-paper'); | ||||
| const { substrWithEllipsis } = require('@joplin/lib/string-utils'); | ||||
|  | ||||
| interface Props { | ||||
| 	syncStarted: boolean; | ||||
| 	themeId: number; | ||||
| 	// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied | ||||
| 	dispatch: Function; | ||||
| 	dispatch: Dispatch; | ||||
| 	collapsedFolderIds: string[]; | ||||
| 	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied | ||||
| 	syncReport: any; | ||||
| 	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied | ||||
| 	decryptionWorker: any; | ||||
| 	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied | ||||
| 	resourceFetcher: any; | ||||
| 	decryptionWorker: StateDecryptionWorker; | ||||
| 	resourceFetcher: StateResourceFetcher; | ||||
| 	syncOnlyOverWifi: boolean; | ||||
| 	isOnMobileData: boolean; | ||||
| 	notesParentType: string; | ||||
| 	folders: FolderEntity[]; | ||||
| 	opacity: number; | ||||
| 	profileConfig: ProfileConfig; | ||||
| 	inboxJopId: string; | ||||
| 	selectedFolderId: string; | ||||
| @@ -54,8 +54,7 @@ const syncIconRotation = syncIconRotationValue.interpolate({ | ||||
|  | ||||
| const folderIconRightMargin = 10; | ||||
|  | ||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied | ||||
| let syncIconAnimation: any; | ||||
| let syncIconAnimation: Animated.CompositeAnimation|null = null; | ||||
|  | ||||
| const SideMenuContentComponent = (props: Props) => { | ||||
| 	const alwaysShowFolderIcons = useMemo(() => Folder.shouldShowFolderIcons(props.folders), [props.folders]); | ||||
| @@ -63,27 +62,48 @@ const SideMenuContentComponent = (props: Props) => { | ||||
| 	const styles_ = useMemo(() => { | ||||
| 		const theme = themeStyle(props.themeId); | ||||
|  | ||||
| 		// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied | ||||
| 		const styles: any = { | ||||
| 		const buttonStyle: ViewStyle = { | ||||
| 			flex: 1, | ||||
| 			flexDirection: 'row', | ||||
| 			flexBasis: 'auto', | ||||
| 			height: 36, | ||||
| 			alignItems: 'center', | ||||
| 			paddingLeft: theme.marginLeft, | ||||
| 			paddingRight: theme.marginRight, | ||||
| 		}; | ||||
| 		const buttonTextStyle: TextStyle = { | ||||
| 			flex: 1, | ||||
| 			color: theme.color, | ||||
| 			paddingLeft: 10, | ||||
| 			fontSize: theme.fontSize, | ||||
| 		}; | ||||
| 		const sidebarIconStyle: TextStyle = { | ||||
| 			fontSize: 22, | ||||
| 			color: theme.color, | ||||
| 			width: 26, | ||||
| 			textAlign: 'center', | ||||
| 			textAlignVertical: 'center', | ||||
| 		}; | ||||
| 		const folderIconBase: ViewStyle&ImageStyle = { | ||||
| 			marginRight: folderIconRightMargin, | ||||
| 			width: 27, | ||||
| 		}; | ||||
| 		const folderButtonStyle: ViewStyle = { | ||||
| 			...buttonStyle, | ||||
| 			paddingLeft: 0, | ||||
| 		}; | ||||
| 		const sideButtonStyle: ViewStyle = { | ||||
| 			...buttonStyle, | ||||
| 			flex: 0, | ||||
| 		}; | ||||
|  | ||||
| 		const styles = StyleSheet.create({ | ||||
| 			menu: { | ||||
| 				flex: 1, | ||||
| 				backgroundColor: theme.backgroundColor, | ||||
| 			}, | ||||
| 			button: { | ||||
| 				flex: 1, | ||||
| 				flexDirection: 'row', | ||||
| 				flexBasis: 'auto', | ||||
| 				height: 36, | ||||
| 				alignItems: 'center', | ||||
| 				paddingLeft: theme.marginLeft, | ||||
| 				paddingRight: theme.marginRight, | ||||
| 			}, | ||||
| 			buttonText: { | ||||
| 				flex: 1, | ||||
| 				color: theme.color, | ||||
| 				paddingLeft: 10, | ||||
| 				fontSize: theme.fontSize, | ||||
| 			}, | ||||
| 			button: buttonStyle, | ||||
| 			buttonText: buttonTextStyle, | ||||
| 			syncStatus: { | ||||
| 				paddingLeft: theme.marginLeft, | ||||
| 				paddingRight: theme.marginRight, | ||||
| @@ -91,31 +111,46 @@ const SideMenuContentComponent = (props: Props) => { | ||||
| 				fontSize: theme.fontSizeSmaller, | ||||
| 				flex: 0, | ||||
| 			}, | ||||
| 			sidebarIcon: { | ||||
| 				fontSize: 22, | ||||
| 				color: theme.color, | ||||
| 				width: 26, | ||||
| 				textAlign: 'center', | ||||
| 				textAlignVertical: 'center', | ||||
| 			sidebarIcon: sidebarIconStyle, | ||||
| 			folderButton: folderButtonStyle, | ||||
| 			folderButtonText: { | ||||
| 				...buttonTextStyle, | ||||
| 				paddingLeft: 0, | ||||
| 			}, | ||||
| 		}; | ||||
| 			folderButtonSelected: { | ||||
| 				...folderButtonStyle, | ||||
| 				backgroundColor: theme.selectedColor, | ||||
| 			}, | ||||
| 			folderToggleIcon: { | ||||
| 				...theme.icon, | ||||
| 				color: theme.colorFaded, | ||||
| 				paddingTop: 3, | ||||
| 			}, | ||||
| 			sideButton: sideButtonStyle, | ||||
| 			sideButtonSelected: { | ||||
| 				...sideButtonStyle, | ||||
| 			}, | ||||
| 			sideButtonText: { | ||||
| 				...buttonTextStyle, | ||||
| 			}, | ||||
| 			folderBaseIcon: { | ||||
| 				...sidebarIconStyle, | ||||
| 				...folderIconBase, | ||||
| 			}, | ||||
| 			folderEmojiIcon: { | ||||
| 				...sidebarIconStyle, | ||||
| 				...folderIconBase, | ||||
| 				textAlign: undefined, | ||||
| 				fontSize: theme.fontSize, | ||||
| 			}, | ||||
| 			folderImageIcon: { | ||||
| 				...folderIconBase, | ||||
| 				height: 20, | ||||
| 				resizeMode: 'contain', | ||||
| 			}, | ||||
| 		}); | ||||
|  | ||||
| 		styles.folderButton = { ...styles.button }; | ||||
| 		styles.folderButton.paddingLeft = 0; | ||||
| 		styles.folderButtonText = { ...styles.buttonText, paddingLeft: 0 }; | ||||
| 		styles.folderButtonSelected = { ...styles.folderButton }; | ||||
| 		styles.folderButtonSelected.backgroundColor = theme.selectedColor; | ||||
| 		styles.folderIcon = { ...theme.icon }; | ||||
| 		styles.folderIcon.color = theme.colorFaded; // '#0072d5'; | ||||
| 		styles.folderIcon.paddingTop = 3; | ||||
|  | ||||
| 		styles.sideButton = { ...styles.button, flex: 0 }; | ||||
| 		styles.sideButtonSelected = { ...styles.sideButton, backgroundColor: theme.selectedColor }; | ||||
| 		styles.sideButtonText = { ...styles.buttonText }; | ||||
|  | ||||
| 		styles.emptyFolderIcon = { ...styles.sidebarIcon, marginRight: folderIconRightMargin, width: 26 }; | ||||
|  | ||||
| 		return StyleSheet.create(styles); | ||||
| 		return styles; | ||||
| 	}, [props.themeId]); | ||||
|  | ||||
| 	useEffect(() => { | ||||
| @@ -374,21 +409,23 @@ const SideMenuContentComponent = (props: Props) => { | ||||
| 		if (actionDone === 'auth') props.dispatch({ type: 'SIDE_MENU_CLOSE' }); | ||||
| 	}, [performSync, props.dispatch]); | ||||
|  | ||||
| 	const renderFolderIcon = (folderId: string, theme: ThemeStyle, folderIcon: FolderIcon) => { | ||||
| 	const renderFolderIcon = (folderId: string, folderIcon: FolderIcon) => { | ||||
| 		if (!folderIcon) { | ||||
| 			if (folderId === getTrashFolderId()) { | ||||
| 				folderIcon = getTrashFolderIcon(FolderIconType.Emoji); | ||||
| 			} else if (alwaysShowFolderIcons) { | ||||
| 				return <Icon name="folder-outline" style={styles_.emptyFolderIcon} />; | ||||
| 				return <IonIcon name="folder-outline" style={styles_.folderBaseIcon} />; | ||||
| 			} else { | ||||
| 				return null; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (folderIcon.type === 1) { // FolderIconType.Emoji | ||||
| 			return <Text style={{ fontSize: theme.fontSize, marginRight: folderIconRightMargin, width: 27 }}>{folderIcon.emoji}</Text>; | ||||
| 		} else if (folderIcon.type === 2) { // FolderIconType.DataUrl | ||||
| 			return <Image style={{ width: 27, height: 20, marginRight: folderIconRightMargin, resizeMode: 'contain' }} source={{ uri: folderIcon.dataUrl }}/>; | ||||
| 		if (folderIcon.type === FolderIconType.Emoji) { | ||||
| 			return <Text style={styles_.folderEmojiIcon}>{folderIcon.emoji}</Text>; | ||||
| 		} else if (folderIcon.type === FolderIconType.DataUrl) { | ||||
| 			return <Image style={styles_.folderImageIcon} source={{ uri: folderIcon.dataUrl }}/>; | ||||
| 		} else if (folderIcon.type === FolderIconType.FontAwesome) { | ||||
| 			return <Icon style={styles_.folderBaseIcon} name={folderIcon.name} accessibilityLabel={''}/>; | ||||
| 		} else { | ||||
| 			throw new Error(`Unsupported folder icon type: ${folderIcon.type}`); | ||||
| 		} | ||||
| @@ -397,8 +434,7 @@ const SideMenuContentComponent = (props: Props) => { | ||||
| 	const renderFolderItem = (folder: FolderEntity, hasChildren: boolean, depth: number) => { | ||||
| 		const theme = themeStyle(props.themeId); | ||||
|  | ||||
| 		// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied | ||||
| 		const folderButtonStyle: any = { | ||||
| 		const folderButtonStyle: ViewStyle = { | ||||
| 			flex: 1, | ||||
| 			flexDirection: 'row', | ||||
| 			flexBasis: 'auto', | ||||
| @@ -411,20 +447,18 @@ const SideMenuContentComponent = (props: Props) => { | ||||
| 		if (selected) folderButtonStyle.backgroundColor = theme.selectedColor; | ||||
| 		folderButtonStyle.paddingLeft = depth * 10 + theme.marginLeft; | ||||
|  | ||||
| 		// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied | ||||
| 		const iconWrapperStyle: any = { paddingLeft: 10, paddingRight: 10 }; | ||||
| 		const iconWrapperStyle: ViewStyle = { paddingLeft: 10, paddingRight: 10 }; | ||||
| 		if (selected) iconWrapperStyle.backgroundColor = theme.selectedColor; | ||||
|  | ||||
| 		let iconWrapper = null; | ||||
|  | ||||
| 		const collapsed = props.collapsedFolderIds.indexOf(folder.id) >= 0; | ||||
| 		const iconName = collapsed ? 'chevron-down' : 'chevron-up'; | ||||
| 		const iconComp = <Icon name={iconName} style={styles_.folderIcon} />; | ||||
| 		const iconComp = <IonIcon name={iconName} style={styles_.folderToggleIcon} />; | ||||
|  | ||||
| 		iconWrapper = !hasChildren ? null : ( | ||||
| 			<TouchableOpacity | ||||
| 				style={iconWrapperStyle} | ||||
| 				folderid={folder.id} | ||||
| 				onPress={() => { | ||||
| 					if (hasChildren) folder_togglePress(folder); | ||||
| 				}} | ||||
| @@ -455,7 +489,7 @@ const SideMenuContentComponent = (props: Props) => { | ||||
| 					role='button' | ||||
| 				> | ||||
| 					<View style={folderButtonStyle}> | ||||
| 						{renderFolderIcon(folder.id, theme, folderIcon)} | ||||
| 						{renderFolderIcon(folder.id, folderIcon)} | ||||
| 						<Text numberOfLines={1} style={styles_.folderButtonText}> | ||||
| 							{Folder.displayTitle(folder)} | ||||
| 						</Text> | ||||
| @@ -466,9 +500,8 @@ const SideMenuContentComponent = (props: Props) => { | ||||
| 		); | ||||
| 	}; | ||||
|  | ||||
| 	// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied | ||||
| 	const renderSidebarButton = (key: string, title: string, iconName: string, onPressHandler: Function = null, selected = false) => { | ||||
| 		let icon = <Icon name={iconName} style={styles_.sidebarIcon} aria-hidden={true} />; | ||||
| 	const renderSidebarButton = (key: string, title: string, iconName: string, onPressHandler: ()=> void = null, selected = false) => { | ||||
| 		let icon = <IonIcon name={iconName} style={styles_.sidebarIcon} aria-hidden={true} />; | ||||
|  | ||||
| 		if (key === 'synchronize_button') { | ||||
| 			icon = <Animated.View style={{ transform: [{ rotate: syncIconRotation }] }}>{icon}</Animated.View>; | ||||
| @@ -590,7 +623,7 @@ const SideMenuContentComponent = (props: Props) => { | ||||
|  | ||||
| 	return ( | ||||
| 		<View style={style}> | ||||
| 			<View style={{ flex: 1, opacity: props.opacity }}> | ||||
| 			<View style={{ flex: 1 }}> | ||||
| 				<ScrollView scrollsToTop={false} style={styles_.menu}> | ||||
| 					{items} | ||||
| 				</ScrollView> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user