feat(ui/plugins): overflow actions
This commit is contained in:
parent
d6d0801731
commit
51c6b62f5c
5 changed files with 168 additions and 4 deletions
|
|
@ -2,9 +2,10 @@ import { CardWrapper } from "@core/ui/components/AddonCard";
|
||||||
import { requireAssetIndex } from "@lib/api/assets";
|
import { requireAssetIndex } from "@lib/api/assets";
|
||||||
import { useProxy } from "@lib/api/storage";
|
import { useProxy } from "@lib/api/storage";
|
||||||
import { BunnyPlugin, startPlugin, stopPlugin } from "@lib/managers/plugins";
|
import { BunnyPlugin, startPlugin, stopPlugin } from "@lib/managers/plugins";
|
||||||
|
import { showSheet } from "@lib/ui/sheets";
|
||||||
import { lazyDestructure } from "@lib/utils/lazy";
|
import { lazyDestructure } from "@lib/utils/lazy";
|
||||||
import { findByProps } from "@metro";
|
import { findByProps } from "@metro";
|
||||||
import { tokens } from "@metro/common";
|
import { NavigationNative, tokens } from "@metro/common";
|
||||||
import { Card, IconButton, Stack, TableSwitch, Text } from "@metro/common/components";
|
import { Card, IconButton, Stack, TableSwitch, Text } from "@metro/common/components";
|
||||||
import { createContext, useContext } from "react";
|
import { createContext, useContext } from "react";
|
||||||
import { Image, View } from "react-native";
|
import { Image, View } from "react-native";
|
||||||
|
|
@ -79,6 +80,7 @@ function Status() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PluginCard({ item: plugin }: CardWrapper<BunnyPlugin>) {
|
export default function PluginCard({ item: plugin }: CardWrapper<BunnyPlugin>) {
|
||||||
|
const navigation = NavigationNative.useNavigation();
|
||||||
useProxy(plugin);
|
useProxy(plugin);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -94,10 +96,16 @@ export default function PluginCard({ item: plugin }: CardWrapper<BunnyPlugin>) {
|
||||||
<View style={{ marginLeft: "auto" }}>
|
<View style={{ marginLeft: "auto" }}>
|
||||||
<Stack spacing={12} direction="horizontal">
|
<Stack spacing={12} direction="horizontal">
|
||||||
<IconButton
|
<IconButton
|
||||||
onPress={() => { }}
|
onPress={() => {
|
||||||
|
showSheet(
|
||||||
|
"PluginInfoActionSheet",
|
||||||
|
import("./sheets/PluginInfoActionSheet"),
|
||||||
|
{ plugin, navigation }
|
||||||
|
);
|
||||||
|
}}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
icon={requireAssetIndex("SettingsIcon")}
|
icon={requireAssetIndex("MoreHorizontalIcon")}
|
||||||
/>
|
/>
|
||||||
<TableSwitch
|
<TableSwitch
|
||||||
value={plugin.enabled}
|
value={plugin.enabled}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
import { formatString, Strings } from "@core/i18n";
|
||||||
|
import { requireAssetIndex } from "@lib/api/assets";
|
||||||
|
import { purgeStorage } from "@lib/api/storage";
|
||||||
|
import { BunnyPlugin, fetchPlugin, getSettings, removePlugin, startPlugin, stopPlugin } from "@lib/managers/plugins";
|
||||||
|
import { showConfirmationAlert } from "@lib/ui/alerts";
|
||||||
|
import { hideSheet } from "@lib/ui/sheets";
|
||||||
|
import { showToast } from "@lib/ui/toasts";
|
||||||
|
import { ButtonColors } from "@lib/utils/types";
|
||||||
|
import { clipboard } from "@metro/common";
|
||||||
|
import { ActionSheet, IconButton, TableRow, TableRowGroup, Text } from "@metro/common/components";
|
||||||
|
import { ScrollView, View } from "react-native";
|
||||||
|
|
||||||
|
interface InfoProps {
|
||||||
|
plugin: BunnyPlugin;
|
||||||
|
navigation: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps) {
|
||||||
|
const settings = getSettings(plugin.id);
|
||||||
|
|
||||||
|
return <ActionSheet scrollable>
|
||||||
|
<ScrollView style={{ gap: 12 }}>
|
||||||
|
<View style={{ flexDirection: "row", alignItems: "center", paddingVertical: 24 }}>
|
||||||
|
<Text variant="heading-xl/semibold">
|
||||||
|
{plugin.manifest.name}
|
||||||
|
</Text>
|
||||||
|
<View style={{ marginLeft: "auto" }}>
|
||||||
|
{settings && <IconButton
|
||||||
|
size="md"
|
||||||
|
variant="secondary"
|
||||||
|
icon={requireAssetIndex("SettingsIcon")}
|
||||||
|
onPress={() => {
|
||||||
|
hideSheet("PluginInfoActionSheet");
|
||||||
|
navigation.push("VendettaCustomPage", {
|
||||||
|
title: plugin.manifest.name,
|
||||||
|
render: settings,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<TableRowGroup>
|
||||||
|
<TableRow
|
||||||
|
label={Strings.REFETCH}
|
||||||
|
icon={<TableRow.Icon source={requireAssetIndex("RetryIcon")} />}
|
||||||
|
onPress={async () => {
|
||||||
|
if (plugin.enabled) stopPlugin(plugin.id, false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetchPlugin(plugin.id);
|
||||||
|
showToast(Strings.PLUGIN_REFETCH_SUCCESSFUL, requireAssetIndex("toast_image_saved"));
|
||||||
|
} catch {
|
||||||
|
showToast(Strings.PLUGIN_REFETCH_FAILED, requireAssetIndex("Small"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.enabled) await startPlugin(plugin.id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TableRow
|
||||||
|
label={Strings.COPY_URL}
|
||||||
|
icon={<TableRow.Icon source={requireAssetIndex("copy")} />}
|
||||||
|
onPress={() => {
|
||||||
|
clipboard.setString(plugin.id);
|
||||||
|
showToast.showCopyToClipboard();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TableRow
|
||||||
|
label={plugin.update ? Strings.DISABLE_UPDATES : Strings.ENABLE_UPDATES}
|
||||||
|
icon={<TableRow.Icon source={requireAssetIndex("ic_download_24px")} />}
|
||||||
|
onPress={() => {
|
||||||
|
plugin.update = !plugin.update;
|
||||||
|
showToast(formatString("TOASTS_PLUGIN_UPDATE", {
|
||||||
|
update: plugin.update,
|
||||||
|
name: plugin.manifest.name
|
||||||
|
}), requireAssetIndex("toast_image_saved"));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TableRow
|
||||||
|
label={Strings.CLEAR_DATA}
|
||||||
|
icon={<TableRow.Icon source={requireAssetIndex("ic_duplicate")} />}
|
||||||
|
onPress={() => showConfirmationAlert({
|
||||||
|
title: Strings.HOLD_UP,
|
||||||
|
content: formatString("ARE_YOU_SURE_TO_CLEAR_DATA", { name: plugin.manifest.name }),
|
||||||
|
confirmText: Strings.CLEAR,
|
||||||
|
cancelText: Strings.CANCEL,
|
||||||
|
confirmColor: ButtonColors.RED,
|
||||||
|
onConfirm: async () => {
|
||||||
|
if (plugin.enabled) stopPlugin(plugin.id, false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetchPlugin(plugin.id);
|
||||||
|
showToast(Strings.PLUGIN_REFETCH_SUCCESSFUL, requireAssetIndex("toast_image_saved"));
|
||||||
|
} catch {
|
||||||
|
showToast(Strings.PLUGIN_REFETCH_FAILED, requireAssetIndex("Small"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let message: any[];
|
||||||
|
try {
|
||||||
|
purgeStorage(plugin.id);
|
||||||
|
message = ["CLEAR_DATA_SUCCESSFUL", "trash"];
|
||||||
|
} catch {
|
||||||
|
message = ["CLEAR_DATA_FAILED", "Small"];
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast(
|
||||||
|
formatString(message[0], { name: plugin.manifest.name }),
|
||||||
|
requireAssetIndex(message[1])
|
||||||
|
);
|
||||||
|
|
||||||
|
if (plugin.enabled) await startPlugin(plugin.id);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<TableRow
|
||||||
|
label={Strings.DELETE}
|
||||||
|
icon={<TableRow.Icon source={requireAssetIndex("ic_message_delete")} />}
|
||||||
|
onPress={() => showConfirmationAlert({
|
||||||
|
title: Strings.HOLD_UP,
|
||||||
|
content: formatString("ARE_YOU_SURE_TO_DELETE_PLUGIN", { name: plugin.manifest.name }),
|
||||||
|
confirmText: Strings.DELETE,
|
||||||
|
cancelText: Strings.CANCEL,
|
||||||
|
confirmColor: ButtonColors.RED,
|
||||||
|
onConfirm: () => {
|
||||||
|
try {
|
||||||
|
removePlugin(plugin.id);
|
||||||
|
} catch (e) {
|
||||||
|
showToast(String(e), requireAssetIndex("Small"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</TableRowGroup>
|
||||||
|
</ScrollView>
|
||||||
|
</ActionSheet>;
|
||||||
|
}
|
||||||
15
src/lib/ui/sheets.ts
Normal file
15
src/lib/ui/sheets.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { findByPropsProxy } from "@metro/utils";
|
||||||
|
|
||||||
|
const actionSheet = findByPropsProxy("openLazy", "hideActionSheet");
|
||||||
|
|
||||||
|
export function showSheet<T extends React.ComponentType<any>>(
|
||||||
|
key: string,
|
||||||
|
lazyImport: Promise<{ default: T; }>,
|
||||||
|
props?: React.ComponentProps<T>
|
||||||
|
) {
|
||||||
|
actionSheet.openLazy(lazyImport, key, props ?? {});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hideSheet(key: string) {
|
||||||
|
actionSheet.hideActionSheet(key);
|
||||||
|
}
|
||||||
|
|
@ -57,7 +57,7 @@ export const SegmentedControl = findProp("SegmentedControl") as t.SegmentedContr
|
||||||
export const CompatSegmentedControl = findProp("CompatSegmentedControl") as t.CompatSegmentedControl;
|
export const CompatSegmentedControl = findProp("CompatSegmentedControl") as t.CompatSegmentedControl;
|
||||||
|
|
||||||
export const FloatingActionButton = findProp("FloatingActionButton") as t.FloatingActionButton;
|
export const FloatingActionButton = findProp("FloatingActionButton") as t.FloatingActionButton;
|
||||||
export const ActionSheet = findProp("ActionSheet");
|
export const ActionSheet = findProp("ActionSheet") as t.ActionSheet;
|
||||||
export const BottomSheetTitleHeader = findProp("BottomSheetTitleHeader");
|
export const BottomSheetTitleHeader = findProp("BottomSheetTitleHeader");
|
||||||
|
|
||||||
const textsModule = findByPropsProxy("Text", "LegacyText");
|
const textsModule = findByPropsProxy("Text", "LegacyText");
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,12 @@ interface FABProps {
|
||||||
|
|
||||||
export type FloatingActionButton = React.FC<FABProps>;
|
export type FloatingActionButton = React.FC<FABProps>;
|
||||||
|
|
||||||
|
interface ActionSheetProps {
|
||||||
|
scrollable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ActionSheet = React.FC<React.PropsWithChildren<ActionSheetProps>>;
|
||||||
|
|
||||||
type TextProps = React.ComponentProps<typeof RN.Text> & {
|
type TextProps = React.ComponentProps<typeof RN.Text> & {
|
||||||
variant?: DiscordTextStyles;
|
variant?: DiscordTextStyles;
|
||||||
color?: string; // TODO: type this
|
color?: string; // TODO: type this
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue