import { PluginHtmlContents, PluginStates, ViewInfo } from '@joplin/lib/services/plugins/reducer'; import * as React from 'react'; import { Button, IconButton, Portal, SegmentedButtons, Text } from 'react-native-paper'; import useViewInfos from './hooks/useViewInfos'; import WebviewController, { ContainerType } from '@joplin/lib/services/plugins/WebviewController'; import { useCallback, useEffect, useMemo, useState } from 'react'; import PluginService from '@joplin/lib/services/plugins/PluginService'; import { connect } from 'react-redux'; import { AppState } from '../../../utils/types'; import PluginUserWebView from './PluginUserWebView'; import { View, useWindowDimensions, StyleSheet, AccessibilityInfo } from 'react-native'; import { _ } from '@joplin/lib/locale'; import { Theme } from '@joplin/lib/themes/type'; import { themeStyle } from '@joplin/lib/theme'; import Setting from '@joplin/lib/models/Setting'; import { Dispatch } from 'redux'; import Modal from '../../../components/Modal'; interface Props { themeId: number; pluginHtmlContents: PluginHtmlContents; pluginStates: PluginStates; visible: boolean; dispatch: Dispatch; } const useStyles = (themeId: number) => { const windowSize = useWindowDimensions(); return useMemo(() => { const theme: Theme = themeStyle(themeId); return StyleSheet.create({ webView: { backgroundColor: 'transparent', display: 'flex', }, webViewContainer: { flexGrow: 1, flexShrink: 1, }, closeButtonContainer: { flexDirection: 'row', justifyContent: 'flex-end', }, dialog: { backgroundColor: theme.backgroundColor, borderRadius: 12, padding: 10, height: windowSize.height * 0.9, width: windowSize.width * 0.97, // Center marginLeft: 'auto', marginRight: 'auto', }, }); }, [themeId, windowSize.width, windowSize.height]); }; type ButtonInfo = { value: string; label: string; icon: string; }; const useSelectedTabId = (buttonInfos: ButtonInfo[], viewInfoById: Record) => { const getDefaultSelectedTabId = useCallback((): string|undefined => { const lastSelectedId = Setting.value('ui.lastSelectedPluginPanel'); if (lastSelectedId && viewInfoById[lastSelectedId]) { return lastSelectedId; } else { return buttonInfos[0]?.value; } }, [buttonInfos, viewInfoById]); const [selectedTabId, setSelectedTabId] = useState(getDefaultSelectedTabId); useEffect(() => { if (!selectedTabId) { setSelectedTabId(getDefaultSelectedTabId()); } }, [selectedTabId, getDefaultSelectedTabId]); useEffect(() => { if (!selectedTabId) return () => {}; const info = viewInfoById[selectedTabId]; let controller: WebviewController|null = null; if (info && info.view.opened) { const plugin = PluginService.instance().pluginById(info.plugin.id); controller = plugin.viewController(info.view.id) as WebviewController; controller.setIsShownInModal(true); } Setting.setValue('ui.lastSelectedPluginPanel', selectedTabId); AccessibilityInfo.announceForAccessibility(_('%s tab opened', getTabLabel(info))); return () => { controller?.setIsShownInModal(false); }; }, [viewInfoById, selectedTabId]); return { setSelectedTabId, selectedTabId }; }; const emptyCallback = () => {}; const getTabLabel = (info: ViewInfo) => { // Handles the case where a plugin just unloaded or hasn't loaded yet. if (!info) { return '...'; } return PluginService.instance().pluginById(info.plugin.id).manifest.name; }; const PluginPanelViewer: React.FC = props => { const viewInfos = useViewInfos(props.pluginStates); const viewInfoById = useMemo(() => { const result: Record = {}; for (const info of viewInfos) { result[`${info.plugin.id}--${info.view.id}`] = info; } return result; }, [viewInfos]); const buttonInfos = useMemo(() => { return Object.entries(viewInfoById) .filter(([_id, info]) => info.view.containerType === ContainerType.Panel) .map(([id, info]) => { return { value: id, label: getTabLabel(info), icon: 'puzzle', }; }); }, [viewInfoById]); const styles = useStyles(props.themeId); const { selectedTabId, setSelectedTabId } = useSelectedTabId(buttonInfos, viewInfoById); const viewInfo = viewInfoById[selectedTabId]; const renderTabContent = () => { if (!viewInfo) { return {_('No tab selected')}; } return ( ); }; const renderTabSelector = () => { // SegmentedButtons doesn't display correctly when there's only one button. // As such, we include a special case: if (buttonInfos.length === 1) { const buttonInfo = buttonInfos[0]; return ( ); } return ( ); }; const onClose = useCallback(() => { props.dispatch({ type: 'SET_PLUGIN_PANELS_DIALOG_VISIBLE', visible: false, }); }, [props.dispatch]); const closeButton = ( ); return ( {closeButton} {renderTabContent()} {renderTabSelector()} ); }; export default connect((state: AppState) => { return { themeId: state.settings.theme, pluginHtmlContents: state.pluginService.pluginHtmlContents, visible: state.showPanelsDialog, pluginStates: state.pluginService.plugins, }; })(PluginPanelViewer);