feat(plugins/developer-kit): improve UI for JS eval
Currently the eval is done in a modal which makes it hard to use and means any code written is discarded after pressing evaluate and also it is hard to read output from an alert. This commit improves it by instead having it as a page instead where the code remains after evaluation and outputs the result in a box in the page rather than in an alert. Eval results are also selectable now, and have display options. And inspecting with zero depth is now possible. --------- Co-authored-by: PalmDevs <git@palmdevs.me>
This commit is contained in:
parent
3284300ef1
commit
aa07721269
3 changed files with 156 additions and 133 deletions
|
|
@ -21,5 +21,6 @@ export const Setting = {
|
|||
export const RouteNames = {
|
||||
[Setting.RevengeDeveloper]: 'Revenge Developer',
|
||||
[Setting.AssetBrowser]: 'Asset Browser',
|
||||
[Setting.EvalJS]: 'Evaluate JavaScript',
|
||||
[Setting.TestErrorBoundary]: 'Test ErrorBoundary',
|
||||
} as const
|
||||
|
|
|
|||
|
|
@ -1,144 +1,18 @@
|
|||
import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
|
||||
import { AlertActionCreators } from '@revenge-mod/discord/actions'
|
||||
import { Design } from '@revenge-mod/discord/design'
|
||||
import { nodeUtil } from '@revenge-mod/externals/browserify'
|
||||
import { getErrorStack } from '@revenge-mod/utils/error'
|
||||
import { sleep } from '@revenge-mod/utils/promise'
|
||||
import { useRef, useState } from 'react'
|
||||
import { api } from '..'
|
||||
import { Setting } from '../constants'
|
||||
import { TableRowAssetIcon } from '@revenge-mod/components'
|
||||
import { RouteNames, Setting } from '../constants'
|
||||
import EvalJSSettingScreen from '../screens/EvalJSSettingScreen'
|
||||
import type { SettingsItem } from '@revenge-mod/discord/modules/settings'
|
||||
|
||||
const AlertKey = 'evaluate-javascript'
|
||||
|
||||
const EvalJSSetting: SettingsItem = {
|
||||
parent: Setting.RevengeDeveloper,
|
||||
type: 'route',
|
||||
IconComponent: () => <TableRowAssetIcon name="FileIcon" />,
|
||||
title: () => 'Evaluate JavaScript',
|
||||
useDescription: () => 'Runs a JavaScript code snippet.',
|
||||
onPress: () => {
|
||||
AlertActionCreators.openAlert(AlertKey, <EvalJSAlert />)
|
||||
screen: {
|
||||
route: RouteNames[Setting.EvalJS],
|
||||
getComponent: () => EvalJSSettingScreen,
|
||||
},
|
||||
type: 'pressable',
|
||||
}
|
||||
|
||||
const {
|
||||
AlertActionButton,
|
||||
AlertModal,
|
||||
Button,
|
||||
Slider,
|
||||
Stack,
|
||||
TableRow,
|
||||
TableRowGroup,
|
||||
TableSwitchRow,
|
||||
Text,
|
||||
TextArea,
|
||||
} = Design
|
||||
|
||||
function EvalJSAlert() {
|
||||
const code = useRef('')
|
||||
const [awaitResult, setAwaitResult] = useState(true)
|
||||
const [inspectDepth, setInspectDepth] = useState(3)
|
||||
const [showHidden, setShowHidden] = useState(true)
|
||||
|
||||
return (
|
||||
<AlertModal
|
||||
actions={
|
||||
<>
|
||||
<Button
|
||||
onPress={async function onPress() {
|
||||
const key = `_${Math.random()
|
||||
.toString(36)
|
||||
.substring(2, 15)}`
|
||||
|
||||
try {
|
||||
if (!api) {
|
||||
alert(
|
||||
'Unable to provide plugin API. Running snippet in a second...',
|
||||
)
|
||||
|
||||
await sleep(1000)
|
||||
}
|
||||
// @ts-expect-error
|
||||
else globalThis[key] = api
|
||||
|
||||
// biome-ignore lint/security/noGlobalEval: Intentional
|
||||
const res = (0, eval)(
|
||||
`var api=${key},{unscoped:revenge}=api;undefined;${code.current}//# sourceURL=Revenge:EvalJS`,
|
||||
)
|
||||
|
||||
alert(
|
||||
nodeUtil.inspect(
|
||||
awaitResult ? await res : res,
|
||||
{
|
||||
depth: inspectDepth,
|
||||
showHidden,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
// @ts-expect-error
|
||||
delete globalThis[key]
|
||||
} catch (e) {
|
||||
alert(getErrorStack(e))
|
||||
}
|
||||
|
||||
AlertActionCreators.dismissAlert(AlertKey)
|
||||
}}
|
||||
text="Evaluate"
|
||||
variant="primary"
|
||||
/>
|
||||
<AlertActionButton text="Cancel" variant="secondary" />
|
||||
</>
|
||||
}
|
||||
extraContent={
|
||||
<Stack>
|
||||
<TextArea
|
||||
autoFocus
|
||||
label="Code"
|
||||
onChange={v => {
|
||||
code.current = v
|
||||
}}
|
||||
placeholder="revenge.discord.native.BundleUpdaterManager.reload()"
|
||||
size="md"
|
||||
/>
|
||||
<TableRowGroup>
|
||||
<TableSwitchRow
|
||||
label="Await result"
|
||||
onValueChange={setAwaitResult}
|
||||
subLabel="Wait for the result of the code before displaying it."
|
||||
value={awaitResult}
|
||||
/>
|
||||
<TableSwitchRow
|
||||
label="Show hidden"
|
||||
onValueChange={setShowHidden}
|
||||
subLabel="Show hidden properties of the object."
|
||||
value={showHidden}
|
||||
/>
|
||||
<TableRow
|
||||
label="Inspect depth"
|
||||
subLabel="The depth of the object to inspect."
|
||||
trailing={
|
||||
<Text variant="text-sm/normal">
|
||||
{inspectDepth}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
</TableRowGroup>
|
||||
<Slider
|
||||
endIcon={<Text variant="text-sm/normal">10</Text>}
|
||||
maximumValue={10}
|
||||
minimumValue={1}
|
||||
onValueChange={setInspectDepth}
|
||||
startIcon={<Text variant="text-sm/normal">1</Text>}
|
||||
step={1}
|
||||
value={inspectDepth}
|
||||
/>
|
||||
</Stack>
|
||||
}
|
||||
title="Evaluate JavaScript"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default EvalJSSetting
|
||||
|
|
|
|||
148
src/plugins/start/developer-kit/screens/EvalJSSettingScreen.tsx
Normal file
148
src/plugins/start/developer-kit/screens/EvalJSSettingScreen.tsx
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
import { Page } from '@revenge-mod/components'
|
||||
import { Design } from '@revenge-mod/discord/design'
|
||||
import { nodeUtil } from '@revenge-mod/externals/browserify'
|
||||
import { getErrorStack } from '@revenge-mod/utils/error'
|
||||
import { sleep } from '@revenge-mod/utils/promise'
|
||||
import { useRef, useState } from 'react'
|
||||
import { ScrollView } from 'react-native'
|
||||
import { api } from '..'
|
||||
|
||||
const {
|
||||
Button,
|
||||
Card,
|
||||
Slider,
|
||||
Stack,
|
||||
TableRow,
|
||||
TableRowGroup,
|
||||
TableSwitchRow,
|
||||
Text,
|
||||
TextArea,
|
||||
} = Design
|
||||
|
||||
export default function EvalJSSettingScreen() {
|
||||
const code = useRef('')
|
||||
const [result, setResult] = useState('')
|
||||
const [awaitResult, setAwaitResult] = useState(true)
|
||||
const [showHidden, setShowHidden] = useState(true)
|
||||
const [inspectDepth, setInspectDepth] = useState(3)
|
||||
const [wordWrap, setWordWrap] = useState(true)
|
||||
|
||||
return (
|
||||
<ScrollView contentContainerStyle={{ paddingBottom: 32 }}>
|
||||
<Page spacing={16}>
|
||||
<TextArea
|
||||
autoFocus
|
||||
label="Code"
|
||||
onChange={v => {
|
||||
code.current = v
|
||||
}}
|
||||
placeholder="revenge.discord.native.BundleUpdaterManager.reload()"
|
||||
size="md"
|
||||
/>
|
||||
<TableRowGroup title="Inspection Options">
|
||||
<TableSwitchRow
|
||||
label="Await result"
|
||||
onValueChange={setAwaitResult}
|
||||
subLabel="Wait for the result of the code before displaying it."
|
||||
value={awaitResult}
|
||||
/>
|
||||
<TableSwitchRow
|
||||
label="Show hidden"
|
||||
onValueChange={setShowHidden}
|
||||
subLabel="Show hidden properties of the object."
|
||||
value={showHidden}
|
||||
/>
|
||||
<TableRow
|
||||
label="Inspect depth"
|
||||
subLabel="The depth of the object to inspect."
|
||||
trailing={
|
||||
<Text variant="text-sm/normal">{inspectDepth}</Text>
|
||||
}
|
||||
/>
|
||||
</TableRowGroup>
|
||||
<Slider
|
||||
endIcon={<Text variant="text-sm/normal">10</Text>}
|
||||
maximumValue={10}
|
||||
minimumValue={0}
|
||||
onValueChange={setInspectDepth}
|
||||
startIcon={<Text variant="text-sm/normal">0</Text>}
|
||||
step={1}
|
||||
value={inspectDepth}
|
||||
/>
|
||||
<TableRowGroup title="Display Options">
|
||||
<TableSwitchRow
|
||||
label="Word wrap"
|
||||
subLabel="Long results will wrap to the next line."
|
||||
onValueChange={setWordWrap}
|
||||
value={wordWrap}
|
||||
/>
|
||||
</TableRowGroup>
|
||||
<Stack>
|
||||
<Text variant="heading-sm/semibold" color="text-strong">
|
||||
Result
|
||||
</Text>
|
||||
<Card>
|
||||
{/* TODO: Maybe try to make it fill space with minHeight: 128? */}
|
||||
<ScrollView
|
||||
nestedScrollEnabled
|
||||
style={{ maxHeight: 300 }}
|
||||
>
|
||||
<ScrollView
|
||||
nestedScrollEnabled
|
||||
horizontal={!wordWrap}
|
||||
>
|
||||
<Text
|
||||
selectable
|
||||
style={{ fontFamily: 'monospace' }}
|
||||
>
|
||||
{result || '<empty>'}
|
||||
</Text>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
</Card>
|
||||
</Stack>
|
||||
<Button
|
||||
onPress={async function onPress() {
|
||||
const key = `_${Math.random()
|
||||
.toString(36)
|
||||
.substring(2, 15)}`
|
||||
|
||||
try {
|
||||
if (!api) {
|
||||
setResult(
|
||||
'<Revenge API not available, snippet will be run without it...>',
|
||||
)
|
||||
|
||||
await sleep(1000)
|
||||
}
|
||||
// @ts-expect-error
|
||||
else globalThis[key] = api
|
||||
|
||||
// biome-ignore lint/security/noGlobalEval: Intentional
|
||||
const res = (0, eval)(
|
||||
`var api=${key},{unscoped:revenge}=api;undefined;${code.current}//# sourceURL=Revenge:EvalJS`,
|
||||
)
|
||||
|
||||
setResult(
|
||||
nodeUtil.inspect(
|
||||
awaitResult ? await res : res,
|
||||
{
|
||||
depth: inspectDepth,
|
||||
showHidden,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
// @ts-expect-error
|
||||
delete globalThis[key]
|
||||
} catch (e) {
|
||||
setResult(getErrorStack(e))
|
||||
}
|
||||
}}
|
||||
text="Evaluate"
|
||||
variant="primary"
|
||||
/>
|
||||
</Page>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue