1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-18 09:35:20 +02:00
joplin/packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx

192 lines
5.8 KiB
TypeScript

import * as React from 'react';
import { useCallback, useMemo } from 'react';
import createRootStyle from '../../utils/createRootStyle';
import { View, StyleSheet, ScrollView } from 'react-native';
import { Divider, Text, TouchableRipple } from 'react-native-paper';
import { _ } from '@joplin/lib/locale';
import { themeStyle } from '../global-style';
import { connect } from 'react-redux';
import ToolbarButtonUtils, { ToolbarButtonInfo, ToolbarItem } from '@joplin/lib/services/commands/ToolbarButtonUtils';
import Icon from '../Icon';
import { AppState } from '../../utils/types';
import CommandService from '@joplin/lib/services/CommandService';
import allToolbarCommandNamesFromState from './utils/allToolbarCommandNamesFromState';
import Setting from '@joplin/lib/models/Setting';
import DismissibleDialog, { DialogSize } from '../DismissibleDialog';
import selectedCommandNamesFromState from './utils/selectedCommandNamesFromState';
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
import { DeleteButton } from '../buttons';
import shim from '@joplin/lib/shim';
const toolbarButtonUtils = new ToolbarButtonUtils(CommandService.instance());
interface EditorDialogProps {
themeId: number;
defaultToolbarButtonInfos: ToolbarItem[];
selectedCommandNames: string[];
allCommandNames: string[];
hasCustomizedLayout: boolean;
visible: boolean;
onDismiss: ()=> void;
}
const useStyle = (themeId: number) => {
return useMemo(() => {
const theme = themeStyle(themeId);
return StyleSheet.create({
...createRootStyle(themeId),
icon: {
color: theme.color,
fontSize: theme.fontSizeLarge,
},
labelText: {
fontSize: theme.fontSize,
},
listContainer: {
marginTop: theme.marginTop,
flex: 1,
},
resetButton: {
marginTop: theme.marginTop,
},
listItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
gap: theme.margin,
padding: 4,
paddingTop: theme.itemMarginTop,
paddingBottom: theme.itemMarginBottom,
},
});
}, [themeId]);
};
type Styles = ReturnType<typeof useStyle>;
const setCommandIncluded = (
commandName: string,
lastSelectedCommands: string[],
allCommandNames: string[],
include: boolean,
) => {
let newSelectedCommands;
if (include) {
newSelectedCommands = [];
for (const name of allCommandNames) {
const isDivider = name === '-';
if (isDivider || name === commandName || lastSelectedCommands.includes(name)) {
newSelectedCommands.push(name);
}
}
} else {
newSelectedCommands = lastSelectedCommands.filter(name => name !== commandName);
}
Setting.setValue('editor.toolbarButtons', newSelectedCommands);
};
interface ItemToggleProps {
item: ToolbarButtonInfo;
selectedCommandNames: string[];
allCommandNames: string[];
styles: Styles;
}
const ToolbarItemToggle: React.FC<ItemToggleProps> = ({
item, selectedCommandNames, styles, allCommandNames,
}) => {
const title = item.title || item.tooltip;
const checked = selectedCommandNames.includes(item.name);
const onToggle = useCallback(() => {
setCommandIncluded(item.name, selectedCommandNames, allCommandNames, !checked);
}, [item, selectedCommandNames, allCommandNames, checked]);
return (
<TouchableRipple
accessibilityRole='checkbox'
accessibilityState={{ checked }}
aria-checked={checked}
onPress={onToggle}
>
<View style={styles.listItem}>
<Icon name={checked ? 'ionicon checkbox-outline' : 'ionicon square-outline'} style={styles.icon} accessibilityLabel={null}/>
<Icon name={item.iconName} style={styles.icon} accessibilityLabel={null}/>
<Text style={styles.labelText}>
{title}
</Text>
</View>
</TouchableRipple>
);
};
const ToolbarEditorScreen: React.FC<EditorDialogProps> = props => {
const styles = useStyle(props.themeId);
const renderItem = (item: ToolbarItem, index: number) => {
if (item.type === 'separator') {
return <Divider key={`separator-${index}`} />;
}
return <ToolbarItemToggle
key={`command-${item.name}`}
item={item}
styles={styles}
allCommandNames={props.allCommandNames}
selectedCommandNames={props.selectedCommandNames}
/>;
};
const onRestoreDefaultLayout = useCallback(async () => {
// Dismiss before showing the confirm dialog to prevent modal conflicts.
// On some platforms (web and possibly iOS) showing multiple modals
// at the same time can cause issues.
props.onDismiss();
const message = _('Are you sure that you want to restore the default toolbar layout?\nThis cannot be undone.');
if (await shim.showConfirmationDialog(message)) {
Setting.setValue('editor.toolbarButtons', []);
}
}, [props.onDismiss]);
const restoreButton = <DeleteButton
style={styles.resetButton}
onPress={onRestoreDefaultLayout}
>
{_('Restore defaults')}
</DeleteButton>;
return (
<DismissibleDialog
size={DialogSize.Small}
themeId={props.themeId}
visible={props.visible}
onDismiss={props.onDismiss}
heading={_('Manage toolbar options')}
>
<View>
<Text variant='bodyMedium'>{_('Check elements to display in the toolbar')}</Text>
</View>
<ScrollView style={styles.listContainer}>
{props.defaultToolbarButtonInfos.map((item, index) => renderItem(item, index))}
{props.hasCustomizedLayout ? restoreButton : null}
</ScrollView>
</DismissibleDialog>
);
};
export default connect((state: AppState) => {
const whenClauseContext = stateToWhenClauseContext(state);
const allCommandNames = allToolbarCommandNamesFromState(state);
const selectedCommandNames = selectedCommandNamesFromState(state);
return {
themeId: state.settings.theme,
selectedCommandNames,
allCommandNames,
hasCustomizedLayout: state.settings['editor.toolbarButtons'].length > 0,
defaultToolbarButtonInfos: toolbarButtonUtils.commandsToToolbarButtons(allCommandNames, whenClauseContext),
};
})(ToolbarEditorScreen);