feat(plugins/settings): add react devtools support
This commit is contained in:
parent
1d32f75f79
commit
8f74dfc79c
8 changed files with 194 additions and 13 deletions
2
lib/types/types.d.ts
vendored
2
lib/types/types.d.ts
vendored
|
|
@ -47,7 +47,7 @@ declare global {
|
|||
|
||||
declare global {
|
||||
var __REACT_DEVTOOLS_GLOBAL_HOOK__: unknown | undefined
|
||||
var __reactDevTools:
|
||||
var __REACT_DEVTOOLS__:
|
||||
| {
|
||||
version: string
|
||||
exports: {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const MobileSetting = {
|
|||
REVENGE_DEVELOPER: 'REVENGE_DEVELOPER',
|
||||
|
||||
// SUBSETTINGS
|
||||
|
||||
REVENGE_DISCORD_SERVER: 'REVENGE_DISCORD_SERVER',
|
||||
REVENGE_GITHUB_ORGANIZATION: 'REVENGE_GITHUB_ORGANIZATION',
|
||||
RELOAD_APP: 'RELOAD_APP',
|
||||
|
|
@ -17,6 +18,9 @@ export const MobileSetting = {
|
|||
REACT_NATIVE_VERSION: 'REACT_NATIVE_VERSION',
|
||||
HERMES_VERSION: 'HERMES_VERSION',
|
||||
|
||||
REACT_DEVTOOLS_VERSION: 'REACT_DEVTOOLS_VERSION',
|
||||
REACT_DEVTOOLS_CONNECT: 'REACT_DEVTOOLS_CONNECT',
|
||||
REACT_DEVTOOLS_DISCONNECT: 'REACT_DEVTOOLS_DISCONNECT',
|
||||
EVALUATE_JAVASCRIPT: 'EVALUATE_JAVASCRIPT',
|
||||
|
||||
REVENGE_NOT_IMPLEMENTED: 'REVENGE_NOT_IMPLEMENTED',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { React } from '@revenge-mod/react'
|
||||
|
||||
import AssetIcon from '../components/icons/AssetIcon'
|
||||
import { connectToDevTools, DevToolsContext, useIsDevToolsOpen } from '../devtools'
|
||||
import { MobileSetting } from '../SettingsConstants'
|
||||
|
||||
import type { SettingsItem } from '@revenge-mod/discord/ui/settings'
|
||||
|
||||
const ReactDevToolsConnectSetting: SettingsItem = {
|
||||
parent: MobileSetting.REVENGE_DEVELOPER,
|
||||
IconComponent: () => <AssetIcon name="LinkIcon" />,
|
||||
title: () => 'Connect to React DevTools',
|
||||
usePredicate: () => {
|
||||
const dtOpen = useIsDevToolsOpen()
|
||||
return DevToolsContext.available && !dtOpen
|
||||
},
|
||||
onPress: connectToDevTools,
|
||||
type: 'pressable',
|
||||
}
|
||||
|
||||
export default ReactDevToolsConnectSetting
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { React } from '@revenge-mod/react'
|
||||
|
||||
import AssetIcon from '../components/icons/AssetIcon'
|
||||
import { disconnectFromDevTools, useIsDevToolsOpen } from '../devtools'
|
||||
import { MobileSetting } from '../SettingsConstants'
|
||||
|
||||
import type { SettingsItem } from '@revenge-mod/discord/ui/settings'
|
||||
|
||||
const ReactDevToolsDisconnectSetting: SettingsItem = {
|
||||
parent: MobileSetting.REVENGE_DEVELOPER,
|
||||
IconComponent: () => <AssetIcon name="DenyIcon" variant="danger" />,
|
||||
variant: 'danger',
|
||||
title: () => 'Disconnect from React DevTools',
|
||||
usePredicate: useIsDevToolsOpen,
|
||||
onPress: disconnectFromDevTools,
|
||||
type: 'pressable',
|
||||
}
|
||||
|
||||
export default ReactDevToolsDisconnectSetting
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { React } from '@revenge-mod/react'
|
||||
|
||||
import AssetIcon from '../components/icons/AssetIcon'
|
||||
import { MobileSetting } from '../SettingsConstants'
|
||||
|
||||
import type { SettingsItem } from '@revenge-mod/discord/ui/settings'
|
||||
import { DevToolsContext } from '../devtools'
|
||||
|
||||
const ReactDevToolsVersionSetting: SettingsItem = {
|
||||
parent: MobileSetting.REVENGE_DEVELOPER,
|
||||
IconComponent: () => <AssetIcon name="ScienceIcon" />,
|
||||
title: () => 'React DevTools',
|
||||
useDescription: () => __REACT_DEVTOOLS__!.version,
|
||||
usePredicate: () => DevToolsContext.available,
|
||||
type: 'static',
|
||||
}
|
||||
|
||||
export default ReactDevToolsVersionSetting
|
||||
84
src/plugins/settings/devtools.ts
Normal file
84
src/plugins/settings/devtools.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import { getAssetByName } from '@revenge-mod/assets'
|
||||
import { ToastActionCreators } from '@revenge-mod/discord/actions'
|
||||
import { React } from '@revenge-mod/react'
|
||||
import { getErrorStack } from '@revenge-mod/utils/errors'
|
||||
import { useReRender } from '@revenge-mod/utils/react'
|
||||
import { pluginApi } from '.'
|
||||
|
||||
export const DevToolsContext: {
|
||||
ws: WebSocket | undefined
|
||||
address: string
|
||||
open: boolean
|
||||
available: boolean
|
||||
} = {
|
||||
available: Boolean(globalThis.__REACT_DEVTOOLS__),
|
||||
ws: undefined,
|
||||
address: 'localhost:8097',
|
||||
open: false,
|
||||
}
|
||||
|
||||
type Subscription = (event: 1 | 2 | 3, err?: unknown) => void
|
||||
const subscriptions: Set<Subscription> = new Set()
|
||||
|
||||
subscriptions.add((e, err) => {
|
||||
if (e === 3) {
|
||||
const actualError = (err as { message: string }).message ?? getErrorStack(err)
|
||||
|
||||
pluginApi.logger.error('React DevTools error:', actualError)
|
||||
|
||||
ToastActionCreators.open({
|
||||
key: 'DEVTOOLS_ERROR',
|
||||
icon: getAssetByName('CircleXIcon-primary')!.id,
|
||||
content: actualError,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export function connectToDevTools() {
|
||||
if (!DevToolsContext.available || DevToolsContext.ws) return
|
||||
|
||||
const websocket = (DevToolsContext.ws = new WebSocket(`ws://${DevToolsContext.address}`))
|
||||
|
||||
websocket.addEventListener('open', () => {
|
||||
DevToolsContext.open = true
|
||||
for (const sub of subscriptions) sub(1)
|
||||
})
|
||||
|
||||
websocket.addEventListener('close', () => {
|
||||
cleanup()
|
||||
for (const sub of subscriptions) sub(2)
|
||||
})
|
||||
|
||||
websocket.addEventListener('error', e => {
|
||||
cleanup()
|
||||
for (const sub of subscriptions) sub(3, e)
|
||||
})
|
||||
|
||||
__REACT_DEVTOOLS__!.exports.connectToDevTools({
|
||||
websocket,
|
||||
})
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
DevToolsContext.open = false
|
||||
DevToolsContext.ws = undefined
|
||||
}
|
||||
|
||||
export function disconnectFromDevTools() {
|
||||
if (DevToolsContext.ws) DevToolsContext.ws.close()
|
||||
}
|
||||
|
||||
export function useIsDevToolsOpen() {
|
||||
const rerender = useReRender()
|
||||
|
||||
React.useEffect(() => {
|
||||
const sub: Subscription = e => (e === 1 || e === 2) && rerender()
|
||||
subscriptions.add(sub)
|
||||
|
||||
return () => {
|
||||
subscriptions.delete(sub)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return DevToolsContext.open
|
||||
}
|
||||
|
|
@ -13,6 +13,9 @@ import RevengeVersionSetting from './definitions/RevengeVersionSetting'
|
|||
import HermesVersionSetting from './definitions/HermesVersionSetting'
|
||||
import RevengeNotImplementedSetting from './definitions/RevengeNotImplementedSetting'
|
||||
import EvaluateJavaScriptSetting from './definitions/EvaluateJavaScriptSetting'
|
||||
import ReactDevToolsVersionSetting from './definitions/ReactDevToolsVersionSetting'
|
||||
import ReactDevToolsConnectSetting from './definitions/ReactDevToolsConnectSetting'
|
||||
import ReactDevToolsDisconnectSetting from './definitions/ReactDevToolsDisconnectSetting'
|
||||
import ReloadAppSetting from './definitions/ReloadAppSetting'
|
||||
import RevengeDiscordServerSetting from './definitions/RevengeDiscordServerSetting'
|
||||
import RevengeGitHubOrganizationSetting from './definitions/RevengeGitHubOrganizationSetting'
|
||||
|
|
@ -30,6 +33,9 @@ registerSettingsItems({
|
|||
[MobileSetting.REACT_VERSION]: ReactVersionSetting,
|
||||
[MobileSetting.REACT_NATIVE_VERSION]: ReactNativeVersionSetting,
|
||||
[MobileSetting.HERMES_VERSION]: HermesVersionSetting,
|
||||
[MobileSetting.REACT_DEVTOOLS_VERSION]: ReactDevToolsVersionSetting,
|
||||
[MobileSetting.REACT_DEVTOOLS_CONNECT]: ReactDevToolsConnectSetting,
|
||||
[MobileSetting.REACT_DEVTOOLS_DISCONNECT]: ReactDevToolsDisconnectSetting,
|
||||
[MobileSetting.EVALUATE_JAVASCRIPT]: EvaluateJavaScriptSetting,
|
||||
[MobileSetting.REVENGE_NOT_IMPLEMENTED]: RevengeNotImplementedSetting,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,20 +1,49 @@
|
|||
import { React, ReactNative } from '@revenge-mod/react'
|
||||
import { Components } from '@revenge-mod/discord/ui'
|
||||
import { SettingListRenderer } from '@revenge-mod/discord/ui/settings'
|
||||
|
||||
import { MobileSetting } from '../SettingsConstants'
|
||||
import { DevToolsContext } from '../devtools'
|
||||
|
||||
// TODO(plugins/settings): debug bridge
|
||||
export default function RevengeDeveloperSettingScreen() {
|
||||
return (
|
||||
<SettingListRenderer.SettingsList
|
||||
sections={[
|
||||
{
|
||||
label: 'React DevTools',
|
||||
settings: [MobileSetting.REVENGE_NOT_IMPLEMENTED],
|
||||
},
|
||||
{
|
||||
label: 'Tools',
|
||||
settings: [MobileSetting.EVALUATE_JAVASCRIPT],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Components.Stack spacing={0} style={{ flexGrow: 1 }}>
|
||||
{globalThis.__REACT_DEVTOOLS__ && <ReactDevToolsAddressSetting />}
|
||||
<SettingListRenderer.SettingsList
|
||||
sections={[
|
||||
{
|
||||
settings: [],
|
||||
},
|
||||
{
|
||||
settings: [
|
||||
MobileSetting.REACT_DEVTOOLS_VERSION,
|
||||
MobileSetting.REACT_DEVTOOLS_CONNECT,
|
||||
MobileSetting.REACT_DEVTOOLS_DISCONNECT,
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Tools',
|
||||
settings: [MobileSetting.EVALUATE_JAVASCRIPT],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Components.Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export function ReactDevToolsAddressSetting() {
|
||||
return (
|
||||
<ReactNative.View style={{ paddingHorizontal: 16, paddingTop: 16 }}>
|
||||
<Components.TextInput
|
||||
editable={!DevToolsContext.open}
|
||||
isDisabled={DevToolsContext.open}
|
||||
leadingText="ws://"
|
||||
defaultValue={DevToolsContext.address}
|
||||
label="React DevTools"
|
||||
onChange={text => (DevToolsContext.address = text)}
|
||||
returnKeyType="done"
|
||||
/>
|
||||
</ReactNative.View>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue