You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Chore: Refactor warning banner out of ScreenHeader into a separate file (#10268)
				
					
				
			This commit is contained in:
		| @@ -549,7 +549,10 @@ packages/app-mobile/components/NoteList.js | ||||
| packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js | ||||
| packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js | ||||
| packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js | ||||
| packages/app-mobile/components/ScreenHeader.js | ||||
| packages/app-mobile/components/ScreenHeader/WarningBanner.test.js | ||||
| packages/app-mobile/components/ScreenHeader/WarningBanner.js | ||||
| packages/app-mobile/components/ScreenHeader/WarningBox.js | ||||
| packages/app-mobile/components/ScreenHeader/index.js | ||||
| packages/app-mobile/components/SelectDateTimeDialog.js | ||||
| packages/app-mobile/components/SideMenu.js | ||||
| packages/app-mobile/components/TextInput.js | ||||
|   | ||||
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -529,7 +529,10 @@ packages/app-mobile/components/NoteList.js | ||||
| packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js | ||||
| packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js | ||||
| packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js | ||||
| packages/app-mobile/components/ScreenHeader.js | ||||
| packages/app-mobile/components/ScreenHeader/WarningBanner.test.js | ||||
| packages/app-mobile/components/ScreenHeader/WarningBanner.js | ||||
| packages/app-mobile/components/ScreenHeader/WarningBox.js | ||||
| packages/app-mobile/components/ScreenHeader/index.js | ||||
| packages/app-mobile/components/SelectDateTimeDialog.js | ||||
| packages/app-mobile/components/SideMenu.js | ||||
| packages/app-mobile/components/TextInput.js | ||||
|   | ||||
| Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 344 B | 
| @@ -0,0 +1,48 @@ | ||||
| import * as React from 'react'; | ||||
| import { WarningBannerComponent } from './WarningBanner'; | ||||
| import Setting from '@joplin/lib/models/Setting'; | ||||
| import NavService from '@joplin/lib/services/NavService'; | ||||
| import { render, screen, userEvent } from '@testing-library/react-native'; | ||||
|  | ||||
| interface WrapperProps { | ||||
| 	showMissingMasterKeyMessage?: boolean; | ||||
| 	hasDisabledSyncItems?: boolean; | ||||
| 	shouldUpgradeSyncTarget?: boolean; | ||||
| 	showShouldUpgradeSyncTargetMessage?: boolean; | ||||
| 	hasDisabledEncryptionItems?: boolean; | ||||
| 	mustUpgradeAppMessage?: string; | ||||
| } | ||||
|  | ||||
| const WarningBannerWrapper: React.FC<WrapperProps> = props => { | ||||
| 	return <WarningBannerComponent | ||||
| 		themeId={Setting.THEME_LIGHT} | ||||
| 		showMissingMasterKeyMessage={props.showMissingMasterKeyMessage ?? false} | ||||
| 		hasDisabledSyncItems={props.hasDisabledSyncItems ?? false} | ||||
| 		shouldUpgradeSyncTarget={props.shouldUpgradeSyncTarget ?? false} | ||||
| 		showShouldUpgradeSyncTargetMessage={props.showShouldUpgradeSyncTargetMessage ?? false} | ||||
| 		hasDisabledEncryptionItems={props.hasDisabledEncryptionItems ?? false} | ||||
| 		mustUpgradeAppMessage={props.mustUpgradeAppMessage ?? ''} | ||||
| 	/>; | ||||
| }; | ||||
|  | ||||
| describe('WarningBanner', () => { | ||||
| 	let navServiceMock: jest.Mock<(route: unknown)=> void>; | ||||
| 	beforeEach(() => { | ||||
| 		navServiceMock = jest.fn(); | ||||
| 		NavService.dispatch = navServiceMock; | ||||
| 		jest.useFakeTimers(); | ||||
| 	}); | ||||
|  | ||||
| 	test('the missing master key alert should link to the encryption config screen', async () => { | ||||
| 		render(<WarningBannerWrapper showMissingMasterKeyMessage={true}/>); | ||||
| 		expect(await screen.findAllByTestId('warning-box')).toHaveLength(1); | ||||
|  | ||||
| 		expect(navServiceMock).not.toHaveBeenCalled(); | ||||
|  | ||||
| 		const masterKeyWarning = screen.getByText(/decryption password/); | ||||
| 		const user = userEvent.setup(); | ||||
| 		await user.press(masterKeyWarning); | ||||
|  | ||||
| 		expect(navServiceMock.mock.lastCall).toMatchObject([{ routeName: 'EncryptionConfig' }]); | ||||
| 	}); | ||||
| }); | ||||
| @@ -0,0 +1,67 @@ | ||||
| import * as React from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { AppState } from '../../utils/types'; | ||||
| import WarningBox from './WarningBox'; | ||||
| import { _ } from '@joplin/lib/locale'; | ||||
| import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils'; | ||||
| import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils'; | ||||
| import Setting from '@joplin/lib/models/Setting'; | ||||
|  | ||||
| interface Props { | ||||
| 	themeId: number; | ||||
| 	showMissingMasterKeyMessage: boolean; | ||||
| 	hasDisabledSyncItems: boolean; | ||||
| 	shouldUpgradeSyncTarget: boolean; | ||||
| 	showShouldUpgradeSyncTargetMessage: boolean|undefined; | ||||
| 	hasDisabledEncryptionItems: boolean; | ||||
| 	mustUpgradeAppMessage: string; | ||||
| } | ||||
|  | ||||
|  | ||||
| export const WarningBannerComponent: React.FC<Props> = props => { | ||||
| 	const warningComps = []; | ||||
|  | ||||
| 	const renderWarningBox = (screen: string, message: string) => { | ||||
| 		return <WarningBox | ||||
| 			key={screen} | ||||
| 			themeId={props.themeId} | ||||
| 			targetScreen={screen} | ||||
| 			message={message} | ||||
| 			testID='warning-box' | ||||
| 		/>; | ||||
| 	}; | ||||
|  | ||||
| 	if (props.showMissingMasterKeyMessage) { | ||||
| 		warningComps.push(renderWarningBox('EncryptionConfig', _('Press to set the decryption password.'))); | ||||
| 	} | ||||
| 	if (props.hasDisabledSyncItems) { | ||||
| 		warningComps.push(renderWarningBox('Status', _('Some items cannot be synchronised. Press for more info.'))); | ||||
| 	} | ||||
| 	if (props.shouldUpgradeSyncTarget && props.showShouldUpgradeSyncTargetMessage !== false) { | ||||
| 		warningComps.push(renderWarningBox('UpgradeSyncTarget', _('The sync target needs to be upgraded. Press this banner to proceed.'))); | ||||
| 	} | ||||
| 	if (props.mustUpgradeAppMessage) { | ||||
| 		warningComps.push(renderWarningBox('UpgradeApp', props.mustUpgradeAppMessage)); | ||||
| 	} | ||||
| 	if (props.hasDisabledEncryptionItems) { | ||||
| 		warningComps.push(renderWarningBox('Status', _('Some items cannot be decrypted.'))); | ||||
| 	} | ||||
|  | ||||
| 	return warningComps; | ||||
| }; | ||||
|  | ||||
| export default connect((state: AppState) => { | ||||
| 	const syncInfo = localSyncInfoFromState(state); | ||||
|  | ||||
| 	return { | ||||
| 		themeId: state.settings.theme, | ||||
| 		hasDisabledEncryptionItems: state.hasDisabledEncryptionItems, | ||||
| 		noteSelectionEnabled: state.noteSelectionEnabled, | ||||
| 		selectedFolderId: state.selectedFolderId, | ||||
| 		notesParentType: state.notesParentType, | ||||
| 		showMissingMasterKeyMessage: showMissingMasterKeyMessage(syncInfo, state.notLoadedMasterKeys), | ||||
| 		hasDisabledSyncItems: state.hasDisabledSyncItems, | ||||
| 		shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO, | ||||
| 		mustUpgradeAppMessage: state.mustUpgradeAppMessage, | ||||
| 	}; | ||||
| })(WarningBannerComponent); | ||||
							
								
								
									
										51
									
								
								packages/app-mobile/components/ScreenHeader/WarningBox.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								packages/app-mobile/components/ScreenHeader/WarningBox.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| import * as React from 'react'; | ||||
| import { useMemo, useCallback } from 'react'; | ||||
| import { TouchableOpacity, StyleSheet, Text } from 'react-native'; | ||||
| import { themeStyle } from '../global-style'; | ||||
| import NavService from '@joplin/lib/services/NavService'; | ||||
|  | ||||
| interface Props { | ||||
| 	themeId: number; | ||||
| 	targetScreen: string; | ||||
| 	message: string; | ||||
| 	testID?: string; | ||||
| } | ||||
|  | ||||
| const useStyles = (themeId: number) => { | ||||
| 	return useMemo(() => { | ||||
| 		const theme = themeStyle(themeId); | ||||
| 		return StyleSheet.create({ | ||||
| 			container: { | ||||
| 				backgroundColor: '#ff9900', | ||||
| 				flexDirection: 'row', | ||||
| 				padding: theme.marginLeft, | ||||
| 			}, | ||||
| 			text: { | ||||
| 				flex: 1, | ||||
| 				color: 'black', | ||||
| 			}, | ||||
| 		}); | ||||
| 	}, [themeId]); | ||||
| }; | ||||
|  | ||||
| const WarningBox: React.FC<Props> = props => { | ||||
| 	const styles = useStyles(props.themeId); | ||||
|  | ||||
| 	const onPress = useCallback(() => { | ||||
| 		void NavService.go(props.targetScreen); | ||||
| 	}, [props.targetScreen]); | ||||
|  | ||||
| 	return ( | ||||
| 		<TouchableOpacity | ||||
| 			style={styles.container} | ||||
| 			onPress={onPress} | ||||
| 			activeOpacity={0.8} | ||||
| 			accessibilityRole='button' | ||||
| 			testID={props.testID} | ||||
| 		> | ||||
| 			<Text style={styles.text}>{props.message}</Text> | ||||
| 		</TouchableOpacity> | ||||
| 	); | ||||
| }; | ||||
|  | ||||
| export default WarningBox; | ||||
| @@ -1,32 +1,29 @@ | ||||
| const React = require('react'); | ||||
| 
 | ||||
| import { connect } from 'react-redux'; | ||||
| import * as React from 'react'; | ||||
| import { PureComponent, ReactElement } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { View, Text, StyleSheet, TouchableOpacity, Image, ScrollView, Dimensions, ViewStyle } from 'react-native'; | ||||
| const Icon = require('react-native-vector-icons/Ionicons').default; | ||||
| const { BackButtonService } = require('../services/back-button.js'); | ||||
| const { BackButtonService } = require('../../services/back-button.js'); | ||||
| import NavService from '@joplin/lib/services/NavService'; | ||||
| import { Menu, MenuOptions, MenuOption, MenuTrigger } from 'react-native-popup-menu'; | ||||
| import { _, _n } from '@joplin/lib/locale'; | ||||
| import Setting from '@joplin/lib/models/Setting'; | ||||
| import Note from '@joplin/lib/models/Note'; | ||||
| import Folder from '@joplin/lib/models/Folder'; | ||||
| import { themeStyle } from './global-style'; | ||||
| import { OnValueChangedListener } from './Dropdown'; | ||||
| const { dialogs } = require('../utils/dialogs.js'); | ||||
| import { themeStyle } from '../global-style'; | ||||
| import { OnValueChangedListener } from '../Dropdown'; | ||||
| const { dialogs } = require('../../utils/dialogs.js'); | ||||
| const DialogBox = require('react-native-dialogbox').default; | ||||
| import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils'; | ||||
| import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils'; | ||||
| import { FolderEntity } from '@joplin/lib/services/database/types'; | ||||
| import { State } from '@joplin/lib/reducer'; | ||||
| import CustomButton from './CustomButton'; | ||||
| import FolderPicker from './FolderPicker'; | ||||
| import CustomButton from '../CustomButton'; | ||||
| import FolderPicker from '../FolderPicker'; | ||||
| import { itemIsInTrash } from '@joplin/lib/services/trash'; | ||||
| import restoreItems from '@joplin/lib/services/trash/restoreItems'; | ||||
| import { ModelType } from '@joplin/lib/BaseModel'; | ||||
| import { PluginStates } from '@joplin/lib/services/plugins/reducer'; | ||||
| import { ContainerType } from '@joplin/lib/services/plugins/WebviewController'; | ||||
| import { Dispatch } from 'redux'; | ||||
| import WarningBanner from './WarningBanner'; | ||||
| 
 | ||||
| // We need this to suppress the useless warning
 | ||||
| // https://github.com/oblador/react-native-vector-icons/issues/1465
 | ||||
| @@ -41,10 +38,6 @@ const PADDING_V = 10; | ||||
| 
 | ||||
| type OnSelectCallbackType=()=> void; | ||||
| type OnPressCallback=()=> void; | ||||
| interface NavButtonPressEvent { | ||||
| 	// Name of the screen to navigate to
 | ||||
| 	screen: string; | ||||
| } | ||||
| 
 | ||||
| export interface MenuOptionType { | ||||
| 	onPress: OnPressCallback; | ||||
| @@ -90,12 +83,7 @@ interface ScreenHeaderProps { | ||||
| 	showSaveButton?: boolean; | ||||
| 
 | ||||
| 	historyCanGoBack?: boolean; | ||||
| 	showMissingMasterKeyMessage?: boolean; | ||||
| 	hasDisabledSyncItems?: boolean; | ||||
| 	hasDisabledEncryptionItems?: boolean; | ||||
| 	shouldUpgradeSyncTarget?: boolean; | ||||
| 	showShouldUpgradeSyncTargetMessage?: boolean; | ||||
| 	mustUpgradeAppMessage: string; | ||||
| 
 | ||||
| 	themeId: number; | ||||
| } | ||||
| @@ -210,11 +198,6 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade | ||||
| 				paddingTop: 15, | ||||
| 				paddingBottom: 15, | ||||
| 			}, | ||||
| 			warningBox: { | ||||
| 				backgroundColor: '#ff9900', | ||||
| 				flexDirection: 'row', | ||||
| 				padding: theme.marginLeft, | ||||
| 			}, | ||||
| 		}; | ||||
| 
 | ||||
| 		styleObject.contextMenuItemTextDisabled = { | ||||
| @@ -312,18 +295,6 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	private warningBox_press(event: NavButtonPressEvent) { | ||||
| 		void NavService.go(event.screen); | ||||
| 	} | ||||
| 
 | ||||
| 	private renderWarningBox(screen: string, message: string) { | ||||
| 		return ( | ||||
| 			<TouchableOpacity key={screen} style={this.styles().warningBox} onPress={() => this.warningBox_press({ screen: screen })} activeOpacity={0.8}> | ||||
| 				<Text style={{ flex: 1 }}>{message}</Text> | ||||
| 			</TouchableOpacity> | ||||
| 		); | ||||
| 	} | ||||
| 
 | ||||
| 	public render() { | ||||
| 		const themeId = this.props.themeId; | ||||
| 		// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
 | ||||
| @@ -640,17 +611,6 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| 		const warningComps = []; | ||||
| 
 | ||||
| 		if (this.props.showMissingMasterKeyMessage) warningComps.push(this.renderWarningBox('EncryptionConfig', _('Press to set the decryption password.'))); | ||||
| 		if (this.props.hasDisabledSyncItems) warningComps.push(this.renderWarningBox('Status', _('Some items cannot be synchronised. Press for more info.'))); | ||||
| 		if (this.props.shouldUpgradeSyncTarget && this.props.showShouldUpgradeSyncTargetMessage !== false) warningComps.push(this.renderWarningBox('UpgradeSyncTarget', _('The sync target needs to be upgraded. Press this banner to proceed.'))); | ||||
| 		if (this.props.mustUpgradeAppMessage) warningComps.push(this.renderWarningBox('UpgradeApp', this.props.mustUpgradeAppMessage)); | ||||
| 
 | ||||
| 		if (this.props.hasDisabledEncryptionItems) { | ||||
| 			warningComps.push(this.renderWarningBox('Status', _('Some items cannot be decrypted.'))); | ||||
| 		} | ||||
| 
 | ||||
| 		const showSideMenuButton = !!this.props.showSideMenuButton && !this.props.noteSelectionEnabled; | ||||
| 		const showSelectAllButton = this.props.noteSelectionEnabled; | ||||
| 		const showSearchButton = !!this.props.showSearchButton && !this.props.noteSelectionEnabled; | ||||
| @@ -724,7 +684,9 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade | ||||
| 					{sortButtonComp} | ||||
| 					{menuComp} | ||||
| 				</View> | ||||
| 				{warningComps} | ||||
| 				<WarningBanner | ||||
| 					showShouldUpgradeSyncTargetMessage={this.props.showShouldUpgradeSyncTargetMessage} | ||||
| 				/> | ||||
| 				<DialogBox | ||||
| 					ref={(dialogbox: typeof DialogBox) => { | ||||
| 						this.dialogbox = dialogbox; | ||||
| @@ -740,22 +702,15 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade | ||||
| } | ||||
| 
 | ||||
| const ScreenHeader = connect((state: State) => { | ||||
| 	const syncInfo = localSyncInfoFromState(state); | ||||
| 
 | ||||
| 	return { | ||||
| 		historyCanGoBack: state.historyCanGoBack, | ||||
| 		locale: state.settings.locale, | ||||
| 		folders: state.folders, | ||||
| 		themeId: state.settings.theme, | ||||
| 		hasDisabledEncryptionItems: state.hasDisabledEncryptionItems, | ||||
| 		noteSelectionEnabled: state.noteSelectionEnabled, | ||||
| 		selectedNoteIds: state.selectedNoteIds, | ||||
| 		selectedFolderId: state.selectedFolderId, | ||||
| 		notesParentType: state.notesParentType, | ||||
| 		showMissingMasterKeyMessage: showMissingMasterKeyMessage(syncInfo, state.notLoadedMasterKeys), | ||||
| 		hasDisabledSyncItems: state.hasDisabledSyncItems, | ||||
| 		shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO, | ||||
| 		mustUpgradeAppMessage: state.mustUpgradeAppMessage, | ||||
| 		plugins: state.pluginService.plugins, | ||||
| 	}; | ||||
| })(ScreenHeaderComponent); | ||||
		Reference in New Issue
	
	Block a user