Skip to content

Commit aa07721

Browse files
tralwdwdPalmDevs
andauthored
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>
1 parent 3284300 commit aa07721

File tree

3 files changed

+156
-133
lines changed

3 files changed

+156
-133
lines changed

src/plugins/start/developer-kit/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ export const Setting = {
2121
export const RouteNames = {
2222
[Setting.RevengeDeveloper]: 'Revenge Developer',
2323
[Setting.AssetBrowser]: 'Asset Browser',
24+
[Setting.EvalJS]: 'Evaluate JavaScript',
2425
[Setting.TestErrorBoundary]: 'Test ErrorBoundary',
2526
} as const
Lines changed: 7 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,144 +1,18 @@
1-
import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
2-
import { AlertActionCreators } from '@revenge-mod/discord/actions'
3-
import { Design } from '@revenge-mod/discord/design'
4-
import { nodeUtil } from '@revenge-mod/externals/browserify'
5-
import { getErrorStack } from '@revenge-mod/utils/error'
6-
import { sleep } from '@revenge-mod/utils/promise'
7-
import { useRef, useState } from 'react'
8-
import { api } from '..'
9-
import { Setting } from '../constants'
1+
import { TableRowAssetIcon } from '@revenge-mod/components'
2+
import { RouteNames, Setting } from '../constants'
3+
import EvalJSSettingScreen from '../screens/EvalJSSettingScreen'
104
import type { SettingsItem } from '@revenge-mod/discord/modules/settings'
115

12-
const AlertKey = 'evaluate-javascript'
13-
146
const EvalJSSetting: SettingsItem = {
157
parent: Setting.RevengeDeveloper,
8+
type: 'route',
169
IconComponent: () => <TableRowAssetIcon name="FileIcon" />,
1710
title: () => 'Evaluate JavaScript',
1811
useDescription: () => 'Runs a JavaScript code snippet.',
19-
onPress: () => {
20-
AlertActionCreators.openAlert(AlertKey, <EvalJSAlert />)
12+
screen: {
13+
route: RouteNames[Setting.EvalJS],
14+
getComponent: () => EvalJSSettingScreen,
2115
},
22-
type: 'pressable',
23-
}
24-
25-
const {
26-
AlertActionButton,
27-
AlertModal,
28-
Button,
29-
Slider,
30-
Stack,
31-
TableRow,
32-
TableRowGroup,
33-
TableSwitchRow,
34-
Text,
35-
TextArea,
36-
} = Design
37-
38-
function EvalJSAlert() {
39-
const code = useRef('')
40-
const [awaitResult, setAwaitResult] = useState(true)
41-
const [inspectDepth, setInspectDepth] = useState(3)
42-
const [showHidden, setShowHidden] = useState(true)
43-
44-
return (
45-
<AlertModal
46-
actions={
47-
<>
48-
<Button
49-
onPress={async function onPress() {
50-
const key = `_${Math.random()
51-
.toString(36)
52-
.substring(2, 15)}`
53-
54-
try {
55-
if (!api) {
56-
alert(
57-
'Unable to provide plugin API. Running snippet in a second...',
58-
)
59-
60-
await sleep(1000)
61-
}
62-
// @ts-expect-error
63-
else globalThis[key] = api
64-
65-
// biome-ignore lint/security/noGlobalEval: Intentional
66-
const res = (0, eval)(
67-
`var api=${key},{unscoped:revenge}=api;undefined;${code.current}//# sourceURL=Revenge:EvalJS`,
68-
)
69-
70-
alert(
71-
nodeUtil.inspect(
72-
awaitResult ? await res : res,
73-
{
74-
depth: inspectDepth,
75-
showHidden,
76-
},
77-
),
78-
)
79-
80-
// @ts-expect-error
81-
delete globalThis[key]
82-
} catch (e) {
83-
alert(getErrorStack(e))
84-
}
85-
86-
AlertActionCreators.dismissAlert(AlertKey)
87-
}}
88-
text="Evaluate"
89-
variant="primary"
90-
/>
91-
<AlertActionButton text="Cancel" variant="secondary" />
92-
</>
93-
}
94-
extraContent={
95-
<Stack>
96-
<TextArea
97-
autoFocus
98-
label="Code"
99-
onChange={v => {
100-
code.current = v
101-
}}
102-
placeholder="revenge.discord.native.BundleUpdaterManager.reload()"
103-
size="md"
104-
/>
105-
<TableRowGroup>
106-
<TableSwitchRow
107-
label="Await result"
108-
onValueChange={setAwaitResult}
109-
subLabel="Wait for the result of the code before displaying it."
110-
value={awaitResult}
111-
/>
112-
<TableSwitchRow
113-
label="Show hidden"
114-
onValueChange={setShowHidden}
115-
subLabel="Show hidden properties of the object."
116-
value={showHidden}
117-
/>
118-
<TableRow
119-
label="Inspect depth"
120-
subLabel="The depth of the object to inspect."
121-
trailing={
122-
<Text variant="text-sm/normal">
123-
{inspectDepth}
124-
</Text>
125-
}
126-
/>
127-
</TableRowGroup>
128-
<Slider
129-
endIcon={<Text variant="text-sm/normal">10</Text>}
130-
maximumValue={10}
131-
minimumValue={1}
132-
onValueChange={setInspectDepth}
133-
startIcon={<Text variant="text-sm/normal">1</Text>}
134-
step={1}
135-
value={inspectDepth}
136-
/>
137-
</Stack>
138-
}
139-
title="Evaluate JavaScript"
140-
/>
141-
)
14216
}
14317

14418
export default EvalJSSetting
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { Page } from '@revenge-mod/components'
2+
import { Design } from '@revenge-mod/discord/design'
3+
import { nodeUtil } from '@revenge-mod/externals/browserify'
4+
import { getErrorStack } from '@revenge-mod/utils/error'
5+
import { sleep } from '@revenge-mod/utils/promise'
6+
import { useRef, useState } from 'react'
7+
import { ScrollView } from 'react-native'
8+
import { api } from '..'
9+
10+
const {
11+
Button,
12+
Card,
13+
Slider,
14+
Stack,
15+
TableRow,
16+
TableRowGroup,
17+
TableSwitchRow,
18+
Text,
19+
TextArea,
20+
} = Design
21+
22+
export default function EvalJSSettingScreen() {
23+
const code = useRef('')
24+
const [result, setResult] = useState('')
25+
const [awaitResult, setAwaitResult] = useState(true)
26+
const [showHidden, setShowHidden] = useState(true)
27+
const [inspectDepth, setInspectDepth] = useState(3)
28+
const [wordWrap, setWordWrap] = useState(true)
29+
30+
return (
31+
<ScrollView contentContainerStyle={{ paddingBottom: 32 }}>
32+
<Page spacing={16}>
33+
<TextArea
34+
autoFocus
35+
label="Code"
36+
onChange={v => {
37+
code.current = v
38+
}}
39+
placeholder="revenge.discord.native.BundleUpdaterManager.reload()"
40+
size="md"
41+
/>
42+
<TableRowGroup title="Inspection Options">
43+
<TableSwitchRow
44+
label="Await result"
45+
onValueChange={setAwaitResult}
46+
subLabel="Wait for the result of the code before displaying it."
47+
value={awaitResult}
48+
/>
49+
<TableSwitchRow
50+
label="Show hidden"
51+
onValueChange={setShowHidden}
52+
subLabel="Show hidden properties of the object."
53+
value={showHidden}
54+
/>
55+
<TableRow
56+
label="Inspect depth"
57+
subLabel="The depth of the object to inspect."
58+
trailing={
59+
<Text variant="text-sm/normal">{inspectDepth}</Text>
60+
}
61+
/>
62+
</TableRowGroup>
63+
<Slider
64+
endIcon={<Text variant="text-sm/normal">10</Text>}
65+
maximumValue={10}
66+
minimumValue={0}
67+
onValueChange={setInspectDepth}
68+
startIcon={<Text variant="text-sm/normal">0</Text>}
69+
step={1}
70+
value={inspectDepth}
71+
/>
72+
<TableRowGroup title="Display Options">
73+
<TableSwitchRow
74+
label="Word wrap"
75+
subLabel="Long results will wrap to the next line."
76+
onValueChange={setWordWrap}
77+
value={wordWrap}
78+
/>
79+
</TableRowGroup>
80+
<Stack>
81+
<Text variant="heading-sm/semibold" color="text-strong">
82+
Result
83+
</Text>
84+
<Card>
85+
{/* TODO: Maybe try to make it fill space with minHeight: 128? */}
86+
<ScrollView
87+
nestedScrollEnabled
88+
style={{ maxHeight: 300 }}
89+
>
90+
<ScrollView
91+
nestedScrollEnabled
92+
horizontal={!wordWrap}
93+
>
94+
<Text
95+
selectable
96+
style={{ fontFamily: 'monospace' }}
97+
>
98+
{result || '<empty>'}
99+
</Text>
100+
</ScrollView>
101+
</ScrollView>
102+
</Card>
103+
</Stack>
104+
<Button
105+
onPress={async function onPress() {
106+
const key = `_${Math.random()
107+
.toString(36)
108+
.substring(2, 15)}`
109+
110+
try {
111+
if (!api) {
112+
setResult(
113+
'<Revenge API not available, snippet will be run without it...>',
114+
)
115+
116+
await sleep(1000)
117+
}
118+
// @ts-expect-error
119+
else globalThis[key] = api
120+
121+
// biome-ignore lint/security/noGlobalEval: Intentional
122+
const res = (0, eval)(
123+
`var api=${key},{unscoped:revenge}=api;undefined;${code.current}//# sourceURL=Revenge:EvalJS`,
124+
)
125+
126+
setResult(
127+
nodeUtil.inspect(
128+
awaitResult ? await res : res,
129+
{
130+
depth: inspectDepth,
131+
showHidden,
132+
},
133+
),
134+
)
135+
136+
// @ts-expect-error
137+
delete globalThis[key]
138+
} catch (e) {
139+
setResult(getErrorStack(e))
140+
}
141+
}}
142+
text="Evaluate"
143+
variant="primary"
144+
/>
145+
</Page>
146+
</ScrollView>
147+
)
148+
}

0 commit comments

Comments
 (0)