Skip to content

Commit

Permalink
feat(ui/plugins): Bunny plugin sheet impl
Browse files Browse the repository at this point in the history
  • Loading branch information
pylixonly committed Nov 16, 2024
1 parent cc87948 commit c168237
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 64 deletions.
38 changes: 19 additions & 19 deletions src/core/ui/settings/pages/Plugins/components/PluginCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CardWrapper } from "@core/ui/components/AddonCard";
import { UnifiedPluginModel } from "@core/ui/settings/pages/Plugins/models";
import { usePluginCardStyles } from "@core/ui/settings/pages/Plugins/usePluginCardStyles";
import { findAssetId } from "@lib/api/assets";
import { NavigationNative, tokens } from "@metro/common";
Expand All @@ -8,8 +9,6 @@ import chroma from "chroma-js";
import { createContext, useContext, useMemo } from "react";
import { Image, View } from "react-native";

import { UnifiedPluginModel } from "..";

const CardContext = createContext<{ plugin: UnifiedPluginModel, result: Fuzzysort.KeysResult<UnifiedPluginModel>; }>(null!);
const useCardContext = () => useContext(CardContext);

Expand Down Expand Up @@ -50,6 +49,8 @@ function Title() {

function Authors() {
const { plugin, result } = useCardContext();
const styles = usePluginCardStyles();

if (!plugin.authors) return null;

// could be empty if the author(s) are irrelevant with the search!
Expand All @@ -59,24 +60,23 @@ function Authors() {
</Text>
);

if (highlightedNode.length > 0) return (
<Text variant="text-md/semibold" color="text-muted">
by {highlightedNode}
</Text>
);

const children = ["by "];
const badges = plugin.getBadges();
const authorText = highlightedNode.length > 0 ? highlightedNode : plugin.authors.map(a => a.name).join(", ");

for (const author of plugin.authors) {
children.push(typeof author === "string" ? author : author.name);
children.push(", ");
}

children.pop();

return <Text variant="text-md/semibold" color="text-muted">
{children}
</Text>;
return (
<View style={{ flexDirection: "row", flexWrap: "wrap", flexShrink: 1, gap: 4 }}>
<Text variant="text-sm/semibold" color="text-muted">
by {authorText}
</Text>
{badges.length > 0 && <View style={styles.badgesContainer}>
{badges.map((b, i) => <Image
key={i}
source={b.source}
style={styles.badgeIcon}
/>)}
</View>}
</View>
);
}

function Description() {
Expand Down
16 changes: 1 addition & 15 deletions src/core/ui/settings/pages/Plugins/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,10 @@ import { Button, Card, FlashList, IconButton, Text } from "@metro/common/compone
import { ComponentProps } from "react";
import { View } from "react-native";

import { UnifiedPluginModel } from "./models";
import unifyBunnyPlugin from "./models/bunny";
import unifyVdPlugin from "./models/vendetta";

export interface UnifiedPluginModel {
id: string;
name: string;
description?: string;
authors?: Author[];
icon?: string;

isEnabled(): boolean;
usePluginState(): void;
isInstalled(): boolean;
toggle(start: boolean): void;
resolveSheetComponent(): Promise<{ default: React.ComponentType<any>; }>;
getPluginSettingsComponent(): React.ComponentType<any> | null | undefined;
}

const { openAlert } = lazyDestructure(() => findByProps("openAlert", "dismissAlert"));
const { AlertModal, AlertActions, AlertActionButton } = lazyDestructure(() => findByProps("AlertModal", "AlertActions"));

Expand Down
10 changes: 9 additions & 1 deletion src/core/ui/settings/pages/Plugins/models/bunny.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { PyoncordIcon } from "@core/ui/settings";
import { disablePlugin, enablePlugin, getPluginSettingsComponent, isPluginEnabled, pluginSettings } from "@lib/addons/plugins";
import { BunnyPluginManifest } from "@lib/addons/plugins/types";
import { useObservable } from "@lib/api/storage";

import { UnifiedPluginModel } from "..";
import { UnifiedPluginModel } from ".";

export default function unifyBunnyPlugin(manifest: BunnyPluginManifest): UnifiedPluginModel {
return {
id: manifest.id,
name: manifest.display.name,
description: manifest.display.description,
authors: manifest.display.authors,

getBadges() {
return [
{ source: { uri: PyoncordIcon } },
// { source: findAssetId("CheckmarkLargeBoldIcon")! }
];
},
isEnabled: () => isPluginEnabled(manifest.id),
isInstalled: () => manifest.id in pluginSettings,
usePluginState() {
Expand Down
23 changes: 23 additions & 0 deletions src/core/ui/settings/pages/Plugins/models/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Author } from "@lib/addons/types";
import { ImageSourcePropType } from "react-native";

interface Badge {
source: ImageSourcePropType;
color?: string;
onPress?: () => void;
}

export interface UnifiedPluginModel {
id: string;
name: string;
description?: string;
authors?: Author[];
icon?: string;
getBadges(): Badge[];
isEnabled(): boolean;
usePluginState(): void;
isInstalled(): boolean;
toggle(start: boolean): void;
resolveSheetComponent(): Promise<{ default: React.ComponentType<any>; }>;
getPluginSettingsComponent(): React.ComponentType<any> | null | undefined;
}
5 changes: 4 additions & 1 deletion src/core/ui/settings/pages/Plugins/models/vendetta.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { VdPluginManager, VendettaPlugin } from "@core/vendetta/plugins";
import { useProxy } from "@core/vendetta/storage";

import { UnifiedPluginModel } from "..";
import { UnifiedPluginModel } from ".";

export default function unifyVdPlugin(vdPlugin: VendettaPlugin): UnifiedPluginModel {
return {
Expand All @@ -11,6 +11,9 @@ export default function unifyVdPlugin(vdPlugin: VendettaPlugin): UnifiedPluginMo
authors: vdPlugin.manifest.authors,
icon: vdPlugin.manifest.vendetta?.icon,

getBadges() {
return [];
},
isEnabled: () => vdPlugin.enabled,
isInstalled: () => Boolean(vdPlugin && VdPluginManager.plugins[vdPlugin.id]),
usePluginState() {
Expand Down
117 changes: 89 additions & 28 deletions src/core/ui/settings/pages/Plugins/sheets/PluginInfoActionSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,106 @@
import { startPlugin } from "@lib/addons/plugins";
import { findAssetId } from "@lib/api/assets";
import { hideSheet } from "@lib/ui/sheets";
import { ActionSheet, Button, Text } from "@metro/common/components";
import { ActionSheet, Card, ContextMenu, IconButton, Text } from "@metro/common/components";
import { ComponentProps } from "react";
import { ScrollView, View } from "react-native";

import { PluginInfoActionSheetProps } from "./common";
import TitleComponent from "./TitleComponent";

function PluginInfoIconButton(props: ComponentProps<typeof IconButton>) {
const { onPress } = props;
props.onPress &&= () => {
hideSheet("PluginInfoActionSheet");
onPress?.();
};

return <IconButton {...props} />;
}

export default function PluginInfoActionSheet({ plugin, navigation }: PluginInfoActionSheetProps) {
plugin.usePluginState();

return <ActionSheet>
<ScrollView contentContainerStyle={{ gap: 8, marginBottom: 12 }}>
<View style={{ flexDirection: "row", alignItems: "center", paddingVertical: 24 }}>
<View style={{ gap: 4 }}>
<Text variant="heading-xl/semibold">
{plugin.name}
</Text>
<Text variant="text-md/medium" color="text-muted">
{plugin.description}
</Text>
</View>
<View style={{ marginLeft: "auto" }}>
{plugin.getPluginSettingsComponent() && <Button
size="md"
text="Configure"
<ScrollView contentContainerStyle={{ gap: 12, marginBottom: 12 }}>
<View style={{ flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 24, justifyContent: "space-between", width: "100%" }}>
<TitleComponent plugin={plugin} />
<ContextMenu
items={[
{
label: "Details",
iconSource: findAssetId("CircleInformationIcon-primary"),
action: () => {
}
},
// {
// label: true ? "Disable Updates" : "Enable Updates",
// iconSource: true ? findAssetId("ClockXIcon") : findAssetId("ClockIcon"),
// action: () => {

// }
// },
{
label: "Clear Data",
iconSource: findAssetId("CopyIcon"),
variant: "destructive",
action: () => {
}
},
{
label: "Uninstall",
iconSource: findAssetId("TrashIcon"),
variant: "destructive",
action: () => {
}
}
]}
>
{props => <IconButton
{...props}
icon={findAssetId("MoreHorizontalIcon")}
variant="secondary"
icon={findAssetId("WrenchIcon")}
onPress={() => {
hideSheet("PluginInfoActionSheet");
navigation.push("BUNNY_CUSTOM_PAGE", {
title: plugin.name,
render: plugin.getPluginSettingsComponent(),
});
}}
size="sm"
/>}
</View>
</ContextMenu>
</View>
<View style={{ flexDirection: "row", justifyContent: "center", alignContent: "center" }}>
{/* <Text variant="text-lg/medium">
Oops, you shouldn't see this!
</Text> */}
<View style={{ flexDirection: "row", justifyContent: "space-around", alignContent: "center" }}>
<PluginInfoIconButton
label="Configure"
variant="secondary"
disabled={!plugin.getPluginSettingsComponent()}
icon={findAssetId("WrenchIcon")}
onPress={() => {
navigation.push("BUNNY_CUSTOM_PAGE", {
title: plugin.name,
render: plugin.getPluginSettingsComponent(),
});
}}
/>
<PluginInfoIconButton
label="Refetch"
variant="secondary"
icon={findAssetId("RetryIcon")}
onPress={() => {
startPlugin(plugin.id);
}}
/>
<PluginInfoIconButton
label="Copy URL"
variant="secondary"
icon={findAssetId("LinkIcon")}
onPress={() => {
}}
/>
</View>
<Card>
<Text variant="text-md/semibold" color="text-primary" style={{ marginBottom: 4 }}>
Description
</Text>
<Text variant="text-md/medium">
{plugin.description}
</Text>
</Card>
</ScrollView>
</ActionSheet>;
}
57 changes: 57 additions & 0 deletions src/core/ui/settings/pages/Plugins/sheets/TitleComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { UnifiedPluginModel } from "@core/ui/settings/pages/Plugins/models";
import { lazyDestructure } from "@lib/utils/lazy";
import { findByNameLazy, findByProps } from "@metro";
import { FluxUtils } from "@metro/common";
import { Avatar, AvatarPile, Text } from "@metro/common/components";
import { UserStore } from "@metro/common/stores";
import { View } from "react-native";

const showUserProfileActionSheet = findByNameLazy("showUserProfileActionSheet");
const { getUser: maybeFetchUser } = lazyDestructure(() => findByProps("getUser", "fetchProfile"));

export default function TitleComponent({ plugin }: { plugin: UnifiedPluginModel; }) {
const users: any[] = FluxUtils.useStateFromStoresArray([UserStore], () => {
plugin.authors?.forEach(a => a.id && maybeFetchUser(a.id));
return plugin.authors?.map(a => UserStore.getUser(a.id));
});

const { authors } = plugin;
const authorTextNode = [];

if (authors) {
for (const author of authors) {
authorTextNode.push(<Text
onPress={() => showUserProfileActionSheet({ userId: author.id })}
variant="text-md/medium"
>
{author.name}
</Text>);

authorTextNode.push(", ");
}

authorTextNode.pop();
}

return <View style={{ gap: 4 }}>
<View>
<Text variant="heading-xl/semibold">
{plugin.name}
</Text>
</View>
<View style={{ flexDirection: "row", flexShrink: 1 }}>
{authors?.length && <View style={{ flexDirection: "row", gap: 8, alignItems: "center", paddingVertical: 4, paddingHorizontal: 8, backgroundColor: "#00000016", borderRadius: 32 }}>
{users.length && <AvatarPile
size="xxsmall"
names={plugin.authors?.map(a => a.name)}
totalCount={plugin.authors?.length}
>
{users.map(a => <Avatar size="xxsmall" user={a} />)}
</AvatarPile>}
<Text variant="text-md/medium">
{authorTextNode}
</Text>
</View>}
</View>
</View>;
}
12 changes: 12 additions & 0 deletions src/core/ui/settings/pages/Plugins/usePluginCardStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,17 @@ export const usePluginCardStyles = createStyles({
tintColor: tokens.colors.LOGO_PRIMARY,
height: 18,
width: 18,
},
badgeIcon: {
tintColor: tokens.colors.LOGO_PRIMARY,
height: 12,
width: 12,
},
badgesContainer: {
flexWrap: "wrap",
flexDirection: "row",
gap: 6,
borderRadius: 6,
padding: 4
}
});
1 change: 1 addition & 0 deletions src/lib/addons/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ export async function uninstallPlugin(id: string) {
pluginInstances.has(id) && stopPlugin(id);
delete pluginSettings[id];

await purgeStorage(`plugins/storage/${id}.json`);
await removeFile(`plugins/scripts/${id}.js`);
}

Expand Down
1 change: 1 addition & 0 deletions src/metro/common/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const AlertActions = findProp("AlertActions");
export const AvatarPile = findSingular("AvatarPile");

// Misc.
export const ContextMenu = findProp("ContextMenu") as t.ContextMenu;
export const Stack = findProp("Stack") as t.Stack;
export const Avatar = findProp("default", "AvatarSizes", "getStatusSize");

Expand Down
Loading

0 comments on commit c168237

Please sign in to comment.