From 5fe302533921fedb48414a17ce01225a8c9f39c4 Mon Sep 17 00:00:00 2001 From: amsyarasyiq <82711525+amsyarasyiq@users.noreply.github.com> Date: Tue, 30 Jul 2024 20:42:10 +0800 Subject: [PATCH] feat: bunny plugins ui init --- src/core/plugins/index.ts | 2 +- .../forumPost.tsx | 0 .../index.ts | 4 +- .../url.tsx | 0 src/core/ui/components/AddonPage.tsx | 9 +- src/core/ui/components/InstallButton.tsx | 2 +- src/core/ui/settings/pages/Plugins/index.tsx | 92 ++++++++++++++++--- src/lib/api/storage/index.ts | 1 + src/lib/plugins/types.ts | 2 +- src/lib/ui/components/ErrorBoundary.tsx | 3 +- src/lib/ui/components/index.ts | 5 - src/metro/common/components.ts | 2 + src/metro/common/types/components.ts | 44 +++++---- 13 files changed, 117 insertions(+), 49 deletions(-) rename src/core/plugins/{vd-quick-install => quickinstall}/forumPost.tsx (100%) rename src/core/plugins/{vd-quick-install => quickinstall}/index.ts (85%) rename src/core/plugins/{vd-quick-install => quickinstall}/url.tsx (100%) diff --git a/src/core/plugins/index.ts b/src/core/plugins/index.ts index 6b2e1ea..f9b76a9 100644 --- a/src/core/plugins/index.ts +++ b/src/core/plugins/index.ts @@ -7,7 +7,7 @@ interface CorePlugin { // Called from @lib/plugins export const getCorePlugins = (): Record => ({ - "vd-quick-install": require("./vd-quick-install") + "bunny.quickinstall": require("./vd-quick-install") }); /** diff --git a/src/core/plugins/vd-quick-install/forumPost.tsx b/src/core/plugins/quickinstall/forumPost.tsx similarity index 100% rename from src/core/plugins/vd-quick-install/forumPost.tsx rename to src/core/plugins/quickinstall/forumPost.tsx diff --git a/src/core/plugins/vd-quick-install/index.ts b/src/core/plugins/quickinstall/index.ts similarity index 85% rename from src/core/plugins/vd-quick-install/index.ts rename to src/core/plugins/quickinstall/index.ts index 931c228..f56336a 100644 --- a/src/core/plugins/vd-quick-install/index.ts +++ b/src/core/plugins/quickinstall/index.ts @@ -6,11 +6,11 @@ let patches = [] as (() => unknown)[]; export default defineCorePlugin({ manifest: { - id: "bunny.quick.install", + id: "bunny.quickinstall", name: "QuickInstall", version: "1.0.0", description: "Quickly install Vendetta plugins and themes", - authors: [{ name: "pylixonly" }] + authors: ["pyoncord"] }, start() { patches = [patchForumPost(), patchUrl()]; diff --git a/src/core/plugins/vd-quick-install/url.tsx b/src/core/plugins/quickinstall/url.tsx similarity index 100% rename from src/core/plugins/vd-quick-install/url.tsx rename to src/core/plugins/quickinstall/url.tsx diff --git a/src/core/ui/components/AddonPage.tsx b/src/core/ui/components/AddonPage.tsx index 85ca8dc..068d9a6 100644 --- a/src/core/ui/components/AddonPage.tsx +++ b/src/core/ui/components/AddonPage.tsx @@ -17,7 +17,7 @@ type SearchKeywords = Array string)>; interface AddonPageProps { title: string; - fetchFunction: (url: string) => Promise; + fetchFunction?: (url: string) => Promise; items: Record; resolveItem?: (value: any) => T | undefined; safeModeMessage: string; @@ -32,7 +32,6 @@ const { FlashList } = findByProps("FlashList"); export default function AddonPage({ card: CardComponent, ...props }: AddonPageProps) { useProxy(settings); - useProxy(props.items); const [search, setSearch] = React.useState(""); @@ -74,7 +73,7 @@ export default function AddonPage({ card: CardComponent, ...pr )} /> - { // from ./InstallButton.tsx @@ -82,13 +81,13 @@ export default function AddonPage({ card: CardComponent, ...pr showInputAlert({ initialValue: content.match(HTTP_REGEX_MULTI)?.[0] ?? "", placeholder: Strings.URL_PLACEHOLDER, - onConfirm: (input: string) => props.fetchFunction(input), + onConfirm: (input: string) => props.fetchFunction!(input), confirmText: Strings.INSTALL, cancelText: Strings.CANCEL, }) ); })} - /> + />} ); } diff --git a/src/core/ui/components/InstallButton.tsx b/src/core/ui/components/InstallButton.tsx index a810bd1..204d6a7 100644 --- a/src/core/ui/components/InstallButton.tsx +++ b/src/core/ui/components/InstallButton.tsx @@ -24,7 +24,7 @@ export default function InstallButton({ alertTitle, installFunction: fetchFuncti return ( - clipboard.getString().then(content => + clipboard.getString().then((content: string) => showInputAlert({ title: alertTitle, initialValue: content.match(HTTP_REGEX_MULTI)?.[0] ?? "", diff --git a/src/core/ui/settings/pages/Plugins/index.tsx b/src/core/ui/settings/pages/Plugins/index.tsx index 7510e61..e4a6fa5 100644 --- a/src/core/ui/settings/pages/Plugins/index.tsx +++ b/src/core/ui/settings/pages/Plugins/index.tsx @@ -4,7 +4,11 @@ import PluginCard from "@core/ui/settings/pages/Plugins/PluginCard"; import { VdPluginManager, VendettaPlugin } from "@core/vendetta/plugins"; import { settings } from "@lib/api/settings"; import { useProxy } from "@lib/api/storage"; +import { registeredPlugins } from "@lib/plugins"; +import { BunnyPluginManifest } from "@lib/plugins/types"; import { Author } from "@lib/utils/types"; +import { SegmentedControl, SegmentedControlPages, useSegmentedControlState } from "@metro/common/components"; +import { useWindowDimensions, View } from "react-native"; export interface PluginManifest { id: string; @@ -47,22 +51,80 @@ function resolveFromVdPlugin(vdPlugin: VendettaPlugin): PluginManifest { }; } +function resolveFromBunnyPlugin(manifest: BunnyPluginManifest): PluginManifest { + return { + id: manifest.id, + name: manifest.name, + description: manifest.description, + authors: manifest.authors, + isEnabled() { + return false; + }, + usePluginState() { + + }, + toggle(start: boolean) { + + }, + resolveSheetComponent() { + return import("./sheets/PluginInfoActionSheet"); + }, + getPluginSettingsComponent() { + return null; + }, + }; +} + +function Page({ items, resolveItem, fetchFunction }: Record) { + return + card={PluginCard} + title={Strings.PLUGINS} + searchKeywords={[ + "name", + "description", + p => p.authors?.map((a: Author | string) => typeof a === "string" ? a : a.name).join() + ]} + items={items} + resolveItem={resolveItem} + fetchFunction={fetchFunction} + safeModeMessage={Strings.SAFE_MODE_NOTICE_PLUGINS} + />; +} + export default function Plugins() { useProxy(settings); - return ( - - title={Strings.PLUGINS} - searchKeywords={[ - "name", - "description", - p => p.authors?.map((a: Author | string) => typeof a === "string" ? a : a.name).join() - ]} - items={VdPluginManager.plugins} - resolveItem={resolveFromVdPlugin} - fetchFunction={async () => { /** TODO */ }} - safeModeMessage={Strings.SAFE_MODE_NOTICE_PLUGINS} - card={PluginCard} - /> - ); + const { width: pageWidth } = useWindowDimensions(); + + const state = useSegmentedControlState({ + pageWidth, + items: [ + { + label: "Vendetta", + id: "vendetta-plugins", + page: ( + VdPluginManager.installPlugin(url)} + /> + ) + }, + { + label: "Bunny", + id: "bunny-plugins", + page: ( + + ) + }, + ] + }); + + return + + + ; } diff --git a/src/lib/api/storage/index.ts b/src/lib/api/storage/index.ts index 207441e..52c5687 100644 --- a/src/lib/api/storage/index.ts +++ b/src/lib/api/storage/index.ts @@ -58,6 +58,7 @@ export function createProxy(target: any = {}): { proxy: any; emitter: Emitter; } export function useProxy(storage: T): T { const emitter = (storage as any)[emitterSymbol] as Emitter; + if (!emitter) throw new Error("storage[emitterSymbol] is undefined"); const [, forceUpdate] = React.useReducer(n => ~n, 0); diff --git a/src/lib/plugins/types.ts b/src/lib/plugins/types.ts index 75a3b4c..b8507b2 100644 --- a/src/lib/plugins/types.ts +++ b/src/lib/plugins/types.ts @@ -26,7 +26,7 @@ export interface BunnyPluginManifest { readonly name: string; readonly description: string; readonly version: string; - readonly authors: Author[]; + readonly authors: (Author | string)[]; } export interface BunnyPluginManifestInternal extends BunnyPluginManifest { diff --git a/src/lib/ui/components/ErrorBoundary.tsx b/src/lib/ui/components/ErrorBoundary.tsx index a2009cd..d6c71fc 100644 --- a/src/lib/ui/components/ErrorBoundary.tsx +++ b/src/lib/ui/components/ErrorBoundary.tsx @@ -1,4 +1,5 @@ import { Strings } from "@core/i18n"; +import { Nullish } from "@lib/utils/types"; import { React } from "@metro/common"; import { Button, LegacyFormText } from "@metro/common/components"; import { Codeblock } from "@ui/components"; @@ -13,7 +14,7 @@ type ErrorBoundaryState = { }; export interface ErrorBoundaryProps { - children: JSX.Element | JSX.Element[]; + children: JSX.Element | Nullish | (JSX.Element | Nullish)[]; } const styles = createThemedStyleSheet({ diff --git a/src/lib/ui/components/index.ts b/src/lib/ui/components/index.ts index 8c55c24..57d8407 100644 --- a/src/lib/ui/components/index.ts +++ b/src/lib/ui/components/index.ts @@ -1,9 +1,4 @@ -// Discord -// export * as discord from "./discord"; - -// Vendetta export { default as Codeblock } from "@ui/components/Codeblock"; -export { default as ContextMenu } from "@ui/components/ContextMenu"; export { default as ErrorBoundary } from "@ui/components/ErrorBoundary"; export { default as Search } from "@ui/components/Search"; export { default as Summary } from "@ui/components/Summary"; diff --git a/src/metro/common/components.ts b/src/metro/common/components.ts index f0e8e2a..b9d9fa8 100644 --- a/src/metro/common/components.ts +++ b/src/metro/common/components.ts @@ -59,6 +59,8 @@ export const TextInput = findSingular("TextInput") as t.TextInput; // SegmentedControl export const SegmentedControl = findProp("SegmentedControl") as t.SegmentedControl; +export const SegmentedControlPages = findProp("SegmentedControlPages") as t.SegmentedControlPages; +export const useSegmentedControlState = findSingular("useSegmentedControlState") as (...args: unknown[]) => t.SegmentedControlState; export const CompatSegmentedControl = findProp("CompatSegmentedControl") as t.CompatSegmentedControl; export const FloatingActionButton = findProp("FloatingActionButton") as t.FloatingActionButton; diff --git a/src/metro/common/types/components.ts b/src/metro/common/types/components.ts index ffa5708..6a577d9 100644 --- a/src/metro/common/types/components.ts +++ b/src/metro/common/types/components.ts @@ -1,8 +1,9 @@ import { Nullish } from "@lib/utils/types"; import { DiscordTextStyles } from "@ui/types"; -import { ReactNode } from "react"; +import { MutableRefObject, ReactNode, RefObject } from "react"; import type * as RN from "react-native"; import { ImageSourcePropType, PressableProps } from "react-native"; +import { SharedValue } from "react-native-reanimated"; import { LiteralUnion } from "type-fest"; // Abandon all hope, ye who enter here @@ -32,30 +33,37 @@ export type Button = React.ForwardRefExoticComponent; interface SegmentedControlItem { id: string; label: string; - page: JSX.Element | null; + page?: JSX.Element | null; +} + +export interface SegmentedControlState { + activeIndex: SharedValue; + pagerRef: RefObject; + scrollTarget: SharedValue; + scrollOverflow: SharedValue; + scrollOffset: SharedValue; + items: SegmentedControlItem[]; + itemDimensions: SharedValue; + pageWidth: number; + pressedIndex: SharedValue; + onPageChangeRef: MutableRefObject; // { current: undefined } + setActiveIndex: (index: number) => void; + setItemDimensions: (index: number, dimensions: unknown[]) => void; + useReducedMotion: boolean; } interface SegmentedControlProps { - state: any; - // state: { - // activeIndex: SharedValue; - // pagerRef: RefObject; - // scrollTarget: SharedValue; - // scrollOverflow: SharedValue; - // scrollOffset: SharedValue; - // items: SegmentedControlItem[]; - // itemDimensions: SharedValue; - // pageWidth: number; - // pressedIndex: SharedValue; - // onPageChangeRef: MutableRefObject; // { current: undefined } - // setActiveIndex: (index: number) => void; - // setItemDimensions: (index: number, dimensions: unknown[]) => void; - // useReducedMotion: boolean; - // }; + state: SegmentedControlState; } export type SegmentedControl = React.FC; +interface SegmentedControlPagesProps { + state: SegmentedControlState; +} + +export type SegmentedControlPages = React.FC; + interface CompatSegmentedControlProps { values: string[]; selectedSegmentIndex?: number;