mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +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;
|