From ce726acccae1dc9af35d0789a154a4d6b217786d Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 22 May 2025 02:48:54 +0700 Subject: [PATCH] feat(plugins/developer-settings/react-devtools): add settings --- .../init/developer-settings/constants.ts | 1 + .../ReactDevToolsAutoConnectSetting.tsx | 22 ++++ .../ReactDevToolsConnectSetting.tsx | 2 +- .../ReactDevToolsDisconnectSetting.tsx | 2 +- .../ReactDevToolsVersionSetting.tsx | 2 +- src/plugins/init/developer-settings/index.ts | 27 +++- .../{devtools.ts => react-devtools.ts} | 2 +- .../init/developer-settings/register.ts | 2 + .../screens/AssetBrowserSettingScreen.tsx | 123 +++++++++++------- .../screens/RevengeDeveloperSettingScreen.tsx | 36 ++++- 10 files changed, 160 insertions(+), 59 deletions(-) create mode 100644 src/plugins/init/developer-settings/definitions/ReactDevToolsAutoConnectSetting.tsx rename src/plugins/init/developer-settings/{devtools.ts => react-devtools.ts} (98%) diff --git a/src/plugins/init/developer-settings/constants.ts b/src/plugins/init/developer-settings/constants.ts index 1a32afe..37e4d32 100644 --- a/src/plugins/init/developer-settings/constants.ts +++ b/src/plugins/init/developer-settings/constants.ts @@ -6,6 +6,7 @@ export const MobileSetting = { // SUBSETTINGS REACT_DEVTOOLS_VERSION: 'REACT_DEVTOOLS_VERSION', + REACT_DEVTOOLS_AUTO_CONNECT: 'REACT_DEVTOOLS_AUTO_CONNECT', REACT_DEVTOOLS_CONNECT: 'REACT_DEVTOOLS_CONNECT', REACT_DEVTOOLS_DISCONNECT: 'REACT_DEVTOOLS_DISCONNECT', EVALUATE_JAVASCRIPT: 'EVALUATE_JAVASCRIPT', diff --git a/src/plugins/init/developer-settings/definitions/ReactDevToolsAutoConnectSetting.tsx b/src/plugins/init/developer-settings/definitions/ReactDevToolsAutoConnectSetting.tsx new file mode 100644 index 0000000..f73caf3 --- /dev/null +++ b/src/plugins/init/developer-settings/definitions/ReactDevToolsAutoConnectSetting.tsx @@ -0,0 +1,22 @@ +import { React } from '@revenge-mod/react' + +import TableRowAssetIcon from '~/components/TableRowAssetIcon' + +import { api } from '..' +import { MobileSetting } from '../constants' +import { DevToolsContext } from '../react-devtools' + +import type { SettingsItem } from '@revenge-mod/discord/modules/settings' + +const ReactDevToolsAutoConnectSetting: SettingsItem = { + parent: MobileSetting.REVENGE_DEVELOPER, + IconComponent: () => , + title: () => 'Auto-connect to React DevTools', + useDescription: () => 'Automatically connect to React DevTools during startup.', + usePredicate: () => DevToolsContext.available, + useValue: () => api.storage.use(s => s.devtools?.autoConnect !== undefined)!.devtools.autoConnect, + onValueChange: v => api.storage.set({ devtools: { autoConnect: v } }), + type: 'toggle', +} + +export default ReactDevToolsAutoConnectSetting diff --git a/src/plugins/init/developer-settings/definitions/ReactDevToolsConnectSetting.tsx b/src/plugins/init/developer-settings/definitions/ReactDevToolsConnectSetting.tsx index 334c515..c26d071 100644 --- a/src/plugins/init/developer-settings/definitions/ReactDevToolsConnectSetting.tsx +++ b/src/plugins/init/developer-settings/definitions/ReactDevToolsConnectSetting.tsx @@ -3,7 +3,7 @@ import { React } from '@revenge-mod/react' import TableRowAssetIcon from '~/components/TableRowAssetIcon' import { MobileSetting } from '../constants' -import { DevToolsContext, connectToDevTools, useIsDevToolsOpen } from '../devtools' +import { DevToolsContext, connectToDevTools, useIsDevToolsOpen } from '../react-devtools' import type { SettingsItem } from '@revenge-mod/discord/modules/settings' diff --git a/src/plugins/init/developer-settings/definitions/ReactDevToolsDisconnectSetting.tsx b/src/plugins/init/developer-settings/definitions/ReactDevToolsDisconnectSetting.tsx index 294ceda..0917101 100644 --- a/src/plugins/init/developer-settings/definitions/ReactDevToolsDisconnectSetting.tsx +++ b/src/plugins/init/developer-settings/definitions/ReactDevToolsDisconnectSetting.tsx @@ -3,7 +3,7 @@ import { React } from '@revenge-mod/react' import TableRowAssetIcon from '~/components/TableRowAssetIcon' import { MobileSetting } from '../constants' -import { disconnectFromDevTools, useIsDevToolsOpen } from '../devtools' +import { disconnectFromDevTools, useIsDevToolsOpen } from '../react-devtools' import type { SettingsItem } from '@revenge-mod/discord/modules/settings' diff --git a/src/plugins/init/developer-settings/definitions/ReactDevToolsVersionSetting.tsx b/src/plugins/init/developer-settings/definitions/ReactDevToolsVersionSetting.tsx index 46b3b9e..8c17270 100644 --- a/src/plugins/init/developer-settings/definitions/ReactDevToolsVersionSetting.tsx +++ b/src/plugins/init/developer-settings/definitions/ReactDevToolsVersionSetting.tsx @@ -3,7 +3,7 @@ import { React } from '@revenge-mod/react' import TableRowAssetIcon from '~/components/TableRowAssetIcon' import { MobileSetting } from '../constants' -import { DevToolsContext } from '../devtools' +import { DevToolsContext } from '../react-devtools' import type { SettingsItem } from '@revenge-mod/discord/modules/settings' diff --git a/src/plugins/init/developer-settings/index.ts b/src/plugins/init/developer-settings/index.ts index 90320f1..8770734 100644 --- a/src/plugins/init/developer-settings/index.ts +++ b/src/plugins/init/developer-settings/index.ts @@ -6,7 +6,21 @@ import { isSettingsModulesLoaded, onSettingsModulesLoaded } from '@revenge-mod/d import type { PluginApi } from '@revenge-mod/plugins/types' -registerPlugin( +interface Storage { + devtools: { + address: string + autoConnect: boolean + } +} + +const defaultStorage: Storage = { + devtools: { + address: 'localhost:8097', + autoConnect: false, + }, +} + +registerPlugin( { id: 'revenge.settings.developer', name: 'Developer Settings', @@ -15,10 +29,19 @@ registerPlugin( icon: 'WrenchIcon', }, { + storage: { + load: true, + default: defaultStorage, + }, start(api_) { api = api_ + if (isSettingsModulesLoaded()) require('./register').register() else onSettingsModulesLoaded(() => require('./register').register()) + + api.storage.get().then(settings => { + if (settings.devtools.autoConnect) import('./react-devtools').then(it => it.connectToDevTools()) + }) }, }, PluginFlags.Enabled, @@ -26,4 +49,4 @@ registerPlugin( ) // Expose to EvaluateJavaScriptSetting -export let api: PluginApi +export let api: PluginApi diff --git a/src/plugins/init/developer-settings/devtools.ts b/src/plugins/init/developer-settings/react-devtools.ts similarity index 98% rename from src/plugins/init/developer-settings/devtools.ts rename to src/plugins/init/developer-settings/react-devtools.ts index c1c27bd..9d293fb 100644 --- a/src/plugins/init/developer-settings/devtools.ts +++ b/src/plugins/init/developer-settings/react-devtools.ts @@ -29,7 +29,7 @@ subscriptions.add((e, err) => { api.logger.error('React DevTools error:', actualError) ToastActionCreators.open({ - key: 'DEVTOOLS_ERROR', + key: 'REACT_DEVTOOLS_ERROR', IconComponent: CircleXIcon, content: actualError, }) diff --git a/src/plugins/init/developer-settings/register.ts b/src/plugins/init/developer-settings/register.ts index e07bc26..0219556 100644 --- a/src/plugins/init/developer-settings/register.ts +++ b/src/plugins/init/developer-settings/register.ts @@ -4,6 +4,7 @@ import { MobileSetting } from './constants' import AssetBrowserSetting from './definitions/AssetBrowserSetting' import EvaluateJavaScriptSetting from './definitions/EvaluateJavaScriptSetting' +import ReactDevToolsAutoConnectSetting from './definitions/ReactDevToolsAutoConnectSetting' import ReactDevToolsConnectSetting from './definitions/ReactDevToolsConnectSetting' import ReactDevToolsDisconnectSetting from './definitions/ReactDevToolsDisconnectSetting' import ReactDevToolsVersionSetting from './definitions/ReactDevToolsVersionSetting' @@ -16,6 +17,7 @@ export function register() { registerSettingsItems({ [MobileSetting.REVENGE_DEVELOPER]: RevengeDeveloperSetting, [MobileSetting.REACT_DEVTOOLS_VERSION]: ReactDevToolsVersionSetting, + [MobileSetting.REACT_DEVTOOLS_AUTO_CONNECT]: ReactDevToolsAutoConnectSetting, [MobileSetting.REACT_DEVTOOLS_CONNECT]: ReactDevToolsConnectSetting, [MobileSetting.REACT_DEVTOOLS_DISCONNECT]: ReactDevToolsDisconnectSetting, [MobileSetting.EVALUATE_JAVASCRIPT]: EvaluateJavaScriptSetting, diff --git a/src/plugins/init/developer-settings/screens/AssetBrowserSettingScreen.tsx b/src/plugins/init/developer-settings/screens/AssetBrowserSettingScreen.tsx index 1afb61e..9bf4dec 100644 --- a/src/plugins/init/developer-settings/screens/AssetBrowserSettingScreen.tsx +++ b/src/plugins/init/developer-settings/screens/AssetBrowserSettingScreen.tsx @@ -8,12 +8,13 @@ import Page from '~/components/Page' import SearchInput from '~/components/SearchInput' import TableRowAssetIcon from '~/components/TableRowAssetIcon' -import { getAllAssets, getAssetId } from '@revenge-mod/assets' +import { getAllAssets, getAssetId, getAssetModuleId } from '@revenge-mod/assets' import type { Asset, AssetId } from '@revenge-mod/assets/types' +import type { Metro } from '@revenge-mod/modules/types' -const { Image, StyleSheet, View } = ReactNative -const { AlertModal, AlertActionButton, TableRow, Text, Stack } = Design +const { Image, StyleSheet } = ReactNative +const { AlertModal, AlertActionButton, TableRow, TableRowGroup, Text } = Design const Displayable = new Set(['png', 'jpg', 'svg', 'webp']) const UndisplayableFallback = { @@ -27,7 +28,7 @@ const UndisplayableFallback = { export default function AssetBrowserSettingScreen() { const [search, setSearch] = React.useState('') const assets = React.useMemo( - () => [...getAllAssets()].map(asset => [getAssetId(asset)!, asset] as const), + () => [...getAllAssets()].map(asset => [getAssetId(asset)!, getAssetModuleId(asset)!, asset] as const), [getAllAssets], ) @@ -35,39 +36,58 @@ export default function AssetBrowserSettingScreen() { () => !search.length ? assets - : assets.filter(([_, asset]) => asset.name.toLowerCase().includes(search.toLowerCase())), + : assets.filter(([, , asset]) => asset.name.toLowerCase().includes(search.toLowerCase())), [assets, search], ) return ( setSearch(v)} /> - - } - estimatedItemSize={80} - /> - + id.toString()} + renderItem={({ item: [id, moduleId, asset], index }) => ( + + )} + estimatedItemSize={80} + /> ) } function AssetDisplay({ id, + moduleId, asset, + start, + end, }: { id: AssetId + moduleId: Metro.ModuleID asset: Asset + start?: boolean + end?: boolean }) { const isDisplayable = Displayable.has(asset.type) - const metadata = [`ID: ${id}`, `Type: ${asset.type}`] + const metadata = [ + ['ID', id.toString()], + ['Type', asset.type], + ['Module ID', moduleId.toString()], + ] as const return ( `${name}: ${value}`).join(' • ')} icon={ isDisplayable ? ( @@ -82,40 +102,50 @@ function AssetDisplay({ /> ) } - onPress={() => - AlertActionCreators.openAlert( - 'asset-display', - - ) : ( - - ) - } - actions={ - - Clipboard.setString(asset.name)} - /> - Clipboard.setString(id.toString())} - /> - - } - />, - ) - } + onPress={() => openAssetDisplayAlert(asset, id, metadata)} /> ) } +function openAssetDisplayAlert( + asset: Asset, + id: AssetId, + metadata: Readonly[]> | [string, string][], +) { + const isDisplayable = Displayable.has(asset.type) + + AlertActionCreators.openAlert( + 'asset-display', + + {isDisplayable ? ( + + ) : ( + + )} + + {metadata.map(([name, value]) => ( + Clipboard.setString(value)} + /> + ))} + + + Note: Asset IDs and module IDs are not consistent between app launches and app versions + respectively and should only be used when absolutely needed. + + + } + actions={} + />, + ) +} + function PreviewUnavailable({ type }: { type: string }) { return ( @@ -125,11 +155,6 @@ function PreviewUnavailable({ type }: { type: string }) { } const styles = StyleSheet.create({ - listContainer: { - flex: 1, - borderRadius: 16, - overflow: 'hidden', - }, smallPreview: { height: 32, width: 32, diff --git a/src/plugins/init/developer-settings/screens/RevengeDeveloperSettingScreen.tsx b/src/plugins/init/developer-settings/screens/RevengeDeveloperSettingScreen.tsx index 69dfc19..a17fe93 100644 --- a/src/plugins/init/developer-settings/screens/RevengeDeveloperSettingScreen.tsx +++ b/src/plugins/init/developer-settings/screens/RevengeDeveloperSettingScreen.tsx @@ -3,7 +3,16 @@ import { SettingListRenderer } from '@revenge-mod/discord/modules/settings/rende import { React, ReactNative } from '@revenge-mod/react' import { MobileSetting } from '../constants' -import { DevToolsContext } from '../devtools' +import { DevToolsContext, useIsDevToolsOpen } from '../react-devtools' +import { api } from '..' +import { ToastActionCreators } from '@revenge-mod/discord/actions' +import { lookupGeneratedIconComponent } from '@revenge-mod/utils/discord' + +const CircleCheckIcon = lookupGeneratedIconComponent( + 'CircleCheckIcon', + 'CircleCheckIcon-secondary', + 'CircleCheckIcon-primary', +) // TODO(plugins/settings): debug bridge export default function RevengeDeveloperSettingScreen() { @@ -18,6 +27,7 @@ export default function RevengeDeveloperSettingScreen() { { settings: [ MobileSetting.REACT_DEVTOOLS_VERSION, + MobileSetting.REACT_DEVTOOLS_AUTO_CONNECT, MobileSetting.REACT_DEVTOOLS_CONNECT, MobileSetting.REACT_DEVTOOLS_DISCONNECT, ], @@ -33,15 +43,33 @@ export default function RevengeDeveloperSettingScreen() { } export function ReactDevToolsAddressSetting() { + const open = useIsDevToolsOpen() + const settings = api.storage.use(s => s.devtools?.address) + return ( (DevToolsContext.address = text)} + onBlur={() => { + api.storage + .set({ + devtools: { + address: DevToolsContext.address, + }, + }) + .then(() => { + ToastActionCreators.open({ + IconComponent: CircleCheckIcon, + key: 'REACT_DEVTOOLS_ADDRESS_SAVED', + content: 'Address saved', + }) + }) + }} returnKeyType="done" />