feat: highlight search keywords
This commit is contained in:
parent
5afcb7b330
commit
0fc8c69d51
4 changed files with 77 additions and 70 deletions
|
|
@ -71,6 +71,7 @@ interface OverflowAction extends Action {
|
|||
|
||||
export interface CardWrapper<T> {
|
||||
item: T;
|
||||
result: Fuzzysort.KeysResult<T>;
|
||||
}
|
||||
|
||||
interface CardProps {
|
||||
|
|
|
|||
|
|
@ -33,16 +33,13 @@ export default function AddonPage<T extends object>({ card: CardComponent, ...pr
|
|||
|
||||
const [search, setSearch] = React.useState("");
|
||||
|
||||
const items = useMemo(() => {
|
||||
const results = useMemo(() => {
|
||||
let values = props.items;
|
||||
if (props.resolveItem) values = values.map(props.resolveItem);
|
||||
return values.filter(i => i && typeof i === "object");
|
||||
}, [props.items]);
|
||||
const items = values.filter(i => i && typeof i === "object");
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (!search) return items;
|
||||
return fuzzysort.go(search, items, { keys: props.searchKeywords }).map(r => r.obj);
|
||||
}, [items, search]);
|
||||
return fuzzysort.go(search, items, { keys: props.searchKeywords, all: true });
|
||||
}, [props.items, search]);
|
||||
|
||||
const headerElement = (
|
||||
<View style={{ paddingBottom: 8 }}>
|
||||
|
|
@ -58,14 +55,14 @@ export default function AddonPage<T extends object>({ card: CardComponent, ...pr
|
|||
return (
|
||||
<ErrorBoundary>
|
||||
<FlashList
|
||||
data={data}
|
||||
data={results}
|
||||
extraData={search}
|
||||
estimatedItemSize={136}
|
||||
ListHeaderComponent={headerElement}
|
||||
contentContainerStyle={{ padding: 8, paddingHorizontal: 12 }}
|
||||
ItemSeparatorComponent={() => <View style={{ height: 8 }} />}
|
||||
ListFooterComponent={props.ListFooterComponent}
|
||||
renderItem={({ item }: any) => <CardComponent item={item} />}
|
||||
renderItem={({ item }: any) => <CardComponent item={item.obj} result={item} />}
|
||||
/>
|
||||
{(props.fetchFunction ?? props.onFabPress) && <FloatingActionButton
|
||||
icon={findAssetId("PlusLargeIcon")}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,70 @@
|
|||
import { CardWrapper } from "@core/ui/components/AddonCard";
|
||||
import { usePluginCardStyles } from "@core/ui/settings/pages/Plugins/usePluginCardStyles";
|
||||
import { findAssetId } from "@lib/api/assets";
|
||||
import { NavigationNative } from "@metro/common";
|
||||
import { NavigationNative, tokens } from "@metro/common";
|
||||
import { Card, IconButton, Stack, TableSwitch, Text } from "@metro/common/components";
|
||||
import { showSheet } from "@ui/sheets";
|
||||
import { createContext, memo, useContext } from "react";
|
||||
import chroma from "chroma-js";
|
||||
import { createContext, memo, useContext, useMemo } from "react";
|
||||
import { Image, View } from "react-native";
|
||||
|
||||
import { UnifiedPluginModel } from "..";
|
||||
|
||||
const PluginContext = createContext<UnifiedPluginModel>(null!);
|
||||
const usePlugin = () => useContext(PluginContext);
|
||||
const CardContext = createContext<{ plugin: UnifiedPluginModel, result: Fuzzysort.KeysResult<UnifiedPluginModel> }>(null!);
|
||||
const useCardContext = () => useContext(CardContext);
|
||||
|
||||
function getHighlightColor(): import("react-native").ColorValue {
|
||||
return chroma(tokens.unsafe_rawColors.YELLOW_300).alpha(0.3).hex();
|
||||
}
|
||||
|
||||
function Title() {
|
||||
const styles = usePluginCardStyles();
|
||||
const { plugin, result } = useCardContext();
|
||||
|
||||
// could be empty if the plugin name is irrelevant!
|
||||
const highlightedNode = result[0].highlight((m, i) =>
|
||||
<Text key={i} style={{ backgroundColor: getHighlightColor() }}>
|
||||
{m}
|
||||
</Text>
|
||||
);
|
||||
|
||||
const icon = plugin.icon && findAssetId(plugin.icon);
|
||||
|
||||
const textNode = (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
variant="heading-lg/semibold"
|
||||
>
|
||||
{highlightedNode.length ? highlightedNode : plugin.name}
|
||||
</Text>
|
||||
);
|
||||
|
||||
return <View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
|
||||
{icon && <Image
|
||||
style={styles.smallIcon}
|
||||
source={icon}
|
||||
/>}
|
||||
{textNode}
|
||||
</View>;
|
||||
}
|
||||
|
||||
function Authors() {
|
||||
const plugin = usePlugin();
|
||||
const { plugin, result } = useCardContext();
|
||||
if (!plugin.authors) return null;
|
||||
|
||||
// could be empty if the author(s) are irrelevant with the search!
|
||||
const highlightedNode = result[2].highlight((m, i) =>
|
||||
<Text key={i} style={{ backgroundColor: getHighlightColor() }}>
|
||||
{m}
|
||||
</Text>
|
||||
);
|
||||
|
||||
if (highlightedNode.length > 0) return (
|
||||
<Text variant="text-md/semibold" color="text-muted">
|
||||
by {highlightedNode}
|
||||
</Text>
|
||||
);
|
||||
|
||||
const children = ["by "];
|
||||
|
||||
for (const author of plugin.authors) {
|
||||
|
|
@ -30,60 +79,21 @@ function Authors() {
|
|||
</Text>;
|
||||
}
|
||||
|
||||
function Title() {
|
||||
const styles = usePluginCardStyles();
|
||||
const plugin = usePlugin();
|
||||
function Description() {
|
||||
const { plugin, result } = useCardContext();
|
||||
|
||||
const icon = plugin.icon && findAssetId(plugin.icon);
|
||||
|
||||
const textElement = (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
variant="heading-lg/semibold"
|
||||
>
|
||||
{plugin.name}
|
||||
</Text>
|
||||
// could be empty if the description is irrelevant with the search!
|
||||
const highlightedNode = result[1].highlight((m, i) =>
|
||||
<Text key={i} style={{ backgroundColor: getHighlightColor() }}>{m}</Text>
|
||||
);
|
||||
|
||||
return !icon ? textElement : <View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 6
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
style={styles.smallIcon}
|
||||
source={icon}
|
||||
/>
|
||||
{textElement}
|
||||
</View>;
|
||||
return <Text variant="text-md/medium">
|
||||
{highlightedNode.length ? highlightedNode : plugin.description}
|
||||
</Text>;
|
||||
}
|
||||
|
||||
// TODO: Wrap in a Card-ish component with red bg
|
||||
// TODO: Allow glacing at the error's stack
|
||||
// function Status() {
|
||||
// const plugin = usePlugin();
|
||||
// const styles = usePluginCardStyles();
|
||||
// const INTERACTIVE_NORMAL = useToken(tokens.colors.INTERACTIVE_NORMAL);
|
||||
|
||||
// if (!plugin.error) return null;
|
||||
|
||||
// return <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
|
||||
// <View style={styles.smallIcon}>
|
||||
// <Image
|
||||
// tintColor={INTERACTIVE_NORMAL}
|
||||
// source={findAssetId("WarningIcon")}
|
||||
// />
|
||||
// </View>
|
||||
// <Text variant="text-sm/semibold">
|
||||
// There was an error while attempting to start this plugin.
|
||||
// </Text>
|
||||
// </View>;
|
||||
// }
|
||||
|
||||
const Actions = memo(() => {
|
||||
const plugin = usePlugin();
|
||||
const { plugin } = useCardContext();
|
||||
const navigation = NavigationNative.useNavigation();
|
||||
|
||||
return <View style={{ flexDirection: "row", gap: 6 }}>
|
||||
|
|
@ -110,14 +120,15 @@ const Actions = memo(() => {
|
|||
</View>;
|
||||
});
|
||||
|
||||
export default function PluginCard({ item: plugin }: CardWrapper<UnifiedPluginModel>) {
|
||||
export default function PluginCard({ result, item: plugin }: CardWrapper<UnifiedPluginModel>) {
|
||||
plugin.usePluginState();
|
||||
|
||||
const cardContextValue = useMemo(() => ({ plugin, result }), [plugin, result]);
|
||||
|
||||
return (
|
||||
<PluginContext.Provider value={plugin}>
|
||||
<CardContext.Provider value={cardContextValue}>
|
||||
<Card>
|
||||
<Stack spacing={16}>
|
||||
{/* <Status /> */}
|
||||
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
|
||||
<View style={{ flexShrink: 1 }}>
|
||||
<Title />
|
||||
|
|
@ -135,11 +146,9 @@ export default function PluginCard({ item: plugin }: CardWrapper<UnifiedPluginMo
|
|||
</Stack>
|
||||
</View>
|
||||
</View>
|
||||
<Text variant="text-md/medium">
|
||||
{plugin.description}
|
||||
</Text>
|
||||
<Description />
|
||||
</Stack>
|
||||
</Card>
|
||||
</PluginContext.Provider>
|
||||
</CardContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export default function Themes() {
|
|||
searchKeywords={[
|
||||
"manifest.name",
|
||||
"manifest.description",
|
||||
p => p.manifest.authors?.map((a: Author) => a.name).join()
|
||||
p => p.manifest.authors?.map((a: Author) => a.name).join(", ")
|
||||
]}
|
||||
fetchFunction={installTheme}
|
||||
items={Object.values(themes)}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue