You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			151 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			151 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import * as React from 'react';
 | |
| import { createContext, useEffect, useMemo, useRef, useState } from 'react';
 | |
| import { Alert, Platform, StyleSheet } from 'react-native';
 | |
| import { Button, Dialog, Portal, Text } from 'react-native-paper';
 | |
| import Modal from './Modal';
 | |
| import { _ } from '@joplin/lib/locale';
 | |
| import shim from '@joplin/lib/shim';
 | |
| import makeShowMessageBox from '../utils/makeShowMessageBox';
 | |
| 
 | |
| export interface PromptButton {
 | |
| 	text: string;
 | |
| 	onPress?: ()=> void;
 | |
| 	style?: 'cancel'|'default'|'destructive';
 | |
| }
 | |
| 
 | |
| interface PromptOptions {
 | |
| 	cancelable?: boolean;
 | |
| }
 | |
| 
 | |
| export interface DialogControl {
 | |
| 	prompt(title: string, message: string, buttons?: PromptButton[], options?: PromptOptions): void;
 | |
| }
 | |
| 
 | |
| export const DialogContext = createContext<DialogControl>(null);
 | |
| 
 | |
| interface Props {
 | |
| 	children: React.ReactNode;
 | |
| }
 | |
| 
 | |
| interface PromptDialogData {
 | |
| 	key: string;
 | |
| 	title: string;
 | |
| 	message: string;
 | |
| 	buttons: PromptButton[];
 | |
| 	onDismiss: (()=> void)|null;
 | |
| }
 | |
| 
 | |
| const styles = StyleSheet.create({
 | |
| 	dialogContainer: {
 | |
| 		maxWidth: 400,
 | |
| 		minWidth: '50%',
 | |
| 		alignSelf: 'center',
 | |
| 	},
 | |
| 	modalContainer: {
 | |
| 		marginTop: 'auto',
 | |
| 		marginBottom: 'auto',
 | |
| 	},
 | |
| });
 | |
| 
 | |
| const DialogManager: React.FC<Props> = props => {
 | |
| 	const [dialogModels, setPromptDialogs] = useState<PromptDialogData[]>([]);
 | |
| 	const nextDialogIdRef = useRef(0);
 | |
| 
 | |
| 	const dialogControl: DialogControl = useMemo(() => {
 | |
| 		const defaultButtons = [{ text: _('OK') }];
 | |
| 		return {
 | |
| 			prompt: (title: string, message: string, buttons: PromptButton[] = defaultButtons, options?: PromptOptions) => {
 | |
| 				if (Platform.OS !== 'web') {
 | |
| 					// Alert.alert provides a more native style on iOS.
 | |
| 					Alert.alert(title, message, buttons, options);
 | |
| 
 | |
| 					// Alert.alert doesn't work on web.
 | |
| 				} else {
 | |
| 					const onDismiss = () => {
 | |
| 						setPromptDialogs(dialogs => dialogs.filter(d => d !== dialog));
 | |
| 					};
 | |
| 
 | |
| 					const cancelable = options?.cancelable ?? true;
 | |
| 					const dialog: PromptDialogData = {
 | |
| 						key: `dialog-${nextDialogIdRef.current++}`,
 | |
| 						title,
 | |
| 						message,
 | |
| 						buttons: buttons.map(button => ({
 | |
| 							...button,
 | |
| 							onPress: () => {
 | |
| 								onDismiss();
 | |
| 								button.onPress?.();
 | |
| 							},
 | |
| 						})),
 | |
| 						onDismiss: cancelable ? onDismiss : null,
 | |
| 					};
 | |
| 
 | |
| 					setPromptDialogs(dialogs => {
 | |
| 						return [
 | |
| 							...dialogs,
 | |
| 							dialog,
 | |
| 						];
 | |
| 					});
 | |
| 				}
 | |
| 			},
 | |
| 		};
 | |
| 	}, []);
 | |
| 	const dialogControlRef = useRef(dialogControl);
 | |
| 	dialogControlRef.current = dialogControl;
 | |
| 
 | |
| 	useEffect(() => {
 | |
| 		shim.showMessageBox = makeShowMessageBox(dialogControlRef);
 | |
| 
 | |
| 		return () => {
 | |
| 			dialogControlRef.current = null;
 | |
| 		};
 | |
| 	}, []);
 | |
| 
 | |
| 	const dialogComponents: React.ReactNode[] = [];
 | |
| 	for (const dialog of dialogModels) {
 | |
| 		const buttons = dialog.buttons.map((button, index) => {
 | |
| 			return (
 | |
| 				<Button key={`${index}-${button.text}`} onPress={button.onPress}>{button.text}</Button>
 | |
| 			);
 | |
| 		});
 | |
| 		dialogComponents.push(
 | |
| 			<Dialog
 | |
| 				testID={'prompt-dialog'}
 | |
| 				style={styles.dialogContainer}
 | |
| 				key={dialog.key}
 | |
| 				visible={true}
 | |
| 				onDismiss={dialog.onDismiss}
 | |
| 			>
 | |
| 				<Dialog.Title>{dialog.title}</Dialog.Title>
 | |
| 				<Dialog.Content>
 | |
| 					<Text variant='bodyMedium'>{dialog.message}</Text>
 | |
| 				</Dialog.Content>
 | |
| 				<Dialog.Actions>
 | |
| 					{buttons}
 | |
| 				</Dialog.Actions>
 | |
| 			</Dialog>,
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	// Web: Use a <Modal> wrapper for better keyboard focus handling.
 | |
| 	return <>
 | |
| 		<DialogContext.Provider value={dialogControl}>
 | |
| 			{props.children}
 | |
| 		</DialogContext.Provider>
 | |
| 		<Portal>
 | |
| 			<Modal
 | |
| 				visible={!!dialogComponents.length}
 | |
| 				containerStyle={styles.modalContainer}
 | |
| 				animationType='none'
 | |
| 				backgroundColor='rgba(0, 0, 0, 0.1)'
 | |
| 				transparent={true}
 | |
| 				onRequestClose={dialogModels[dialogComponents.length - 1]?.onDismiss}
 | |
| 			>
 | |
| 				{dialogComponents}
 | |
| 			</Modal>
 | |
| 		</Portal>
 | |
| 	</>;
 | |
| };
 | |
| 
 | |
| export default DialogManager;
 |