2025-10-22 22:20:58 +07:00
|
|
|
import { getAssetIdByName } from '@revenge-mod/assets'
|
2025-10-23 00:53:35 +07:00
|
|
|
import { FormSwitch, TableRowAssetIcon } from '@revenge-mod/components'
|
2025-10-22 22:20:58 +07:00
|
|
|
import {
|
|
|
|
|
ActionSheetActionCreators,
|
|
|
|
|
ToastActionCreators,
|
|
|
|
|
} from '@revenge-mod/discord/actions'
|
|
|
|
|
import { Design } from '@revenge-mod/discord/design'
|
|
|
|
|
import { Clipboard } from '@revenge-mod/externals/react-native-clipboard'
|
|
|
|
|
import {
|
|
|
|
|
getInternalPluginMeta,
|
2025-10-23 17:50:32 +07:00
|
|
|
InternalPluginFlags,
|
2025-10-22 22:20:58 +07:00
|
|
|
isPluginEssential,
|
|
|
|
|
} from '@revenge-mod/plugins/_'
|
2025-10-23 17:50:32 +07:00
|
|
|
import { PluginFlags, PluginStatus } from '@revenge-mod/plugins/constants'
|
2025-10-22 22:20:58 +07:00
|
|
|
import { lookupGeneratedIconComponent } from '@revenge-mod/utils/discord'
|
|
|
|
|
import { getErrorStack } from '@revenge-mod/utils/error'
|
2025-10-23 17:50:32 +07:00
|
|
|
import { Pressable } from 'react-native'
|
|
|
|
|
import { ClickOutsideProvider } from 'react-native-click-outside'
|
2025-10-22 22:20:58 +07:00
|
|
|
import { handleDisablePlugin, handleEnablePlugin } from '../utils/actions'
|
|
|
|
|
import {
|
|
|
|
|
openPluginSettings,
|
|
|
|
|
showPluginClearDataConfirmation,
|
|
|
|
|
} from '../utils/alerts'
|
|
|
|
|
import { PluginInfo } from './PluginCard'
|
2025-10-23 17:50:32 +07:00
|
|
|
import { usePluginEnabled, usePluginStatus } from './PluginStateProvider'
|
|
|
|
|
import {
|
|
|
|
|
EnablePluginTooltipProvider,
|
|
|
|
|
useClickOutsideTooltip,
|
|
|
|
|
useEnablePluginTooltip,
|
|
|
|
|
} from './TooltipProvider'
|
2025-10-22 22:20:58 +07:00
|
|
|
import type { AnyPlugin } from '@revenge-mod/plugins/_'
|
|
|
|
|
|
|
|
|
|
export interface PluginOptionsActionSheetProps {
|
|
|
|
|
plugin: AnyPlugin
|
|
|
|
|
sheetKey: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { ActionSheet, IconButton, TableRowGroup, TableRow, Stack } = Design
|
|
|
|
|
|
|
|
|
|
const FileWarningIcon = getAssetIdByName('FileWarningIcon', 'png')!
|
|
|
|
|
const SettingsIcon = getAssetIdByName('SettingsIcon', 'png')!
|
|
|
|
|
|
|
|
|
|
export default function PluginOptionsActionSheet({
|
|
|
|
|
plugin,
|
|
|
|
|
sheetKey,
|
|
|
|
|
}: PluginOptionsActionSheetProps) {
|
2025-10-23 17:50:32 +07:00
|
|
|
const status = usePluginStatus(plugin)
|
2025-10-22 22:20:58 +07:00
|
|
|
const enabled = usePluginEnabled(plugin)
|
|
|
|
|
const meta = getInternalPluginMeta(plugin)
|
|
|
|
|
const { dependencies, dependents } = meta!
|
|
|
|
|
const essential = isPluginEssential(meta)
|
|
|
|
|
const {
|
|
|
|
|
manifest: { id, name, author, description, icon },
|
|
|
|
|
errors,
|
|
|
|
|
flags,
|
|
|
|
|
} = plugin
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<ActionSheet>
|
2025-10-23 17:06:08 +07:00
|
|
|
<Stack spacing={24} style={{ paddingTop: 8 }}>
|
2025-10-22 22:20:58 +07:00
|
|
|
<PluginInfo
|
|
|
|
|
name={name}
|
|
|
|
|
author={author}
|
|
|
|
|
description={description}
|
|
|
|
|
icon={icon}
|
|
|
|
|
actions={
|
|
|
|
|
!essential && (
|
|
|
|
|
<FormSwitch
|
|
|
|
|
value={enabled}
|
|
|
|
|
onValueChange={v => {
|
|
|
|
|
if (v) handleEnablePlugin(plugin)
|
|
|
|
|
else handleDisablePlugin(plugin)
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
/>
|
2025-10-23 17:50:32 +07:00
|
|
|
<ClickOutsideProvider>
|
|
|
|
|
<EnablePluginTooltipProvider>
|
|
|
|
|
<PluginActions
|
|
|
|
|
plugin={plugin}
|
|
|
|
|
closeSheet={() => {
|
|
|
|
|
ActionSheetActionCreators.hideActionSheet(
|
|
|
|
|
sheetKey,
|
|
|
|
|
)
|
2025-10-22 22:20:58 +07:00
|
|
|
}}
|
|
|
|
|
/>
|
2025-10-23 17:50:32 +07:00
|
|
|
</EnablePluginTooltipProvider>
|
|
|
|
|
</ClickOutsideProvider>
|
2025-10-22 22:20:58 +07:00
|
|
|
<TableRowGroup title="Status">
|
2025-10-23 00:53:35 +07:00
|
|
|
<TableRow
|
2025-10-23 17:50:32 +07:00
|
|
|
icon={
|
|
|
|
|
<TableRowAssetIcon name="CircleInformationIcon" />
|
|
|
|
|
}
|
|
|
|
|
label="Status (TODO)"
|
|
|
|
|
subLabel={bitFieldToString(PluginStatus, status)}
|
2025-10-23 00:53:35 +07:00
|
|
|
/>
|
2025-10-22 22:20:58 +07:00
|
|
|
{errors.length && (
|
|
|
|
|
<TableRow
|
2025-10-23 00:53:35 +07:00
|
|
|
variant="danger"
|
2025-10-22 22:20:58 +07:00
|
|
|
label="Errors"
|
2025-10-23 00:53:35 +07:00
|
|
|
icon={
|
|
|
|
|
<TableRowAssetIcon
|
|
|
|
|
variant="danger"
|
2025-10-23 17:50:32 +07:00
|
|
|
name="CircleErrorIcon"
|
2025-10-23 00:53:35 +07:00
|
|
|
/>
|
|
|
|
|
}
|
2025-10-22 22:20:58 +07:00
|
|
|
subLabel={`${errors.length} errors. Tap to copy.`}
|
|
|
|
|
onPress={() => {
|
|
|
|
|
Clipboard.setString(
|
|
|
|
|
errors.map(getErrorStack).join('\n'),
|
|
|
|
|
)
|
|
|
|
|
showCopiedToClipboardToast()
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</TableRowGroup>
|
|
|
|
|
<TableRowGroup title="Advanced">
|
|
|
|
|
<TableRow
|
2025-10-23 00:53:35 +07:00
|
|
|
icon={<TableRowAssetIcon name="IdIcon" />}
|
2025-10-22 22:20:58 +07:00
|
|
|
label="ID"
|
|
|
|
|
subLabel={id}
|
|
|
|
|
onPress={() => {
|
|
|
|
|
Clipboard.setString(id)
|
|
|
|
|
showCopiedToClipboardToast()
|
|
|
|
|
}}
|
|
|
|
|
/>
|
2025-10-23 17:50:32 +07:00
|
|
|
<TableRow
|
|
|
|
|
icon={<TableRowAssetIcon name="FlagIcon" />}
|
|
|
|
|
label="Flags"
|
|
|
|
|
subLabel={bitFieldToString(PluginFlags, flags)}
|
|
|
|
|
/>
|
|
|
|
|
{meta.iflags && (
|
|
|
|
|
<TableRow
|
|
|
|
|
icon={<TableRowAssetIcon name="FlagIcon" />}
|
|
|
|
|
label="Internal Flags"
|
|
|
|
|
subLabel={bitFieldToString(
|
|
|
|
|
InternalPluginFlags,
|
|
|
|
|
meta.iflags,
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2025-10-22 22:20:58 +07:00
|
|
|
{dependencies?.length && (
|
|
|
|
|
<TableRow
|
2025-10-23 00:53:35 +07:00
|
|
|
icon={<TableRowAssetIcon name="ListBulletsIcon" />}
|
2025-10-22 22:20:58 +07:00
|
|
|
label="Dependencies (TODO)"
|
|
|
|
|
subLabel={`${name} depends on ${dependencies.length} other plugins`}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
{dependents?.length && (
|
|
|
|
|
<TableRow
|
2025-10-23 00:53:35 +07:00
|
|
|
icon={<TableRowAssetIcon name="ListBulletsIcon" />}
|
2025-10-22 22:20:58 +07:00
|
|
|
label="Dependents (TODO)"
|
|
|
|
|
subLabel={`${dependents.length} other plugins depend on ${name}`}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</TableRowGroup>
|
|
|
|
|
</Stack>
|
|
|
|
|
</ActionSheet>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-23 17:50:32 +07:00
|
|
|
function bitFieldToString(map: Record<string, number>, bitField: number) {
|
|
|
|
|
return (
|
|
|
|
|
Object.entries(map)
|
|
|
|
|
.filter(([, value]) => bitField & value)
|
|
|
|
|
.map(([key]) => key)
|
|
|
|
|
.join(', ') || '-'
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function PluginActions({
|
|
|
|
|
plugin,
|
|
|
|
|
closeSheet,
|
|
|
|
|
}: {
|
|
|
|
|
plugin: AnyPlugin
|
|
|
|
|
closeSheet: () => void
|
|
|
|
|
}) {
|
|
|
|
|
const enabled = usePluginEnabled(plugin)
|
|
|
|
|
const enableTooltip = useEnablePluginTooltip()
|
|
|
|
|
const settingsRef = useClickOutsideTooltip(useEnablePluginTooltip, () => {})
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Stack
|
|
|
|
|
direction="horizontal"
|
|
|
|
|
justify="space-around"
|
|
|
|
|
style={{ paddingHorizontal: 8, paddingVertical: 16 }}
|
|
|
|
|
>
|
|
|
|
|
<IconButton
|
|
|
|
|
variant="secondary"
|
|
|
|
|
size="lg"
|
|
|
|
|
icon={FileWarningIcon}
|
|
|
|
|
label="Clear Data"
|
|
|
|
|
onPress={() => {
|
|
|
|
|
showPluginClearDataConfirmation(plugin, closeSheet)
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
{plugin.SettingsComponent && (
|
|
|
|
|
<Pressable
|
|
|
|
|
onPress={() => {
|
|
|
|
|
if (enabled) return
|
|
|
|
|
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
enableTooltip.targetRef.current =
|
|
|
|
|
settingsRef.current
|
|
|
|
|
enableTooltip.setVisible(true)
|
|
|
|
|
})
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Design.IconButton
|
|
|
|
|
ref={settingsRef}
|
|
|
|
|
variant="secondary"
|
|
|
|
|
size="lg"
|
|
|
|
|
icon={SettingsIcon}
|
|
|
|
|
label="Settings"
|
|
|
|
|
disabled={!enabled}
|
|
|
|
|
onPress={() => {
|
|
|
|
|
openPluginSettings(plugin)
|
|
|
|
|
closeSheet()
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</Pressable>
|
|
|
|
|
)}
|
|
|
|
|
</Stack>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-22 22:20:58 +07:00
|
|
|
const CopyIcon = lookupGeneratedIconComponent('CopyIcon')!
|
|
|
|
|
|
|
|
|
|
function showCopiedToClipboardToast() {
|
|
|
|
|
ToastActionCreators.open({
|
|
|
|
|
key: 'REVENGE_PLUGIN_SETTINGS_COPIED',
|
|
|
|
|
content: 'Copied to clipboard',
|
|
|
|
|
IconComponent: CopyIcon,
|
|
|
|
|
})
|
|
|
|
|
}
|