mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
This commit is contained in:
parent
a4a4170d49
commit
e465b45d6e
@ -619,9 +619,10 @@ packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/InstalledPluginBox.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChip.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginTitle.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/StyledChip.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.installed.test.js
|
||||
@ -629,6 +630,7 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.search.
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginUploadButton.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/SectionLabel.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/ActionButton.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButton.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -598,9 +598,10 @@ packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/InstalledPluginBox.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChip.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginTitle.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/StyledChip.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.installed.test.js
|
||||
@ -608,6 +609,7 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.search.
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginUploadButton.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/SectionLabel.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/ActionButton.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButton.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js
|
||||
|
@ -0,0 +1,57 @@
|
||||
import * as React from 'react';
|
||||
import { Chip, ChipProps } from 'react-native-paper';
|
||||
import { useMemo } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from '../../../../../utils/types';
|
||||
import { themeStyle } from '../../../../global-style';
|
||||
|
||||
type Props = {
|
||||
themeId: number;
|
||||
color?: string;
|
||||
faded?: boolean;
|
||||
onPress?: ()=> void;
|
||||
icon?: string;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const fadedStyle = { opacity: 0.87 };
|
||||
|
||||
const PluginChip: React.FC<Props> = props => {
|
||||
const themeOverride = useMemo(() => {
|
||||
const theme = themeStyle(props.themeId);
|
||||
const foreground = props.color ?? theme.color;
|
||||
const background = theme.backgroundColor;
|
||||
return {
|
||||
colors: {
|
||||
secondaryContainer: background,
|
||||
onSecondaryContainer: foreground,
|
||||
primary: foreground,
|
||||
|
||||
outline: foreground,
|
||||
onPrimary: foreground,
|
||||
onSurfaceVariant: foreground,
|
||||
},
|
||||
};
|
||||
}, [props.themeId, props.color]);
|
||||
|
||||
const accessibilityProps: Partial<ChipProps> = {};
|
||||
if (!props.onPress) {
|
||||
// Note: May have no effect until a future version of RN Paper.
|
||||
// See https://github.com/callstack/react-native-paper/pull/4327
|
||||
accessibilityProps.accessibilityRole = 'text';
|
||||
}
|
||||
|
||||
return <Chip
|
||||
theme={themeOverride}
|
||||
style={props.faded ? fadedStyle : null}
|
||||
mode='outlined'
|
||||
{...accessibilityProps}
|
||||
{...props}
|
||||
/>;
|
||||
};
|
||||
|
||||
export default connect((state: AppState) => {
|
||||
return {
|
||||
themeId: state.settings.theme,
|
||||
};
|
||||
})(PluginChip);
|
@ -2,10 +2,10 @@ import { PluginItem } from '@joplin/lib/components/shared/config/plugins/types';
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import * as React from 'react';
|
||||
import { Alert, Linking, View, ViewStyle } from 'react-native';
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { PluginCallback } from '../utils/usePluginCallbacks';
|
||||
import StyledChip from './StyledChip';
|
||||
import PluginChip from './PluginChip';
|
||||
import { themeStyle } from '../../../../global-style';
|
||||
|
||||
interface Props {
|
||||
@ -19,23 +19,6 @@ interface Props {
|
||||
onShowPluginLog?: PluginCallback;
|
||||
}
|
||||
|
||||
const onRecommendedPress = () => {
|
||||
Alert.alert(
|
||||
'',
|
||||
_('The Joplin team has vetted this plugin and it meets our standards for security and performance.'),
|
||||
[
|
||||
{
|
||||
text: _('Learn more'),
|
||||
onPress: () => Linking.openURL('https://github.com/joplin/plugins/blob/master/readme/recommended.md'),
|
||||
},
|
||||
{
|
||||
text: _('OK'),
|
||||
},
|
||||
],
|
||||
{ cancelable: true },
|
||||
);
|
||||
};
|
||||
|
||||
const containerStyle: ViewStyle = {
|
||||
flexDirection: 'row',
|
||||
gap: 4,
|
||||
@ -54,43 +37,28 @@ const PluginChips: React.FC<Props> = props => {
|
||||
if (!props.hasErrors) return null;
|
||||
|
||||
return (
|
||||
<StyledChip
|
||||
background={theme.backgroundColor2}
|
||||
foreground={theme.colorError2}
|
||||
<PluginChip
|
||||
color={theme.colorError2}
|
||||
icon='alert'
|
||||
mode='flat'
|
||||
onPress={() => props.onShowPluginLog({ item })}
|
||||
>
|
||||
{_('Error')}
|
||||
</StyledChip>
|
||||
</PluginChip>
|
||||
);
|
||||
};
|
||||
|
||||
const renderRecommendedChip = () => {
|
||||
if (!props.item.manifest._recommended || !props.isCompatible) {
|
||||
return null;
|
||||
}
|
||||
return <StyledChip
|
||||
background={theme.searchMarkerBackgroundColor}
|
||||
foreground={theme.searchMarkerColor}
|
||||
icon='crown'
|
||||
onPress={onRecommendedPress}
|
||||
>{_('Recommended')}</StyledChip>;
|
||||
};
|
||||
|
||||
const renderBuiltInChip = () => {
|
||||
if (!props.item.builtIn) {
|
||||
return null;
|
||||
}
|
||||
return <StyledChip icon='code-tags-check'>{_('Built-in')}</StyledChip>;
|
||||
return <PluginChip icon='code-tags-check'>{_('Built-in')}</PluginChip>;
|
||||
};
|
||||
|
||||
const renderIncompatibleChip = () => {
|
||||
if (props.isCompatible) return null;
|
||||
return (
|
||||
<StyledChip
|
||||
background={theme.backgroundColor3}
|
||||
foreground={theme.color3}
|
||||
<PluginChip
|
||||
color={theme.color3}
|
||||
icon='alert'
|
||||
onPress={() => {
|
||||
void shim.showMessageBox(
|
||||
@ -98,7 +66,7 @@ const PluginChips: React.FC<Props> = props => {
|
||||
{ buttons: [_('OK')] },
|
||||
);
|
||||
}}
|
||||
>{_('Incompatible')}</StyledChip>
|
||||
>{_('Incompatible')}</PluginChip>
|
||||
);
|
||||
};
|
||||
|
||||
@ -106,7 +74,7 @@ const PluginChips: React.FC<Props> = props => {
|
||||
if (!props.isCompatible || !props.canUpdate) return null;
|
||||
|
||||
return (
|
||||
<StyledChip>{_('Update available')}</StyledChip>
|
||||
<PluginChip>{_('Update available')}</PluginChip>
|
||||
);
|
||||
};
|
||||
|
||||
@ -114,21 +82,20 @@ const PluginChips: React.FC<Props> = props => {
|
||||
if (props.item.enabled || !props.item.installed) {
|
||||
return null;
|
||||
}
|
||||
return <StyledChip>{_('Disabled')}</StyledChip>;
|
||||
return <PluginChip faded={true}>{_('Disabled')}</PluginChip>;
|
||||
};
|
||||
|
||||
const renderInstalledChip = () => {
|
||||
if (!props.showInstalledChip) {
|
||||
return null;
|
||||
}
|
||||
return <StyledChip>{_('Installed')}</StyledChip>;
|
||||
return <PluginChip faded={true}>{_('Installed')}</PluginChip>;
|
||||
};
|
||||
|
||||
return <View style={containerStyle}>
|
||||
{renderIncompatibleChip()}
|
||||
{renderInstalledChip()}
|
||||
{renderErrorsChip()}
|
||||
{renderRecommendedChip()}
|
||||
{renderBuiltInChip()}
|
||||
{renderUpdatableChip()}
|
||||
{renderDisabledChip()}
|
||||
|
@ -0,0 +1,74 @@
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { PluginManifest } from '@joplin/lib/services/plugins/utils/types';
|
||||
import * as React from 'react';
|
||||
import IconButton from '../../../../IconButton';
|
||||
import { Alert, Linking, StyleSheet } from 'react-native';
|
||||
import { themeStyle } from '../../../../global-style';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
const onRecommendedPress = () => {
|
||||
Alert.alert(
|
||||
'',
|
||||
_('The Joplin team has vetted this plugin and it meets our standards for security and performance.'),
|
||||
[
|
||||
{
|
||||
text: _('Learn more'),
|
||||
onPress: () => Linking.openURL('https://github.com/joplin/plugins/blob/master/readme/recommended.md'),
|
||||
},
|
||||
{
|
||||
text: _('OK'),
|
||||
},
|
||||
],
|
||||
{ cancelable: true },
|
||||
);
|
||||
};
|
||||
|
||||
interface Props {
|
||||
themeId: number;
|
||||
manifest: PluginManifest;
|
||||
isCompatible: boolean;
|
||||
}
|
||||
|
||||
const useStyles = (themeId: number) => {
|
||||
return useMemo(() => {
|
||||
const theme = themeStyle(themeId);
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
opacity: 0.8,
|
||||
},
|
||||
wrapper: {
|
||||
borderColor: theme.colorWarn,
|
||||
borderWidth: 1,
|
||||
borderRadius: 20,
|
||||
justifyContent: 'center',
|
||||
height: 32,
|
||||
width: 32,
|
||||
textAlign: 'center',
|
||||
},
|
||||
icon: {
|
||||
fontSize: 14,
|
||||
color: theme.colorWarn,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
},
|
||||
});
|
||||
}, [themeId]);
|
||||
};
|
||||
|
||||
const RecommendedBadge: React.FC<Props> = props => {
|
||||
const styles = useStyles(props.themeId);
|
||||
|
||||
if (!props.manifest._recommended || !props.isCompatible) return null;
|
||||
|
||||
return <IconButton
|
||||
onPress={onRecommendedPress}
|
||||
iconName='fas fa-crown'
|
||||
containerStyle={styles.container}
|
||||
contentWrapperStyle={styles.wrapper}
|
||||
iconStyle={styles.icon}
|
||||
themeId={props.themeId}
|
||||
description={_('Recommended')}
|
||||
/>;
|
||||
};
|
||||
|
||||
export default RecommendedBadge;
|
@ -1,39 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { Chip, ChipProps } from 'react-native-paper';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
type Props = ({
|
||||
foreground: string;
|
||||
background: string;
|
||||
}|{
|
||||
foreground?: undefined;
|
||||
background?: undefined;
|
||||
}) & ChipProps;
|
||||
|
||||
const RecommendedChip: React.FC<Props> = props => {
|
||||
const themeOverride = useMemo(() => {
|
||||
if (!props.foreground) return {};
|
||||
return {
|
||||
colors: {
|
||||
secondaryContainer: props.background,
|
||||
onSecondaryContainer: props.foreground,
|
||||
primary: props.foreground,
|
||||
},
|
||||
};
|
||||
}, [props.foreground, props.background]);
|
||||
|
||||
const accessibilityProps: Partial<Props> = {};
|
||||
if (!props.onPress) {
|
||||
// Note: May have no effect until a future version of RN Paper.
|
||||
// See https://github.com/callstack/react-native-paper/pull/4327
|
||||
accessibilityProps.accessibilityRole = 'text';
|
||||
}
|
||||
|
||||
return <Chip
|
||||
theme={themeOverride}
|
||||
{...accessibilityProps}
|
||||
{...props}
|
||||
/>;
|
||||
};
|
||||
|
||||
export default RecommendedChip;
|
@ -8,9 +8,10 @@ import PluginChips from './PluginChips';
|
||||
import { UpdateState } from '../utils/useUpdateState';
|
||||
import { PluginCallback } from '../utils/usePluginCallbacks';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import InstallButton from '../buttons/InstallButton';
|
||||
import PluginTitle from './PluginTitle';
|
||||
import RecommendedBadge from './RecommendedBadge';
|
||||
|
||||
export enum InstallState {
|
||||
NotInstalled,
|
||||
@ -92,8 +93,13 @@ const PluginBox: React.FC<Props> = props => {
|
||||
testID='plugin-card'
|
||||
>
|
||||
<Card.Content style={styles.content}>
|
||||
<PluginTitle manifest={item.manifest} />
|
||||
<Text numberOfLines={2}>{manifest.description}</Text>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
||||
<View style={{ flexShrink: 1 }}>
|
||||
<PluginTitle manifest={item.manifest} />
|
||||
<Text numberOfLines={2}>{manifest.description}</Text>
|
||||
</View>
|
||||
<RecommendedBadge manifest={item.manifest} isCompatible={props.isCompatible} themeId={props.themeId} />
|
||||
</View>
|
||||
<PluginChips
|
||||
themeId={props.themeId}
|
||||
item={props.item}
|
||||
|
@ -47,11 +47,6 @@ const loadMockPlugin = async (id: string, name: string, version: string, pluginS
|
||||
});
|
||||
};
|
||||
|
||||
const showInstalledTab = async () => {
|
||||
const installedTab = await screen.findByText('Installed plugins');
|
||||
await userEvent.press(installedTab);
|
||||
};
|
||||
|
||||
const abcPluginId = 'org.joplinapp.plugins.AbcSheetMusic';
|
||||
const backlinksPluginId = 'joplin.plugin.ambrt.backlinksToNote';
|
||||
|
||||
@ -99,7 +94,6 @@ describe('PluginStates.installed', () => {
|
||||
store={reduxStore}
|
||||
/>,
|
||||
);
|
||||
await showInstalledTab();
|
||||
expect(await screen.findByText(/^ABC Sheet Music/)).toBeVisible();
|
||||
expect(await screen.findByText(/^Backlinks to note/)).toBeVisible();
|
||||
|
||||
@ -129,7 +123,6 @@ describe('PluginStates.installed', () => {
|
||||
store={reduxStore}
|
||||
/>,
|
||||
);
|
||||
await showInstalledTab();
|
||||
|
||||
const abcSheetMusicCard = await screen.findByText(/^ABC Sheet Music/);
|
||||
expect(abcSheetMusicCard).toBeVisible();
|
||||
@ -150,7 +143,7 @@ describe('PluginStates.installed', () => {
|
||||
);
|
||||
|
||||
// Initially, no plugins should be installed
|
||||
expect(screen.queryByText(/^You currently have 0 plugins? installed/)).toBeNull();
|
||||
expect(screen.queryByText('Installed (0):')).toBeNull();
|
||||
|
||||
const testPluginId1 = 'org.joplinapp.plugins.AbcSheetMusic';
|
||||
const testPluginId2 = 'org.joplinapp.plugins.test.plugin.id';
|
||||
@ -158,8 +151,6 @@ describe('PluginStates.installed', () => {
|
||||
await act(() => loadMockPlugin(testPluginId2, 'A test plugin', '1.0.0', pluginSettings));
|
||||
expect(PluginService.instance().plugins[testPluginId1]).toBeTruthy();
|
||||
|
||||
await showInstalledTab();
|
||||
|
||||
// Should update the list of installed plugins even though the plugin settings didn't change.
|
||||
expect(await screen.findByText(/^ABC Sheet Music/)).toBeVisible();
|
||||
expect(await screen.findByText(/^A test plugin/)).toBeVisible();
|
||||
@ -184,7 +175,6 @@ describe('PluginStates.installed', () => {
|
||||
store={reduxStore}
|
||||
/>,
|
||||
);
|
||||
await showInstalledTab();
|
||||
|
||||
const card = await screen.findByText('ABC Sheet Music');
|
||||
const user = userEvent.setup();
|
||||
@ -226,7 +216,6 @@ describe('PluginStates.installed', () => {
|
||||
store={reduxStore}
|
||||
/>,
|
||||
);
|
||||
await showInstalledTab();
|
||||
|
||||
// Open the plugin dialog
|
||||
const card = await screen.findByText('ABC Sheet Music');
|
||||
@ -279,7 +268,6 @@ describe('PluginStates.installed', () => {
|
||||
store={reduxStore}
|
||||
/>,
|
||||
);
|
||||
await showInstalledTab();
|
||||
|
||||
// Should be shown as installed.
|
||||
const card = await screen.findByText('ABC Sheet Music');
|
||||
|
@ -18,14 +18,9 @@ const expectSearchResultCountToBe = async (count: number) => {
|
||||
});
|
||||
};
|
||||
|
||||
const showSearchTab = async () => {
|
||||
const searchAccordion = await screen.findByText('Install new plugins');
|
||||
await userEvent.press(searchAccordion);
|
||||
};
|
||||
|
||||
// The search box is initially read-only -- waits for it to be editable.
|
||||
const getEditableSearchBox = async () => {
|
||||
const searchBox = await screen.findByPlaceholderText('Search plugins');
|
||||
const searchBox = await screen.findByPlaceholderText('Search for plugins...');
|
||||
expect(searchBox).toBeVisible();
|
||||
|
||||
await waitFor(() => {
|
||||
@ -53,7 +48,6 @@ describe('PluginStates.search', () => {
|
||||
const wrapper = render(<WrappedPluginStates initialPluginSettings={{}} store={reduxStore}/>);
|
||||
|
||||
const user = userEvent.setup();
|
||||
await showSearchTab();
|
||||
const searchBox = await getEditableSearchBox();
|
||||
await user.type(searchBox, 'backlinks');
|
||||
|
||||
@ -81,8 +75,6 @@ describe('PluginStates.search', () => {
|
||||
const wrapper = render(<WrappedPluginStates initialPluginSettings={{}} store={reduxStore}/>);
|
||||
|
||||
const user = userEvent.setup();
|
||||
await showSearchTab();
|
||||
|
||||
const searchBox = await getEditableSearchBox();
|
||||
|
||||
await user.press(searchBox);
|
||||
@ -111,8 +103,6 @@ describe('PluginStates.search', () => {
|
||||
const wrapper = render(<WrappedPluginStates initialPluginSettings={{}} store={reduxStore}/>);
|
||||
|
||||
const user = userEvent.setup();
|
||||
await showSearchTab();
|
||||
|
||||
const searchBox = await getEditableSearchBox();
|
||||
await user.press(searchBox);
|
||||
await user.type(searchBox, 'abc');
|
||||
|
@ -2,8 +2,8 @@ import * as React from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { ConfigScreenStyles } from '../configScreenStyles';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { Banner, Text, Button, ProgressBar, List, Divider } from 'react-native-paper';
|
||||
import { _, _n } from '@joplin/lib/locale';
|
||||
import { Banner, Text, Button, ProgressBar, Divider } from 'react-native-paper';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import PluginService, { PluginSettings, SerializedPluginSettings } from '@joplin/lib/services/plugins/PluginService';
|
||||
import InstalledPluginBox from './InstalledPluginBox';
|
||||
import SearchPlugins from './SearchPlugins';
|
||||
@ -13,6 +13,7 @@ import RepositoryApi from '@joplin/lib/services/plugins/RepositoryApi';
|
||||
import PluginInfoModal from './PluginInfoModal';
|
||||
import usePluginCallbacks from './utils/usePluginCallbacks';
|
||||
import BetaChip from '../../../BetaChip';
|
||||
import SectionLabel from './SectionLabel';
|
||||
|
||||
interface Props {
|
||||
themeId: number;
|
||||
@ -59,8 +60,8 @@ const useLoadedPluginIds = () => {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
installedPluginsContainer: {
|
||||
marginLeft: 8,
|
||||
marginRight: 8,
|
||||
marginLeft: 12,
|
||||
marginRight: 12,
|
||||
marginBottom: 10,
|
||||
},
|
||||
});
|
||||
@ -134,11 +135,16 @@ const PluginStates: React.FC<Props> = props => {
|
||||
const installedPluginCards = [];
|
||||
const pluginService = PluginService.instance();
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const isPluginSearching = !!searchQuery;
|
||||
|
||||
const pluginIds = useLoadedPluginIds();
|
||||
for (const pluginId of pluginIds) {
|
||||
const plugin = pluginService.plugins[pluginId];
|
||||
|
||||
if (!props.shouldShowBasedOnSearchQuery || props.shouldShowBasedOnSearchQuery(plugin.manifest.name)) {
|
||||
const matchesGlobalSearch = !props.shouldShowBasedOnSearchQuery || props.shouldShowBasedOnSearchQuery(plugin.manifest.name);
|
||||
const showCard = !isPluginSearching && matchesGlobalSearch;
|
||||
if (showCard) {
|
||||
installedPluginCards.push(
|
||||
<InstalledPluginBox
|
||||
key={`plugin-${pluginId}`}
|
||||
@ -159,40 +165,25 @@ const PluginStates: React.FC<Props> = props => {
|
||||
!props.shouldShowBasedOnSearchQuery || props.shouldShowBasedOnSearchQuery(searchInputSearchText())
|
||||
);
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const searchSection = (
|
||||
<SearchPlugins
|
||||
pluginSettings={pluginSettings}
|
||||
themeId={props.themeId}
|
||||
onUpdatePluginStates={props.updatePluginStates}
|
||||
installingPluginIds={installingPluginIds}
|
||||
callbacks={pluginCallbacks}
|
||||
repoApiInitialized={repoApiLoaded}
|
||||
repoApi={repoApi}
|
||||
updatingPluginIds={updatingPluginIds}
|
||||
updatablePluginIds={updatablePluginIds}
|
||||
onShowPluginInfo={onShowPluginInfo}
|
||||
|
||||
const searchAccordion = (
|
||||
<List.Accordion
|
||||
title={_('Install new plugins')}
|
||||
description={_('Browse and install community plugins.')}
|
||||
id='search'
|
||||
>
|
||||
<SearchPlugins
|
||||
pluginSettings={pluginSettings}
|
||||
themeId={props.themeId}
|
||||
onUpdatePluginStates={props.updatePluginStates}
|
||||
installingPluginIds={installingPluginIds}
|
||||
callbacks={pluginCallbacks}
|
||||
repoApiInitialized={repoApiLoaded}
|
||||
repoApi={repoApi}
|
||||
updatingPluginIds={updatingPluginIds}
|
||||
updatablePluginIds={updatablePluginIds}
|
||||
onShowPluginInfo={onShowPluginInfo}
|
||||
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
/>
|
||||
</List.Accordion>
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
/>
|
||||
);
|
||||
|
||||
const isSearching = !!props.shouldShowBasedOnSearchQuery;
|
||||
// Don't include the number of installed plugins when searching -- only a few of the total
|
||||
// may be shown by the search.
|
||||
const installedAccordionDescription = !isSearching ? _n('You currently have %d plugin installed.', 'You currently have %d plugins installed.', pluginIds.length, pluginIds.length) : null;
|
||||
|
||||
// Using a different wrapper prevents the installed item group from being openable when
|
||||
// there are no plugins:
|
||||
const InstalledItemWrapper = pluginIds.length ? List.Accordion : List.Item;
|
||||
const isSearching = !!props.shouldShowBasedOnSearchQuery || isPluginSearching;
|
||||
|
||||
return (
|
||||
<View>
|
||||
@ -202,20 +193,14 @@ const PluginStates: React.FC<Props> = props => {
|
||||
</Banner>
|
||||
<Divider/>
|
||||
|
||||
<List.AccordionGroup>
|
||||
<InstalledItemWrapper
|
||||
title={_('Installed plugins')}
|
||||
description={installedAccordionDescription}
|
||||
id='installed'
|
||||
>
|
||||
<View style={styles.installedPluginsContainer}>
|
||||
{installedPluginCards}
|
||||
</View>
|
||||
</InstalledItemWrapper>
|
||||
<Divider/>
|
||||
{showSearch ? searchAccordion : null}
|
||||
<Divider/>
|
||||
</List.AccordionGroup>
|
||||
{showSearch ? searchSection : null}
|
||||
<View style={styles.installedPluginsContainer}>
|
||||
<SectionLabel visible={!isSearching}>
|
||||
{pluginIds.length ? _('Installed (%d):', pluginIds.length) : _('No plugins are installed.')}
|
||||
</SectionLabel>
|
||||
{installedPluginCards}
|
||||
</View>
|
||||
|
||||
<PluginInfoModal
|
||||
themeId={props.themeId}
|
||||
pluginSettings={pluginSettings}
|
||||
@ -227,6 +212,7 @@ const PluginStates: React.FC<Props> = props => {
|
||||
onModalDismiss={onPluginDialogClosed}
|
||||
pluginCallbacks={pluginCallbacks}
|
||||
/>
|
||||
<Divider/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -5,7 +5,7 @@ import { _ } from '@joplin/lib/locale';
|
||||
import { PluginManifest } from '@joplin/lib/services/plugins/utils/types';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { FlatList, StyleSheet, View } from 'react-native';
|
||||
import { TextInput, Text } from 'react-native-paper';
|
||||
import { TextInput } from 'react-native-paper';
|
||||
import PluginBox, { InstallState } from './PluginBox';
|
||||
import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
|
||||
import { PluginItem } from '@joplin/lib/components/shared/config/plugins/types';
|
||||
@ -13,6 +13,7 @@ import RepositoryApi from '@joplin/lib/services/plugins/RepositoryApi';
|
||||
import openWebsiteForPlugin from './utils/openWebsiteForPlugin';
|
||||
import { PluginCallback, PluginCallbacks } from './utils/usePluginCallbacks';
|
||||
import InstalledPluginBox from './InstalledPluginBox';
|
||||
import SectionLabel from './SectionLabel';
|
||||
|
||||
interface Props {
|
||||
themeId: number;
|
||||
@ -42,14 +43,11 @@ const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'column',
|
||||
margin: 12,
|
||||
},
|
||||
resultsCounter: {
|
||||
margin: 12,
|
||||
marginTop: 17,
|
||||
marginBottom: 4,
|
||||
marginBottom: 0,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const PluginSearch: React.FC<Props> = props => {
|
||||
const { searchQuery, setSearchQuery } = props;
|
||||
const [searchResultManifests, setSearchResultManifests] = useState<PluginManifest[]>([]);
|
||||
@ -133,12 +131,16 @@ const PluginSearch: React.FC<Props> = props => {
|
||||
}
|
||||
}, [onInstall, props.themeId, props.pluginSettings, props.updatingPluginIds, props.updatablePluginIds, props.onShowPluginInfo, props.callbacks]);
|
||||
|
||||
const renderResultsCount = () => {
|
||||
if (!searchQuery.length) return null;
|
||||
const onClearSearch = useCallback(() => {
|
||||
setSearchQuery('');
|
||||
}, [setSearchQuery]);
|
||||
|
||||
return <Text style={styles.resultsCounter} variant='labelLarge'>
|
||||
{_('Results (%d):', searchResults.length)}
|
||||
</Text>;
|
||||
const renderSearchButton = () => {
|
||||
if (searchQuery) {
|
||||
return <TextInput.Icon onPress={onClearSearch} accessibilityLabel={_('Clear search')} icon='close' />;
|
||||
} else {
|
||||
return <TextInput.Icon icon='magnify' aria-hidden={true} importantForAccessibility='no-hide-descendants'/>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@ -146,13 +148,13 @@ const PluginSearch: React.FC<Props> = props => {
|
||||
<TextInput
|
||||
testID='searchbar'
|
||||
mode='outlined'
|
||||
left={<TextInput.Icon icon='magnify' />}
|
||||
placeholder={_('Search plugins')}
|
||||
right={renderSearchButton()}
|
||||
placeholder={_('Search for plugins...')}
|
||||
onChangeText={setSearchQuery}
|
||||
value={searchQuery}
|
||||
editable={props.repoApiInitialized}
|
||||
/>
|
||||
{renderResultsCount()}
|
||||
<SectionLabel visible={!!searchQuery.length}>{_('Results (%d):', searchResults.length)}</SectionLabel>
|
||||
<FlatList
|
||||
data={searchResults}
|
||||
renderItem={renderResult}
|
||||
|
@ -0,0 +1,24 @@
|
||||
import * as React from 'react';
|
||||
import { ViewStyle } from 'react-native';
|
||||
import { Text } from 'react-native-paper';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
const style: ViewStyle = {
|
||||
margin: 12,
|
||||
marginTop: 17,
|
||||
marginBottom: 4,
|
||||
};
|
||||
|
||||
const SectionLabel: React.FC<Props> = props => {
|
||||
if (!props.visible) return null;
|
||||
|
||||
return <Text style={style} variant='labelLarge'>
|
||||
{props.children}
|
||||
</Text>;
|
||||
};
|
||||
|
||||
export default SectionLabel;
|
@ -8,6 +8,7 @@ import { PaperProvider } from 'react-native-paper';
|
||||
import PluginStates from '../PluginStates';
|
||||
import { AppState } from '../../../../../utils/types';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { MenuProvider } from 'react-native-popup-menu';
|
||||
|
||||
interface WrapperProps {
|
||||
initialPluginSettings: PluginSettings;
|
||||
@ -29,15 +30,17 @@ const PluginStatesWrapper = (props: WrapperProps) => {
|
||||
|
||||
return (
|
||||
<Provider store={props.store}>
|
||||
<PaperProvider>
|
||||
<PluginStates
|
||||
styles={styles}
|
||||
themeId={Setting.THEME_LIGHT}
|
||||
updatePluginStates={updatePluginStates}
|
||||
pluginSettings={pluginSettings}
|
||||
shouldShowBasedOnSearchQuery={shouldShowBasedOnSettingSearchQuery}
|
||||
/>
|
||||
</PaperProvider>
|
||||
<MenuProvider>
|
||||
<PaperProvider>
|
||||
<PluginStates
|
||||
styles={styles}
|
||||
themeId={Setting.THEME_LIGHT}
|
||||
updatePluginStates={updatePluginStates}
|
||||
pluginSettings={pluginSettings}
|
||||
shouldShowBasedOnSearchQuery={shouldShowBasedOnSettingSearchQuery}
|
||||
/>
|
||||
</PaperProvider>
|
||||
</MenuProvider>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user