feat(lib/plugins): switch to event-based system
This commit is contained in:
parent
b1f87967e6
commit
33bb878c5c
6 changed files with 265 additions and 139 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { _initExts, _uapi } from '@revenge-mod/plugins/_'
|
||||
import { _emitter, _uapi } from '@revenge-mod/plugins/_'
|
||||
import { defineLazyProperty } from '@revenge-mod/utils/objects'
|
||||
import { Logger } from './common'
|
||||
import type { UnscopedInitPluginApi } from '@revenge-mod/plugins/types'
|
||||
|
|
@ -42,7 +42,7 @@ defineLazyProperty(discord, 'modules', () => {
|
|||
return modules
|
||||
})
|
||||
|
||||
_initExts.push((api, { manifest: { id } }) =>
|
||||
_emitter.on('init', ({ manifest: { id } }, api) =>
|
||||
defineLazyProperty(
|
||||
api,
|
||||
'logger',
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { TypedEventEmitter } from '@revenge-mod/discord/common/utils'
|
||||
import { getStorage } from '@revenge-mod/storage'
|
||||
import { defineLazyProperty } from '@revenge-mod/utils/objects'
|
||||
import { allSettled, sleepReject } from '@revenge-mod/utils/promises'
|
||||
|
|
@ -21,15 +22,20 @@ import type {
|
|||
|
||||
export const _uapi = uapi
|
||||
|
||||
export const _preInitExts: Array<
|
||||
(api: PreInitPluginApi, plugin: InternalPlugin) => void
|
||||
> = []
|
||||
export const _initExts: Array<
|
||||
(api: InitPluginApi, plugin: InternalPlugin) => void
|
||||
> = []
|
||||
export const _startExts: Array<
|
||||
(api: PluginApi, plugin: InternalPlugin) => void
|
||||
> = []
|
||||
export const _emitter = new TypedEventEmitter<{
|
||||
register: [InternalPlugin]
|
||||
disabled: [InternalPlugin]
|
||||
enabled: [InternalPlugin]
|
||||
preInit: [InternalPlugin, PreInitPluginApi]
|
||||
preInited: [InternalPlugin]
|
||||
init: [InternalPlugin, InitPluginApi]
|
||||
inited: [InternalPlugin]
|
||||
start: [InternalPlugin, PluginApi]
|
||||
started: [InternalPlugin]
|
||||
stop: [InternalPlugin]
|
||||
stopped: [InternalPlugin]
|
||||
error: [InternalPlugin, unknown]
|
||||
}>()
|
||||
|
||||
export const _plugins = new Map<PluginManifest['id'], InternalPlugin>()
|
||||
export const _metas = new Map<
|
||||
|
|
@ -82,6 +88,8 @@ function handlePluginError(e: unknown, plugin: InternalPlugin) {
|
|||
const log = (api as InitPluginApi).logger ?? console
|
||||
log.error('Plugin encountered an error', e)
|
||||
|
||||
_emitter.emit('error', plugin, e)
|
||||
|
||||
if (!(iflags & InternalPluginFlags.Essential)) return plugin.disable()
|
||||
}
|
||||
|
||||
|
|
@ -100,8 +108,7 @@ function preparePluginPreInit(plugin: InternalPlugin) {
|
|||
unscoped: _uapi,
|
||||
})
|
||||
|
||||
for (const ext of _preInitExts) ext(api, plugin)
|
||||
|
||||
_emitter.emit('preInit', plugin, api)
|
||||
meta[3] = PluginApiLevel.PreInit
|
||||
}
|
||||
|
||||
|
|
@ -116,8 +123,7 @@ function preparePluginInit(plugin: InternalPlugin) {
|
|||
}),
|
||||
)
|
||||
|
||||
for (const ext of _initExts) ext(api, plugin)
|
||||
|
||||
_emitter.emit('init', plugin, api)
|
||||
meta[3] = PluginApiLevel.Init
|
||||
}
|
||||
|
||||
|
|
@ -127,12 +133,14 @@ function preparePluginStart(plugin: InternalPlugin) {
|
|||
const api = meta[0] as PluginApi
|
||||
// api.settings = ...
|
||||
|
||||
for (const ext of _startExts) ext(api, plugin)
|
||||
|
||||
_emitter.emit('start', plugin, api)
|
||||
meta[3] = PluginApiLevel.Start
|
||||
}
|
||||
|
||||
async function disablePlugin(plugin: InternalPlugin) {
|
||||
if (!(plugin.flags & Flag.Enabled))
|
||||
throw new Error(`Plugin "${plugin.manifest.id}" is not enabled`)
|
||||
|
||||
const iflags = _metas.get(plugin.manifest.id)![2] ?? 0
|
||||
if (iflags & InternalPluginFlags.Essential)
|
||||
throw new Error(
|
||||
|
|
@ -145,12 +153,19 @@ async function disablePlugin(plugin: InternalPlugin) {
|
|||
|
||||
// TODO(plugins): write to storage
|
||||
plugin.flags &= ~Flag.Enabled
|
||||
|
||||
_emitter.emit('disabled', plugin)
|
||||
}
|
||||
|
||||
export function enablePlugin(plugin: InternalPlugin, late: boolean) {
|
||||
if (plugin.flags & Flag.Enabled)
|
||||
throw new Error(`Plugin "${plugin.manifest.id}" is already enabled`)
|
||||
|
||||
// TODO(plugins): write to storage
|
||||
plugin.flags |= Flag.Enabled
|
||||
if (late) plugin.flags |= Flag.EnabledLate
|
||||
|
||||
_emitter.emit('enabled', plugin)
|
||||
}
|
||||
|
||||
export async function preInitPlugin(plugin: InternalPlugin) {
|
||||
|
|
@ -158,23 +173,24 @@ export async function preInitPlugin(plugin: InternalPlugin) {
|
|||
manifest: { id },
|
||||
lifecycles,
|
||||
} = plugin
|
||||
if (!lifecycles.preInit) return
|
||||
|
||||
const meta = _metas.get(id)!
|
||||
const [, promises] = meta
|
||||
|
||||
preparePluginPreInit(plugin)
|
||||
|
||||
if (!(plugin.flags & Flag.Enabled))
|
||||
throw new Error(`Plugin "${id}" is not enabled`)
|
||||
if (plugin.status & (Status.PreIniting | Status.PreInited))
|
||||
throw new Error(
|
||||
`Plugin preInit lifecycle for "${id}" is already running`,
|
||||
)
|
||||
|
||||
plugin.status |= Status.PreIniting
|
||||
|
||||
try {
|
||||
if (!lifecycles.preInit) return
|
||||
|
||||
const meta = _metas.get(id)!
|
||||
const [, promises] = meta
|
||||
|
||||
if (!(plugin.flags & Flag.Enabled))
|
||||
throw new Error(`Plugin "${id}" is not enabled`)
|
||||
if (plugin.status & (Status.PreIniting | Status.PreInited))
|
||||
throw new Error(
|
||||
`Plugin preInit lifecycle for "${id}" is already running`,
|
||||
)
|
||||
|
||||
preparePluginPreInit(plugin)
|
||||
|
||||
plugin.status |= Status.PreIniting
|
||||
|
||||
const prom = lifecycles.preInit(meta[0] as PreInitPluginApi)
|
||||
promises.push(prom)
|
||||
await prom
|
||||
|
|
@ -184,6 +200,8 @@ export async function preInitPlugin(plugin: InternalPlugin) {
|
|||
plugin.status &= ~Status.PreIniting
|
||||
} catch (e) {
|
||||
await handlePluginError(e, plugin)
|
||||
} finally {
|
||||
_emitter.emit('preInited', plugin)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -192,20 +210,23 @@ export async function initPlugin(plugin: InternalPlugin) {
|
|||
manifest: { id },
|
||||
lifecycles,
|
||||
} = plugin
|
||||
if (!lifecycles.init) return
|
||||
|
||||
const meta = _metas.get(id)!
|
||||
const [, promises, , apiLevel] = meta
|
||||
|
||||
if (!(plugin.flags & Flag.Enabled))
|
||||
throw new Error(`Plugin "${id}" is not enabled`)
|
||||
if (plugin.status & (Status.Initing | Status.Inited))
|
||||
throw new Error(`Plugin init lifecycle for "${id}" is already running`)
|
||||
|
||||
if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(plugin)
|
||||
if (apiLevel < PluginApiLevel.Init) preparePluginInit(plugin)
|
||||
|
||||
try {
|
||||
if (!lifecycles.init) return
|
||||
|
||||
const meta = _metas.get(id)!
|
||||
const [, promises, , apiLevel] = meta
|
||||
|
||||
if (!(plugin.flags & Flag.Enabled))
|
||||
throw new Error(`Plugin "${id}" is not enabled`)
|
||||
if (plugin.status & (Status.Initing | Status.Inited))
|
||||
throw new Error(
|
||||
`Plugin init lifecycle for "${id}" is already running`,
|
||||
)
|
||||
|
||||
if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(plugin)
|
||||
if (apiLevel < PluginApiLevel.Init) preparePluginInit(plugin)
|
||||
|
||||
const prom = lifecycles.init(meta[0] as InitPluginApi)
|
||||
promises.push(prom)
|
||||
await prom
|
||||
|
|
@ -215,6 +236,8 @@ export async function initPlugin(plugin: InternalPlugin) {
|
|||
plugin.status &= ~Status.Initing
|
||||
} catch (e) {
|
||||
await handlePluginError(e, plugin)
|
||||
} finally {
|
||||
_emitter.emit('inited', plugin)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -223,23 +246,26 @@ export async function startPlugin(plugin: InternalPlugin) {
|
|||
manifest: { id },
|
||||
lifecycles,
|
||||
} = plugin
|
||||
if (!lifecycles.start) return
|
||||
|
||||
const meta = _metas.get(id)!
|
||||
const [, promises, , apiLevel] = meta
|
||||
|
||||
if (!(plugin.flags & Flag.Enabled))
|
||||
throw new Error(`Plugin "${id}" is not enabled`)
|
||||
if (plugin.status & (Status.Starting | Status.Started))
|
||||
throw new Error(`Plugin start lifecycle for "${id}" is already running`)
|
||||
|
||||
plugin.status |= Status.Starting
|
||||
|
||||
if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(plugin)
|
||||
if (apiLevel < PluginApiLevel.Init) preparePluginInit(plugin)
|
||||
if (apiLevel < PluginApiLevel.Start) preparePluginStart(plugin)
|
||||
|
||||
try {
|
||||
if (!lifecycles.start) return
|
||||
|
||||
const meta = _metas.get(id)!
|
||||
const [, promises, , apiLevel] = meta
|
||||
|
||||
if (!(plugin.flags & Flag.Enabled))
|
||||
throw new Error(`Plugin "${id}" is not enabled`)
|
||||
if (plugin.status & (Status.Starting | Status.Started))
|
||||
throw new Error(
|
||||
`Plugin start lifecycle for "${id}" is already running`,
|
||||
)
|
||||
|
||||
plugin.status |= Status.Starting
|
||||
|
||||
if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(plugin)
|
||||
if (apiLevel < PluginApiLevel.Init) preparePluginInit(plugin)
|
||||
if (apiLevel < PluginApiLevel.Start) preparePluginStart(plugin)
|
||||
|
||||
const prom = lifecycles.start(meta[0] as PluginApi)
|
||||
promises.push(prom)
|
||||
await prom
|
||||
|
|
@ -249,6 +275,8 @@ export async function startPlugin(plugin: InternalPlugin) {
|
|||
plugin.status &= ~Status.Starting
|
||||
} catch (e) {
|
||||
await handlePluginError(e, plugin)
|
||||
} finally {
|
||||
_emitter.emit('started', plugin)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -262,41 +290,47 @@ export async function stopPlugin(plugin: InternalPlugin) {
|
|||
const meta = _metas.get(id)!
|
||||
const [, promises, iflags, apiLevel] = meta
|
||||
|
||||
if (iflags & InternalPluginFlags.Essential)
|
||||
throw new Error(`Plugin "${id}" is essential and cannot be stopped`)
|
||||
|
||||
if (!(plugin.flags & Flag.Enabled))
|
||||
throw new Error(`Plugin "${id}" is not enabled`)
|
||||
if (plugin.status & Status.Stopping)
|
||||
throw new Error(`Plugin "${id}" is stopping`)
|
||||
|
||||
// If the plugin is running its lifecycles, we need to wait for it to finish, then we'll stop it
|
||||
// We want to wait at max 5 seconds for the lifecycles to finish
|
||||
if (plugin.status & (Status.PreIniting | Status.Initing | Status.Starting))
|
||||
await Promise.race([
|
||||
Promise.all(promises),
|
||||
sleepReject(
|
||||
MaxWaitTime,
|
||||
'Plugin lifecycles timed out, force stopping',
|
||||
),
|
||||
])
|
||||
.then(finished => {
|
||||
// If the lifecycles don't finish in 5 seconds, a reload is probably required to unapply the changes
|
||||
if (!finished) plugin.flags |= Flag.ReloadRequired
|
||||
})
|
||||
.catch(e => handlePluginError(e, plugin))
|
||||
else if (
|
||||
!(plugin.status & (Status.PreInited | Status.Inited | Status.Started))
|
||||
)
|
||||
throw new Error(`Plugin "${id}" is not running`)
|
||||
|
||||
if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(plugin)
|
||||
if (apiLevel < PluginApiLevel.Init) preparePluginInit(plugin)
|
||||
if (apiLevel < PluginApiLevel.Start) preparePluginStart(plugin)
|
||||
|
||||
plugin.status |= Status.Stopping
|
||||
|
||||
try {
|
||||
if (iflags & InternalPluginFlags.Essential)
|
||||
throw new Error(`Plugin "${id}" is essential and cannot be stopped`)
|
||||
|
||||
if (!(plugin.flags & Flag.Enabled))
|
||||
throw new Error(`Plugin "${id}" is not enabled`)
|
||||
if (plugin.status & Status.Stopping)
|
||||
throw new Error(`Plugin "${id}" is stopping`)
|
||||
|
||||
// If the plugin is running its lifecycles, we need to wait for it to finish, then we'll stop it
|
||||
// We want to wait at max 5 seconds for the lifecycles to finish
|
||||
if (
|
||||
plugin.status &
|
||||
(Status.PreIniting | Status.Initing | Status.Starting)
|
||||
)
|
||||
await Promise.race([
|
||||
Promise.all(promises),
|
||||
sleepReject(
|
||||
MaxWaitTime,
|
||||
'Plugin lifecycles timed out, force stopping',
|
||||
),
|
||||
])
|
||||
.then(finished => {
|
||||
// If the lifecycles don't finish in 5 seconds, a reload is probably required to unapply the changes
|
||||
if (!finished) plugin.flags |= Flag.ReloadRequired
|
||||
})
|
||||
.catch(e => handlePluginError(e, plugin))
|
||||
else if (
|
||||
!(
|
||||
plugin.status &
|
||||
(Status.PreInited | Status.Inited | Status.Started)
|
||||
)
|
||||
)
|
||||
throw new Error(`Plugin "${id}" is not running`)
|
||||
|
||||
if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(plugin)
|
||||
if (apiLevel < PluginApiLevel.Init) preparePluginInit(plugin)
|
||||
if (apiLevel < PluginApiLevel.Start) preparePluginStart(plugin)
|
||||
|
||||
plugin.status |= Status.Stopping
|
||||
|
||||
if (lifecycles.stop)
|
||||
await Promise.race([
|
||||
lifecycles.stop(meta[0] as PluginApi),
|
||||
|
|
@ -309,13 +343,28 @@ export async function stopPlugin(plugin: InternalPlugin) {
|
|||
await handlePluginError(e, plugin)
|
||||
} finally {
|
||||
// Run cleanups
|
||||
const results = await allSettled(plugin._c.map(cleanup => cleanup()))
|
||||
for (const result of results)
|
||||
if (result.status === 'rejected') {
|
||||
await handlePluginError(result.reason, plugin)
|
||||
// Some cleanup was unsuccessful, so we need to reload the app
|
||||
plugin.flags |= Flag.ReloadRequired
|
||||
}
|
||||
|
||||
function handleStopError(e: unknown) {
|
||||
// Some cleanup was unsuccessful, so we need to reload the app
|
||||
plugin.flags |= Flag.ReloadRequired
|
||||
return handlePluginError(e, plugin)
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await allSettled(
|
||||
plugin._c.map(cleanup => cleanup()),
|
||||
)
|
||||
|
||||
await Promise.all(
|
||||
results.map(
|
||||
result =>
|
||||
result.status === 'rejected' &&
|
||||
handleStopError(result.reason),
|
||||
),
|
||||
)
|
||||
} catch (e) {
|
||||
await handleStopError(e)
|
||||
}
|
||||
|
||||
// Reset APIs
|
||||
meta[0] = undefined
|
||||
|
|
@ -327,6 +376,8 @@ export async function stopPlugin(plugin: InternalPlugin) {
|
|||
|
||||
// Reset status
|
||||
plugin.status = 0
|
||||
|
||||
_emitter.emit('stopped', plugin)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import FormSwitch from '@revenge-mod/components/FormSwitch'
|
|||
import { Tokens } from '@revenge-mod/discord/common'
|
||||
import { Design } from '@revenge-mod/discord/design'
|
||||
import {
|
||||
_emitter,
|
||||
enablePlugin,
|
||||
InternalPluginFlags,
|
||||
initPlugin,
|
||||
|
|
@ -12,6 +13,7 @@ import {
|
|||
} from '@revenge-mod/plugins/_'
|
||||
import { PluginFlags } from '@revenge-mod/plugins/constants'
|
||||
import { useReRender } from '@revenge-mod/utils/react'
|
||||
import { useEffect } from 'react'
|
||||
import { Image, Pressable } from 'react-native'
|
||||
import { useClickOutside } from 'react-native-click-outside'
|
||||
import { navigatePluginSettings } from '../utils'
|
||||
|
|
@ -40,11 +42,25 @@ export function InstalledPluginCard({
|
|||
rightGap?: boolean
|
||||
}) {
|
||||
const {
|
||||
manifest: { name, description, author, icon },
|
||||
manifest: { id, name, description, author, icon },
|
||||
flags,
|
||||
} = plugin
|
||||
|
||||
// Re-render on plugin enable/disable
|
||||
const reRender = useReRender()
|
||||
useEffect(() => {
|
||||
const listener = (p: InternalPlugin) =>
|
||||
id === p.manifest.id && reRender()
|
||||
|
||||
_emitter.on('disabled', listener)
|
||||
_emitter.on('enabled', listener)
|
||||
|
||||
return () => {
|
||||
_emitter.off('disabled', listener)
|
||||
_emitter.off('enabled', listener)
|
||||
}
|
||||
})
|
||||
|
||||
const essential = Boolean(iflags & InternalPluginFlags.Essential)
|
||||
const enabled = Boolean(flags & PluginFlags.Enabled)
|
||||
const styles_ = usePluginCardStyles()
|
||||
|
|
@ -122,13 +138,6 @@ export function InstalledPluginCard({
|
|||
await initPlugin(plugin)
|
||||
await startPlugin(plugin)
|
||||
} else await plugin.disable()
|
||||
|
||||
reRender()
|
||||
|
||||
// TODO(plugins/settings): handle sorting after plugin enabled/disabled
|
||||
// TODO(plugins/settings): show ReloadRequired modal
|
||||
// make an event based system for this, so we can register a listener for when plugins are disabled
|
||||
// and update the UI accordingly
|
||||
}}
|
||||
value={enabled}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
|
||||
import { _plugins } from '@revenge-mod/plugins/_'
|
||||
import { AlertActionCreators } from '@revenge-mod/discord/actions'
|
||||
import { Design } from '@revenge-mod/discord/design'
|
||||
import { BundleUpdaterManager } from '@revenge-mod/discord/native'
|
||||
import { _emitter, _plugins } from '@revenge-mod/plugins/_'
|
||||
import { PluginFlags } from '@revenge-mod/plugins/constants'
|
||||
import { useReRender } from '@revenge-mod/utils/react'
|
||||
import { useEffect } from 'react'
|
||||
import { RouteNames, Setting } from '../constants'
|
||||
import type { SettingsItem } from '@revenge-mod/discord/modules/settings'
|
||||
import type { InternalPlugin } from '@revenge-mod/plugins/_'
|
||||
|
||||
const RevengePluginsSetting: SettingsItem = {
|
||||
parent: null,
|
||||
|
|
@ -10,10 +16,8 @@ const RevengePluginsSetting: SettingsItem = {
|
|||
IconComponent: () => <TableRowAssetIcon name="PuzzlePieceIcon" />,
|
||||
title: () => 'Plugins',
|
||||
useTrailing: () => {
|
||||
let count = 0
|
||||
for (const plugin of _plugins.values())
|
||||
if (plugin.flags & PluginFlags.Enabled) count++
|
||||
return `${count} enabled`
|
||||
useUpdateOnPluginStatesChange()
|
||||
return `${enabledCount} enabled`
|
||||
},
|
||||
screen: {
|
||||
route: RouteNames[Setting.RevengePlugins],
|
||||
|
|
@ -22,4 +26,61 @@ const RevengePluginsSetting: SettingsItem = {
|
|||
},
|
||||
}
|
||||
|
||||
let enabledCount = 0
|
||||
|
||||
for (const plugin of _plugins.values())
|
||||
if (plugin.flags & PluginFlags.Enabled) enabledCount++
|
||||
|
||||
_emitter.on('disabled', () => enabledCount--)
|
||||
_emitter.on('enabled', () => enabledCount++)
|
||||
|
||||
export function useUpdateOnPluginStatesChange() {
|
||||
const reRender = useReRender()
|
||||
|
||||
useEffect(() => {
|
||||
_emitter.on('disabled', reRender)
|
||||
_emitter.on('enabled', reRender)
|
||||
|
||||
return () => {
|
||||
_emitter.off('disabled', reRender)
|
||||
_emitter.off('enabled', reRender)
|
||||
}
|
||||
}, [reRender])
|
||||
}
|
||||
|
||||
const { AlertActionButton, AlertModal, Text } = Design
|
||||
|
||||
_emitter.on('started', showReloadRequiredAlert)
|
||||
_emitter.on('stopped', showReloadRequiredAlert)
|
||||
|
||||
function showReloadRequiredAlert(plugin: InternalPlugin) {
|
||||
if (plugin.flags & PluginFlags.ReloadRequired) {
|
||||
AlertActionCreators.openAlert(
|
||||
'plugin-reload-required',
|
||||
<AlertModal
|
||||
title="Reload required"
|
||||
content={
|
||||
<Text variant="text-md/medium" color="header-secondary">
|
||||
Plugin{' '}
|
||||
<Text variant="text-md/bold" color="header-secondary">
|
||||
{plugin.manifest.name}
|
||||
</Text>{' '}
|
||||
requires a reload to apply changes.
|
||||
</Text>
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
<AlertActionButton
|
||||
variant="destructive"
|
||||
text="Reload"
|
||||
onPress={() => BundleUpdaterManager.reload()}
|
||||
/>
|
||||
<AlertActionButton variant="secondary" text="Not now" />
|
||||
</>
|
||||
}
|
||||
/>,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default RevengePluginsSetting
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
EssentialPluginTooltipProvider,
|
||||
resetTooltips,
|
||||
} from '../components/TooltipProvider'
|
||||
import { useUpdateOnPluginStatesChange } from '../definitions/RevengePluginsSetting'
|
||||
import type { RouteProp } from '@react-navigation/core'
|
||||
import type { ReactNavigationParamList } from '@revenge-mod/externals/react-navigation'
|
||||
import type { InternalPlugin } from '@revenge-mod/plugins/_'
|
||||
|
|
@ -81,6 +82,8 @@ const Sorts = {
|
|||
} satisfies FilterAndSortActionSheetProps['sorts']
|
||||
|
||||
function Screen() {
|
||||
useUpdateOnPluginStatesChange()
|
||||
|
||||
const navigation = ReactNavigationNative.useNavigation()
|
||||
const route =
|
||||
ReactNavigationNative.useRoute<
|
||||
|
|
@ -110,32 +113,28 @@ function Screen() {
|
|||
[],
|
||||
)
|
||||
|
||||
const plugins = useMemo(
|
||||
() =>
|
||||
allPlugins
|
||||
.filter(([plugin, iflags]) => {
|
||||
if (filter.length === 0) return true
|
||||
if (matchAll)
|
||||
return filter.every(f => Filters[f][1](plugin, iflags))
|
||||
const plugins = allPlugins
|
||||
.filter(([plugin, iflags]) => {
|
||||
if (filter.length === 0) return true
|
||||
if (matchAll)
|
||||
return filter.every(f => Filters[f][1](plugin, iflags))
|
||||
|
||||
return filter.some(f => Filters[f][1](plugin, iflags))
|
||||
})
|
||||
.filter(([plugin]) => {
|
||||
const { name, description, author } = plugin.manifest
|
||||
const query = search.toLowerCase()
|
||||
return (
|
||||
name.toLowerCase().includes(query) ||
|
||||
description.toLowerCase().includes(query) ||
|
||||
author.toLowerCase().includes(query)
|
||||
)
|
||||
})
|
||||
.sort(([a], [b]) => {
|
||||
const result = Sorts[sort as keyof typeof Sorts][1](a, b)
|
||||
if (reverse) return -result
|
||||
return result
|
||||
}),
|
||||
[search, sort, reverse, filter, matchAll, allPlugins],
|
||||
)
|
||||
return filter.some(f => Filters[f][1](plugin, iflags))
|
||||
})
|
||||
.filter(([plugin]) => {
|
||||
const { name, description, author } = plugin.manifest
|
||||
const query = search.toLowerCase()
|
||||
return (
|
||||
name.toLowerCase().includes(query) ||
|
||||
description.toLowerCase().includes(query) ||
|
||||
author.toLowerCase().includes(query)
|
||||
)
|
||||
})
|
||||
.sort(([a], [b]) => {
|
||||
const result = Sorts[sort as keyof typeof Sorts][1](a, b)
|
||||
if (reverse) return -result
|
||||
return result
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
{
|
||||
"extends": "@revenge-mod/types/tsconfig",
|
||||
"compilerOptions": {
|
||||
"types": ["@revenge-mod/types", "./types", "./types.modules", "node/util"],
|
||||
"types": [
|
||||
"@revenge-mod/types",
|
||||
"./types",
|
||||
"./types.modules",
|
||||
"node/util",
|
||||
"node/events"
|
||||
],
|
||||
"allowJs": true,
|
||||
"paths": {
|
||||
"~/*": ["./src/*"],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue