feat(lib/plugins): add settings pages for plugins

This commit is contained in:
PalmDevs 2025-06-10 22:22:59 +07:00
parent 88f9000122
commit ca12fac256
No known key found for this signature in database
GPG key ID: 6FAE8F8677ACA8BA
3 changed files with 41 additions and 1 deletions

View file

@ -63,6 +63,7 @@ export function registerPlugin<S extends AnyObject = AnyObject>(
start: options.start,
stop: options.stop,
},
SettingsComponent: options.SettingsComponent,
status: 0,
flags,
disable: () => disablePlugin(plugin),

View file

@ -1,5 +1,6 @@
import type { Storage, StorageOptions } from '@revenge-mod/storage'
import type { AnyObject } from '@revenge-mod/utils/types'
import type { FunctionComponent } from 'react'
import type { PluginApiModules } from './apis/modules'
import type { PluginApiPlugins } from './apis/plugins'
import type { PluginApiReact } from './apis/react'
@ -114,6 +115,7 @@ export interface PluginManifest {
export interface PluginOptions<S extends AnyObject = AnyObject>
extends PluginLifecycles<S> {
SettingsComponent?: PluginSettingsComponent<S>
storage?: Omit<StorageOptions<S>, 'directory'>
}
@ -176,6 +178,11 @@ export interface Plugin<S extends AnyObject = AnyObject> {
*/
errors: unknown[]
/**
* The plugin settings page.
*/
SettingsComponent?: PluginSettingsComponent<S>
/**
* Disable the plugin. This will also stop the plugin if it is running.
*/
@ -185,3 +192,6 @@ export interface Plugin<S extends AnyObject = AnyObject> {
*/
stop(): Promise<void>
}
export type PluginSettingsComponent<S extends AnyObject = AnyObject> =
FunctionComponent<{ api: PluginApi<S> }>

View file

@ -4,6 +4,7 @@ import Page from '@revenge-mod/components/Page'
import SearchInput from '@revenge-mod/components/SearchInput'
import { Tokens } from '@revenge-mod/discord/common'
import { Design } from '@revenge-mod/discord/design'
import { RootNavigationRef } from '@revenge-mod/discord/modules/main_tabs_v2'
import { FlashList } from '@revenge-mod/externals/shopify'
import {
_metas,
@ -17,9 +18,11 @@ import {
import { PluginFlags } from '@revenge-mod/plugins/constants'
import { debounce } from '@revenge-mod/utils/callbacks'
import { useReRender } from '@revenge-mod/utils/react'
import { useCallback, useMemo, useState } from 'react'
import { createElement, useCallback, useMemo, useState } from 'react'
import { Image, useWindowDimensions } from 'react-native'
import { RouteNames, Setting } from '../constants'
import type { InternalPlugin } from '@revenge-mod/plugins/_'
import type { PluginApi } from '@revenge-mod/plugins/types'
const { Card, Text, Stack } = Design
@ -141,6 +144,14 @@ function InstalledPluginCard({
/>
<Text variant="heading-lg/semibold">{name}</Text>
</Stack>
{plugin.SettingsComponent && (
<Design.IconButton
size="sm"
variant="secondary"
icon={getAssetIdByName('SettingsIcon')!}
onPress={() => navigatePluginSettings(plugin)}
/>
)}
<FormSwitch
disabled={essential}
// biome-ignore lint/complexity/useArrowFunction: Async arrows are not supported
@ -176,3 +187,21 @@ function InstalledPluginCard({
</Card>
)
}
// TODO(plugins/settings): Register a custom route instead, so plugin "settings" can actually be pinned and navigated to without hassle.
// This would require us to implement the event-based plugin management system first, so we can listen to plugin enable/disable events and update the settings accordingly.
export function navigatePluginSettings(plugin: InternalPlugin) {
const [api] = _metas.get(plugin.manifest.id)!
const navigation = RootNavigationRef.getRootNavigationRef()
if (!navigation.isReady()) return
navigation.navigate(RouteNames[Setting.RevengeCustomPage], {
render: () =>
createElement(plugin.SettingsComponent!, {
api: api as PluginApi,
}),
options: {
title: plugin.manifest.name,
},
})
}