feat: see commit description

- Shim React and React Native
- Move src/components to lib/components
- Exposed lib/components to `unscoped.components`
- scipts/build: Properly set footer sourceURL
- plugins/developer-settings: Debounce searching in Asset Browser
- plugins/settings: Debounce searching in Plugins
This commit is contained in:
PalmDevs 2025-06-08 19:01:33 +07:00
parent f453a82dab
commit 5b3a8bc5ab
No known key found for this signature in database
GPG key ID: 6FAE8F8677ACA8BA
42 changed files with 174 additions and 71 deletions

View file

@ -29,6 +29,9 @@
"lib/assets": { "lib/assets": {
"name": "@revenge-mod/assets", "name": "@revenge-mod/assets",
}, },
"lib/components": {
"name": "@revenge-mod/components",
},
"lib/discord": { "lib/discord": {
"name": "@revenge-mod/discord", "name": "@revenge-mod/discord",
}, },
@ -437,6 +440,8 @@
"@revenge-mod/assets": ["@revenge-mod/assets@workspace:lib/assets"], "@revenge-mod/assets": ["@revenge-mod/assets@workspace:lib/assets"],
"@revenge-mod/components": ["@revenge-mod/components@workspace:lib/components"],
"@revenge-mod/discord": ["@revenge-mod/discord@workspace:lib/discord"], "@revenge-mod/discord": ["@revenge-mod/discord@workspace:lib/discord"],
"@revenge-mod/externals": ["@revenge-mod/externals@workspace:lib/externals"], "@revenge-mod/externals": ["@revenge-mod/externals@workspace:lib/externals"],

View file

@ -1,4 +1,4 @@
import { ReactNative } from '@revenge-mod/react' import { Platform } from 'react-native'
import { _customs, _overrides } from './_internal' import { _customs, _overrides } from './_internal'
import { cache } from './caches' import { cache } from './caches'
import { AssetsRegistry } from './preinit' import { AssetsRegistry } from './preinit'
@ -16,8 +16,7 @@ export {
} from './preinit' } from './preinit'
// iOS cannot display SVGs // iOS cannot display SVGs
let _preferredType: Asset['type'] = let _preferredType: Asset['type'] = Platform.OS === 'ios' ? 'png' : 'svg'
ReactNative.Platform.OS === 'ios' ? 'png' : 'svg'
/** /**
* Set the preferred asset type. This is used to determine which asset to use when multiple types are available. * Set the preferred asset type. This is used to determine which asset to use when multiple types are available.

View file

@ -0,0 +1,25 @@
{
"name": "@revenge-mod/components",
"private": true,
"type": "module",
"exports": {
"./FormSwitch": {
"default": "./src/FormSwitch.tsx"
},
"./Page": {
"default": "./src/Page.tsx"
},
"./SearchInput": {
"default": "./src/SearchInput.tsx"
},
"./TableRowAssetIcon": {
"default": "./src/TableRowAssetIcon.tsx"
},
"./init": {
"default": "./src/init.ts"
},
"./types": {
"types": "./src/types.ts"
}
}
}

View file

@ -1,5 +1,5 @@
import { FormSwitch as Switch } from '@revenge-mod/discord/design' import { FormSwitch as Switch } from '@revenge-mod/discord/design'
import { ReactNative } from '@revenge-mod/react' import { StyleSheet, View } from 'react-native'
import type { DiscordModules } from '@revenge-mod/discord/types' import type { DiscordModules } from '@revenge-mod/discord/types'
/** /**
@ -9,13 +9,13 @@ export default function FormSwitch(
props: DiscordModules.Components.FormSwitchProps, props: DiscordModules.Components.FormSwitchProps,
) { ) {
return ( return (
<ReactNative.View style={props.disabled && styles.disabled}> <View style={props.disabled && styles.disabled}>
<Switch {...props} /> <Switch {...props} />
</ReactNative.View> </View>
) )
} }
const styles = ReactNative.StyleSheet.create({ const styles = StyleSheet.create({
disabled: { disabled: {
opacity: 0.5, opacity: 0.5,
}, },

View file

@ -1,5 +1,5 @@
import { Design } from '@revenge-mod/discord/design' import { Design } from '@revenge-mod/discord/design'
import { ReactNative } from '@revenge-mod/react' import { StyleSheet } from 'react-native'
import type { DiscordModules } from '@revenge-mod/discord/types' import type { DiscordModules } from '@revenge-mod/discord/types'
export default function Page(props: DiscordModules.Components.StackProps) { export default function Page(props: DiscordModules.Components.StackProps) {
@ -10,7 +10,7 @@ export default function Page(props: DiscordModules.Components.StackProps) {
) )
} }
const styles = ReactNative.StyleSheet.create({ const styles = StyleSheet.create({
page: { page: {
flex: 1, flex: 1,
paddingHorizontal: 16, paddingHorizontal: 16,

View file

@ -0,0 +1,4 @@
export { default as FormSwitch } from './FormSwitch'
export { default as Page } from './Page'
export { default as SearchInput } from './SearchInput'
export { default as TableRowAssetIcon } from './TableRowAssetIcon'

View file

@ -0,0 +1,11 @@
/** biome-ignore-all lint/complexity/useArrowFunction: Rolldown issue: Bug requires us to use normal functions instead of arrow functions */
import { _uapi } from '@revenge-mod/plugins/_'
import { defineLazyProperty } from '@revenge-mod/utils/objects'
import type { UnscopedInitPluginApi } from '@revenge-mod/plugins/types'
const uapi = _uapi as UnscopedInitPluginApi
defineLazyProperty(uapi, 'components', function () {
return require('./index')
})

View file

@ -0,0 +1,12 @@
export interface PluginApiComponents {
FormSwitch: typeof import('@revenge-mod/components/FormSwitch').default
Page: typeof import('@revenge-mod/components/Page').default
SearchInput: typeof import('@revenge-mod/components/SearchInput').default
TableRowAssetIcon: typeof import('@revenge-mod/components/TableRowAssetIcon').default
}
declare module '@revenge-mod/plugins/types' {
export interface UnscopedInitPluginApi {
components: PluginApiComponents
}
}

View file

@ -1,6 +1,7 @@
// TODO(storage): Implement using NativeModules interop instead // TODO(storage): Implement using NativeModules interop instead
import { FileModule } from '@revenge-mod/discord/native' import { FileModule } from '@revenge-mod/discord/native'
// This file is usually imported way too early, so we can't import our shim
import { React } from '@revenge-mod/react' import { React } from '@revenge-mod/react'
import { mergeDeep } from '@revenge-mod/utils/objects' import { mergeDeep } from '@revenge-mod/utils/objects'
import { useReRender } from '@revenge-mod/utils/react' import { useReRender } from '@revenge-mod/utils/react'

View file

@ -1,3 +1,4 @@
// @revenge-mod/storage depends on this file, and it is imported way too early, so we can't import our shim
import { React } from '@revenge-mod/react' import { React } from '@revenge-mod/react'
import { findInTree } from './trees' import { findInTree } from './trees'
import type { ReactNode } from 'react' import type { ReactNode } from 'react'

View file

@ -61,10 +61,21 @@ export default async function build(dev = false, log = true) {
find: 'react/jsx-runtime', find: 'react/jsx-runtime',
replacement: `${ShimsDir}/react~jsx-runtime.ts`, replacement: `${ShimsDir}/react~jsx-runtime.ts`,
}, },
// Do not move React to the top!
// If you do that, react/jsx-runtime would resolve to ${ShimsDir}/react.ts/jsx-runtime instead.
{
find: 'react',
replacement: `${ShimsDir}/react.ts`,
},
{
find: 'react-native',
replacement: `${ShimsDir}/react-native.ts`,
},
], ],
}), }),
importGlobPlugin(), importGlobPlugin(),
swcPlugin(), swcPlugin(),
sourceURLPlugin(),
hermesCPlugin({ hermesCPlugin({
flags: [ flags: [
dev ? '-Og' : '-O', dev ? '-Og' : '-O',
@ -102,7 +113,6 @@ export default async function build(dev = false, log = true) {
minify: {}, minify: {},
file: 'dist/revenge.js', file: 'dist/revenge.js',
format: 'iife', format: 'iife',
footer: '//# sourceURL=Revenge',
}) })
if (log) if (log)
@ -113,6 +123,18 @@ export default async function build(dev = false, log = true) {
) )
} }
function sourceURLPlugin() {
return {
name: 'footer',
renderChunk(code) {
return {
code: `${code}\n//# sourceURL=Revenge`,
map: null,
}
},
} satisfies RolldownPlugin
}
function swcPlugin() { function swcPlugin() {
return { return {
name: 'swc', name: 'swc',

3
shims/react-native.ts Normal file
View file

@ -0,0 +1,3 @@
import { ReactNative } from '@revenge-mod/react'
module.exports = ReactNative

3
shims/react.ts Normal file
View file

@ -0,0 +1,3 @@
import { React } from '@revenge-mod/react'
module.exports = React

View file

@ -19,4 +19,4 @@ const jsxs = ((type, ...args) =>
...args, ...args,
)) as typeof _jsxs )) as typeof _jsxs
export { jsx, jsxs, Fragment } module.exports = { jsx, jsxs, Fragment }

View file

@ -37,6 +37,7 @@ onModuleFirstRequired(IndexModuleId, function onIndexRequired() {
// Initialize init libraries // Initialize init libraries
require('@revenge-mod/externals/init') require('@revenge-mod/externals/init')
require('@revenge-mod/discord/init') require('@revenge-mod/discord/init')
require('@revenge-mod/components/init')
// Run all init plugins // Run all init plugins
require('~/plugins/init') require('~/plugins/init')

View file

@ -1,4 +1,4 @@
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import { MobileSetting, UserSettingsSections } from '../constants' import { MobileSetting, UserSettingsSections } from '../constants'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'

View file

@ -1,9 +1,9 @@
import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import { AlertActionCreators } from '@revenge-mod/discord/actions' import { AlertActionCreators } from '@revenge-mod/discord/actions'
import { Design } from '@revenge-mod/discord/design' import { Design } from '@revenge-mod/discord/design'
import { nodeUtil } from '@revenge-mod/externals/browserify' import { nodeUtil } from '@revenge-mod/externals/browserify'
import { React } from '@revenge-mod/react'
import { getErrorStack } from '@revenge-mod/utils/errors' import { getErrorStack } from '@revenge-mod/utils/errors'
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import { useRef, useState } from 'react'
import { api } from '..' import { api } from '..'
import { MobileSetting } from '../constants' import { MobileSetting } from '../constants'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'
@ -34,11 +34,11 @@ const {
} = Design } = Design
function EvaluateJavaScriptAlert() { function EvaluateJavaScriptAlert() {
const code = React.useRef('') const code = useRef('')
const [awaitResult, setAwaitResult] = React.useState(true) const [awaitResult, setAwaitResult] = useState(true)
const [showHidden, setShowHidden] = React.useState(true) const [showHidden, setShowHidden] = useState(true)
const [inspectDepth, setInspectDepth] = React.useState(3) const [inspectDepth, setInspectDepth] = useState(3)
const [loading, setLoading] = React.useState(false) const [loading, setLoading] = useState(false)
return ( return (
<AlertModal <AlertModal

View file

@ -1,4 +1,4 @@
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import { api } from '..' import { api } from '..'
import { MobileSetting } from '../constants' import { MobileSetting } from '../constants'
import { DevToolsContext } from '../react-devtools' import { DevToolsContext } from '../react-devtools'

View file

@ -1,4 +1,4 @@
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import { MobileSetting } from '../constants' import { MobileSetting } from '../constants'
import { import {
connectToDevTools, connectToDevTools,

View file

@ -1,4 +1,4 @@
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import { MobileSetting } from '../constants' import { MobileSetting } from '../constants'
import { disconnectFromDevTools, useIsDevToolsOpen } from '../react-devtools' import { disconnectFromDevTools, useIsDevToolsOpen } from '../react-devtools'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'

View file

@ -1,4 +1,4 @@
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import { MobileSetting } from '../constants' import { MobileSetting } from '../constants'
import { DevToolsContext } from '../react-devtools' import { DevToolsContext } from '../react-devtools'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'

View file

@ -1,4 +1,4 @@
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import { MobileSetting, UserSettingsSections } from '../constants' import { MobileSetting, UserSettingsSections } from '../constants'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'

View file

@ -1,8 +1,8 @@
import { ToastActionCreators } from '@revenge-mod/discord/actions' import { ToastActionCreators } from '@revenge-mod/discord/actions'
import { React } from '@revenge-mod/react'
import { lookupGeneratedIconComponent } from '@revenge-mod/utils/discord' import { lookupGeneratedIconComponent } from '@revenge-mod/utils/discord'
import { getErrorStack } from '@revenge-mod/utils/errors' import { getErrorStack } from '@revenge-mod/utils/errors'
import { useReRender } from '@revenge-mod/utils/react' import { useReRender } from '@revenge-mod/utils/react'
import { useEffect } from 'react'
import { api } from '.' import { api } from '.'
export const DevToolsContext: { export const DevToolsContext: {
@ -80,14 +80,14 @@ export function disconnectFromDevTools() {
export function useIsDevToolsOpen() { export function useIsDevToolsOpen() {
const rerender = useReRender() const rerender = useReRender()
React.useEffect(() => { useEffect(() => {
const sub: Subscription = e => (e === 1 || e === 2) && rerender() const sub: Subscription = e => (e === 1 || e === 2) && rerender()
subscriptions.add(sub) subscriptions.add(sub)
return () => { return () => {
subscriptions.delete(sub) subscriptions.delete(sub)
} }
}, []) }, [rerender])
return DevToolsContext.open return DevToolsContext.open
} }

View file

@ -1,4 +1,7 @@
import { getAssets } from '@revenge-mod/assets' import { getAssets } from '@revenge-mod/assets'
import Page from '@revenge-mod/components/Page'
import SearchInput from '@revenge-mod/components/SearchInput'
import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import { import {
AlertActionCreators, AlertActionCreators,
ToastActionCreators, ToastActionCreators,
@ -6,15 +9,13 @@ import {
import { Design } from '@revenge-mod/discord/design' import { Design } from '@revenge-mod/discord/design'
import { Clipboard } from '@revenge-mod/externals/react-native-clipboard' import { Clipboard } from '@revenge-mod/externals/react-native-clipboard'
import { FlashList } from '@revenge-mod/externals/shopify' import { FlashList } from '@revenge-mod/externals/shopify'
import { React, ReactNative } from '@revenge-mod/react' import { debounce } from '@revenge-mod/utils/callbacks'
import { lookupGeneratedIconComponent } from '@revenge-mod/utils/discord' import { lookupGeneratedIconComponent } from '@revenge-mod/utils/discord'
import Page from '~/components/Page' import { useCallback, useMemo, useState } from 'react'
import SearchInput from '~/components/SearchInput' import { Image, StyleSheet } from 'react-native'
import TableRowAssetIcon from '~/components/TableRowAssetIcon'
import type { Asset, AssetId } from '@revenge-mod/assets/types' import type { Asset, AssetId } from '@revenge-mod/assets/types'
import type { Metro } from '@revenge-mod/modules/types' import type { Metro } from '@revenge-mod/modules/types'
const { Image, StyleSheet } = ReactNative
const { AlertModal, AlertActionButton, TableRow, TableRowGroup, Text } = Design const { AlertModal, AlertActionButton, TableRow, TableRowGroup, Text } = Design
const Displayable = new Set(['png', 'jpg', 'svg', 'webp']) const Displayable = new Set(['png', 'jpg', 'svg', 'webp'])
@ -27,10 +28,11 @@ const UndisplayableFallback = {
} }
export default function AssetBrowserSettingScreen() { export default function AssetBrowserSettingScreen() {
const [search, setSearch] = React.useState('') const [search, setSearch] = useState('')
const assets = React.useMemo(() => [...getAssets()], [getAssets]) const debouncedSetSearch = useCallback(debounce(setSearch, 100), [])
const assets = useMemo(() => [...getAssets()], [])
const filteredAssets = React.useMemo( const filteredAssets = useMemo(
() => () =>
!search.length !search.length
? assets ? assets
@ -42,7 +44,10 @@ export default function AssetBrowserSettingScreen() {
return ( return (
<Page spacing={16}> <Page spacing={16}>
<SearchInput onChange={(v: string) => setSearch(v)} size="md" /> <SearchInput
onChange={(v: string) => debouncedSetSearch(v)}
size="md"
/>
<FlashList.FlashList <FlashList.FlashList
data={filteredAssets} data={filteredAssets}
contentContainerStyle={styles.listContainer} contentContainerStyle={styles.listContainer}

View file

@ -1,8 +1,8 @@
import { ToastActionCreators } from '@revenge-mod/discord/actions' import { ToastActionCreators } from '@revenge-mod/discord/actions'
import { Design } from '@revenge-mod/discord/design' import { Design } from '@revenge-mod/discord/design'
import { SettingListRenderer } from '@revenge-mod/discord/modules/settings/renderer' import { SettingListRenderer } from '@revenge-mod/discord/modules/settings/renderer'
import { ReactNative } from '@revenge-mod/react'
import { lookupGeneratedIconComponent } from '@revenge-mod/utils/discord' import { lookupGeneratedIconComponent } from '@revenge-mod/utils/discord'
import { View } from 'react-native'
import { api } from '..' import { api } from '..'
import { MobileSetting } from '../constants' import { MobileSetting } from '../constants'
import { DevToolsContext, useIsDevToolsOpen } from '../react-devtools' import { DevToolsContext, useIsDevToolsOpen } from '../react-devtools'
@ -49,7 +49,7 @@ export function ReactDevToolsAddressSetting() {
const settings = api.storage.use(s => s.devtools?.address) const settings = api.storage.use(s => s.devtools?.address)
return ( return (
<ReactNative.View style={{ paddingHorizontal: 16, paddingTop: 16 }}> <View style={{ paddingHorizontal: 16, paddingTop: 16 }}>
<Design.TextInput <Design.TextInput
defaultValue={ defaultValue={
settings?.devtools.address ?? DevToolsContext.address settings?.devtools.address ?? DevToolsContext.address
@ -76,6 +76,6 @@ export function ReactDevToolsAddressSetting() {
onChange={text => (DevToolsContext.address = text)} onChange={text => (DevToolsContext.address = text)}
returnKeyType="done" returnKeyType="done"
/> />
</ReactNative.View> </View>
) )
} }

View file

@ -1,4 +1,4 @@
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import { MobileSetting } from '../constants' import { MobileSetting } from '../constants'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'

View file

@ -1,10 +1,10 @@
import { ReactNative } from '@revenge-mod/react' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import { Platform } from 'react-native'
import { MobileSetting } from '../constants' import { MobileSetting } from '../constants'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'
const { major, minor, patch, prerelease } = const { major, minor, patch, prerelease } =
ReactNative.Platform.constants.reactNativeVersion Platform.constants.reactNativeVersion
const version = `${major}.${minor}.${patch}${prerelease ? `-${prerelease}` : ''}` const version = `${major}.${minor}.${patch}${prerelease ? `-${prerelease}` : ''}`
const ReactNativeVersionSetting: SettingsItem = { const ReactNativeVersionSetting: SettingsItem = {

View file

@ -1,5 +1,5 @@
import { React } from '@revenge-mod/react' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import { version } from 'react'
import { MobileSetting } from '../constants' import { MobileSetting } from '../constants'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'
@ -7,7 +7,7 @@ const ReactVersionSetting: SettingsItem = {
parent: MobileSetting.REVENGE, parent: MobileSetting.REVENGE,
IconComponent: () => <TableRowAssetIcon name="ScienceIcon" />, IconComponent: () => <TableRowAssetIcon name="ScienceIcon" />,
title: () => 'React', title: () => 'React',
useDescription: () => React.version, useDescription: () => version,
type: 'static', type: 'static',
} }

View file

@ -1,5 +1,5 @@
import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import { BundleUpdaterManager } from '@revenge-mod/discord/native' import { BundleUpdaterManager } from '@revenge-mod/discord/native'
import TableRowAssetIcon from '~/components/TableRowAssetIcon'
import { MobileSetting } from '../constants' import { MobileSetting } from '../constants'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'

View file

@ -1,5 +1,5 @@
import { ReactNative } from '@revenge-mod/react' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import { Linking } from 'react-native'
import { DiscordServerURL } from '~/constants' import { DiscordServerURL } from '~/constants'
import { MobileSetting } from '../constants' import { MobileSetting } from '../constants'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'
@ -8,7 +8,7 @@ const RevengeDiscordServerSetting: SettingsItem = {
parent: MobileSetting.REVENGE, parent: MobileSetting.REVENGE,
IconComponent: () => <TableRowAssetIcon name="Discord" />, IconComponent: () => <TableRowAssetIcon name="Discord" />,
title: () => 'Discord Server', title: () => 'Discord Server',
onPress: () => ReactNative.Linking.openURL(DiscordServerURL), onPress: () => Linking.openURL(DiscordServerURL),
type: 'pressable', type: 'pressable',
} }

View file

@ -1,4 +1,4 @@
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import { MobileSetting, UserSettingsSections } from '../constants' import { MobileSetting, UserSettingsSections } from '../constants'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'

View file

@ -1,5 +1,5 @@
import { ReactNative } from '@revenge-mod/react' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import { Linking } from 'react-native'
import { GitHubOrganizationURL } from '~/constants' import { GitHubOrganizationURL } from '~/constants'
import { MobileSetting } from '../constants' import { MobileSetting } from '../constants'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'
@ -10,7 +10,7 @@ const RevengeGitHubOrganizationSetting: SettingsItem = {
<TableRowAssetIcon name="img_account_sync_github_white" /> <TableRowAssetIcon name="img_account_sync_github_white" />
), ),
title: () => 'GitHub', title: () => 'GitHub',
onPress: () => ReactNative.Linking.openURL(GitHubOrganizationURL), onPress: () => Linking.openURL(GitHubOrganizationURL),
type: 'pressable', type: 'pressable',
} }

View file

@ -1,4 +1,4 @@
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'
const RevengeNotImplementedSetting: SettingsItem = { const RevengeNotImplementedSetting: SettingsItem = {

View file

@ -1,6 +1,6 @@
import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import { _plugins } from '@revenge-mod/plugins/_' import { _plugins } from '@revenge-mod/plugins/_'
import { PluginFlags } from '@revenge-mod/plugins/constants' import { PluginFlags } from '@revenge-mod/plugins/constants'
import TableRowAssetIcon from '~/components/TableRowAssetIcon'
import { MobileSetting, UserSettingsSections } from '../constants' import { MobileSetting, UserSettingsSections } from '../constants'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'

View file

@ -1,4 +1,4 @@
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import RevengeIcon from '~assets/RevengeIcon' import RevengeIcon from '~assets/RevengeIcon'
import { MobileSetting, UserSettingsSections } from '../constants' import { MobileSetting, UserSettingsSections } from '../constants'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'

View file

@ -1,4 +1,4 @@
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import { MobileSetting, UserSettingsSections } from '../constants' import { MobileSetting, UserSettingsSections } from '../constants'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'

View file

@ -1,4 +1,4 @@
import TableRowAssetIcon from '~/components/TableRowAssetIcon' import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
import RevengeIcon from '~assets/RevengeIcon' import RevengeIcon from '~assets/RevengeIcon'
import { MobileSetting } from '../constants' import { MobileSetting } from '../constants'
import type { SettingsItem } from '@revenge-mod/discord/modules/settings' import type { SettingsItem } from '@revenge-mod/discord/modules/settings'

View file

@ -1,4 +1,4 @@
import { React } from '@revenge-mod/react' import { useEffect } from 'react'
import type { StackScreenProps } from '@react-navigation/stack' import type { StackScreenProps } from '@react-navigation/stack'
import type { ReactNavigationParamList } from '@revenge-mod/externals/react-navigation' import type { ReactNavigationParamList } from '@revenge-mod/externals/react-navigation'
import type { MobileSetting, UserSettingsSections } from '../constants' import type { MobileSetting, UserSettingsSections } from '../constants'
@ -12,7 +12,8 @@ export default function RevengeCustomPageScreen({
>) { >) {
const { render: PageComponent, options } = route.params const { render: PageComponent, options } = route.params
React.useEffect(() => { // biome-ignore lint/correctness/useExhaustiveDependencies: We only want to set options once
useEffect(() => {
if (options) navigation.setOptions(options) if (options) navigation.setOptions(options)
}, []) }, [])

View file

@ -1,4 +1,7 @@
import { getAssetIdByName } from '@revenge-mod/assets' import { getAssetIdByName } from '@revenge-mod/assets'
import FormSwitch from '@revenge-mod/components/FormSwitch'
import Page from '@revenge-mod/components/Page'
import SearchInput from '@revenge-mod/components/SearchInput'
import { Tokens } from '@revenge-mod/discord/common' import { Tokens } from '@revenge-mod/discord/common'
import { Design } from '@revenge-mod/discord/design' import { Design } from '@revenge-mod/discord/design'
import { FlashList } from '@revenge-mod/externals/shopify' import { FlashList } from '@revenge-mod/externals/shopify'
@ -12,31 +15,30 @@ import {
startPlugin, startPlugin,
} from '@revenge-mod/plugins/_' } from '@revenge-mod/plugins/_'
import { PluginFlags } from '@revenge-mod/plugins/constants' import { PluginFlags } from '@revenge-mod/plugins/constants'
import { React, ReactNative } from '@revenge-mod/react' import { debounce } from '@revenge-mod/utils/callbacks'
import { useReRender } from '@revenge-mod/utils/react' import { useReRender } from '@revenge-mod/utils/react'
import FormSwitch from '~/components/FormSwitch' import { useCallback, useMemo, useState } from 'react'
import Page from '~/components/Page' import { Image, useWindowDimensions } from 'react-native'
import SearchInput from '~/components/SearchInput'
import type { InternalPlugin } from '@revenge-mod/plugins/_' import type { InternalPlugin } from '@revenge-mod/plugins/_'
const { Image, useWindowDimensions } = ReactNative
const { Card, Text, Stack } = Design const { Card, Text, Stack } = Design
export default function RevengePluginsSettingScreen() { export default function RevengePluginsSettingScreen() {
const [search, setSearch] = React.useState('') const [search, setSearch] = useState('')
const debouncedSetSearch = useCallback(debounce(setSearch, 100), [])
const { width } = useWindowDimensions() const { width } = useWindowDimensions()
const numColumns = Math.floor((width - 16) / 448) const numColumns = Math.floor((width - 16) / 448)
const plugins = React.useMemo( const plugins = useMemo(
() => () =>
[..._plugins.values()].map( [..._plugins.values()].map(
plugin => [plugin, _metas.get(plugin.manifest.id)![2]] as const, plugin => [plugin, _metas.get(plugin.manifest.id)![2]] as const,
), ),
[_plugins], [],
) )
const filteredPlugins = React.useMemo( const filteredPlugins = useMemo(
() => () =>
plugins.filter(([plugin]) => { plugins.filter(([plugin]) => {
const { name, description, author } = plugin.manifest const { name, description, author } = plugin.manifest
@ -52,7 +54,10 @@ export default function RevengePluginsSettingScreen() {
return ( return (
<Page spacing={16}> <Page spacing={16}>
<SearchInput onChange={(v: string) => setSearch(v)} size="md" /> <SearchInput
onChange={(v: string) => debouncedSetSearch(v)}
size="md"
/>
<FlashList.MasonryFlashList <FlashList.MasonryFlashList
data={filteredPlugins} data={filteredPlugins}
estimatedItemSize={108} estimatedItemSize={108}

5
types.d.ts vendored
View file

@ -8,6 +8,11 @@ declare global {
const __BUILD_FLAG_LOG_PROMISE_REJECTIONS__: boolean const __BUILD_FLAG_LOG_PROMISE_REJECTIONS__: boolean
// CommonJS modules, we don't want to depend on @types/node
const module: {
exports: any
}
export interface ImportMeta { export interface ImportMeta {
glob<T = any>( glob<T = any>(
pattern: ImportMetaGlobPattern, pattern: ImportMetaGlobPattern,