Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
8327c863c1
33 changed files with 1705 additions and 195 deletions
92
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
92
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
|
|
@ -1,7 +1,7 @@
|
|||
name: Bug report
|
||||
description: Create a bug report to help us improve
|
||||
title: "[bug]: <insert title here>"
|
||||
labels: [bug]
|
||||
name: 🐞 Bug report
|
||||
description: Report a bug or an issue.
|
||||
title: 'bug: '
|
||||
labels: ['Bug report']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
|
@ -47,54 +47,28 @@ body:
|
|||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduce-steps
|
||||
attributes:
|
||||
label: Steps to reproduce the bug
|
||||
description: What did you do for the bug to show up?
|
||||
placeholder: |
|
||||
Example:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
placeholder: |
|
||||
Example:
|
||||
"This should happen..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual-behavior
|
||||
attributes:
|
||||
label: Actual behavior
|
||||
placeholder: |
|
||||
Example:
|
||||
"This happened instead..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: sreen-media
|
||||
attributes:
|
||||
label: Screenshots/Screen recordings
|
||||
label: Bug description
|
||||
description: |
|
||||
A picture or video helps us understand the bug more.
|
||||
|
||||
You can upload them directly in the text box.
|
||||
|
||||
- Describe your bug in detail
|
||||
- Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...)
|
||||
- Add images and videos if possible
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs
|
||||
description: |
|
||||
Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
label: Stack trace
|
||||
description: If this bug causes a JS crash, please paste the stack trace here.
|
||||
render: shell
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Component stack trace
|
||||
description: If this bug causes a JS crash, please paste the component stack trace here.
|
||||
render: shell
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Native crash trace
|
||||
description: If this bug causes a native crash, please paste the crash trace here. On Android, this can be accessed by doing `logcat | grep AndroidRuntime`.
|
||||
render: shell
|
||||
|
||||
- type: input
|
||||
|
|
@ -156,7 +130,6 @@ body:
|
|||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: debug-info
|
||||
attributes:
|
||||
label: Debug information
|
||||
description: |
|
||||
|
|
@ -171,11 +144,18 @@ body:
|
|||
Device: M2101K7BG (secret)
|
||||
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: Additional information
|
||||
placeholder: |
|
||||
Example:
|
||||
"I have tried to do this..."
|
||||
"This only happens in this situation..."
|
||||
"This could be related to..."
|
||||
label: Additional context
|
||||
description: Add additional context here.
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your bug report will be closed if you don't follow the checklist below.
|
||||
options:
|
||||
- label: I have checked all open and closed bug reports and this is not a duplicate.
|
||||
required: true
|
||||
- label: I have chosen an appropriate title.
|
||||
required: true
|
||||
- label: All requested information has been provided properly.
|
||||
required: true
|
||||
|
|
|
|||
51
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
51
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
|
|
@ -3,23 +3,17 @@ description: Suggest an idea for Kettu
|
|||
title: "[feature]: <insert title here>"
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
- type: markdown
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: You should ensure the completion of the task before proceeding to check it off the checklist. Neglecting to do so may impede the efficiency of the issue resolution process. The developer has the right to delete the issue directly if you check the list blindly.
|
||||
options:
|
||||
- label: This is not a plugin request.
|
||||
required: true
|
||||
- label: I've checked that there is no other issue about this feature request.
|
||||
required: true
|
||||
- label: This issue contains only one feature request.
|
||||
required: true
|
||||
- label: The title of this issue accurately describes the feature request.
|
||||
required: true
|
||||
value: |
|
||||
# Revenge feature request
|
||||
|
||||
Before creating a new feature request, please keep the following in mind:
|
||||
|
||||
- **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/revenge-mod/revenge-bundle/issues?q=label%3A%22Feature+request%22).
|
||||
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/revenge-mod/revenge-bundle/blob/main/CONTRIBUTING.md).
|
||||
- **Do not use the issue page for support**: If you need help or have questions, join us on [Discord](https://discord.gg/ddcQf3s2Uq).
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
attributes:
|
||||
label: Feature description
|
||||
description: What feature you want Kettu to have? Provide detailed description about what it should look like or where it should be added.
|
||||
|
|
@ -27,17 +21,26 @@ body:
|
|||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: why-is-the-feature-requested
|
||||
attributes:
|
||||
label: Why do you want this feature?
|
||||
description: Describe the problem or limitation that motivates you to want this feature to be added.
|
||||
label: Motivation
|
||||
description: |
|
||||
A strong motivation is necessary for a feature request to be considered.
|
||||
|
||||
- Why should this feature be implemented?
|
||||
- What is the explicit use case?
|
||||
- What are the benefits?
|
||||
- What makes this feature important?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
placeholder: |
|
||||
Additional details and attachments.
|
||||
label: Acknowledgements
|
||||
description: Your feature request will be closed if you don't follow the checklist below.
|
||||
options:
|
||||
- label: I have checked all open and closed feature requests and this is not a duplicate
|
||||
required: true
|
||||
- label: I have chosen an appropriate title.
|
||||
required: true
|
||||
- label: All requested information has been provided properly.
|
||||
required: true
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@
|
|||
},
|
||||
"license": "BSD-3-Clause",
|
||||
"devDependencies": {
|
||||
"@saithodev/semantic-release-backmerge": "^4.0.1",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/exec": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"semantic-release": "^24.1.3",
|
||||
"@swc/core": "^1.7.0",
|
||||
"@swc/helpers": "^0.5.12",
|
||||
"@types/node": "^20.14.11",
|
||||
|
|
|
|||
1387
pnpm-lock.yaml
generated
1387
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -20,7 +20,8 @@ const metroDeps = await (async () => {
|
|||
const args = yargs(process.argv.slice(2));
|
||||
const {
|
||||
"release-branch": releaseBranch,
|
||||
"build-minify": buildMinify
|
||||
"build-minify": buildMinify,
|
||||
"dev": dev
|
||||
} = args;
|
||||
|
||||
let context = null;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export default () => <ApplicationCommand>{
|
|||
const info = getDebugInfo();
|
||||
const content = [
|
||||
"**Kettu Debug Info**",
|
||||
`> Kettu: ${info.pupu.version} (${info.pupu.loader.name} ${info.pupu.loader.version})`,
|
||||
`> Kettu: ${info.bunny.version} (${info.bunny.loader.name} ${info.bunny.loader.version})`,
|
||||
`> Discord: ${info.discord.version} (${info.discord.build})`,
|
||||
`> React: ${info.react.version} (RN ${info.react.nativeVersion})`,
|
||||
`> Hermes: ${info.hermes.version} (bcv${info.hermes.bytecodeVersion})`,
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@
|
|||
"CLEAR": "Clear",
|
||||
"CLEAR_BUNDLE": "Clear JS Bundle",
|
||||
"CLEAR_BUNDLE_DESC": "Clear the cached bundle. This will force a re-download of the bundle next app launch.",
|
||||
"CLEAR_BUNDLE": "Clear JS Bundle",
|
||||
"CLEAR_BUNDLE_DESC": "Clear the cached bundle. This will force a re-download of the bundle next app launch.",
|
||||
"CLEAR_DATA": "Clear data",
|
||||
"CLEAR_DATA_FAILED": "Failed to clear data for {name}!",
|
||||
"CLEAR_DATA_SUCCESSFUL": "Cleared data for {name}.",
|
||||
"CLEAR_DATA_FAILED": "Failed to clear data for {name}",
|
||||
"CLEAR_DATA_SUCCESSFUL": "Cleared data for {name}",
|
||||
"CODENAME": "Codename",
|
||||
"COMMAND_DEBUG_DESC": "Send Kettu debug info.",
|
||||
"COMMAND_DEBUG_OPT_EPHEMERALLY": "Send debug info ephemerally.",
|
||||
|
|
@ -26,7 +28,7 @@
|
|||
"COMMAND_PLUGINS_OPT_EPHEMERALLY": "Send plugins list ephemerally.",
|
||||
"COMPONENT": "Component",
|
||||
"CONFIRMATION_LINK_IS_A_TYPE": "This link is a **{urlType, select, plugin {Plugin} theme {Theme} other {Add-on}}**, would you like to install it?",
|
||||
"CONNECT_TO_DEBUG_WEBSOCKET": "Connect to debug websocket",
|
||||
"CONNECT_TO_DEBUG_WEBSOCKET": "Connect to debug WebSocket",
|
||||
"CONNECT_TO_REACT_DEVTOOLS": "Connect to React DevTools",
|
||||
"CONTINUE": "Continue",
|
||||
"COPIED_TO_CLIPBOARD": "Copied to clipboard",
|
||||
|
|
@ -38,12 +40,12 @@
|
|||
"DEVELOPER": "Developer",
|
||||
"DEVELOPER_SETTINGS": "Developer Settings",
|
||||
"DISABLE_THEME": "Disable Theme",
|
||||
"DISABLE_UPDATES": "Disable updates",
|
||||
"DISABLE_UPDATES": "Disable Updates",
|
||||
"DISCORD_SERVER": "Discord Server",
|
||||
"DONE": "Done",
|
||||
"ENABLE_EVAL_COMMAND": "Enable /eval command",
|
||||
"ENABLE_EVAL_COMMAND_DESC": "Evaluate JavaScript directly from command. Be cautious when using this command as it may pose a security risk. Make sure to know what you are doing.",
|
||||
"ENABLE_UPDATES": "Enable updates",
|
||||
"ENABLE_EVAL_COMMAND_DESC": "Evaluate JavaScript directly from a command. Be cautious when using this command as it may pose a security risk. Make sure to know what you are doing.",
|
||||
"ENABLE_UPDATES": "Enable Updates",
|
||||
"ERROR_BOUNDARY_TOOLS_LABEL": "ErrorBoundary Tools",
|
||||
"EXTRACT": "Extract",
|
||||
"FONT_NAME": "Font Name",
|
||||
|
|
@ -57,7 +59,7 @@
|
|||
"INSTALL_FONT": "Install a font",
|
||||
"INSTALL_PLUGIN": "Install a plugin",
|
||||
"INSTALL_REACT_DEVTOOLS": "Install React DevTools",
|
||||
"INSTALL_THEME": "Install Theme",
|
||||
"INSTALL_THEME": "Install a theme",
|
||||
"LABEL_EXTRACT_FONTS_FROM_THEME": "Extract font from theme",
|
||||
"LINKS": "Links",
|
||||
"LOAD_FROM_CUSTOM_URL": "Load from custom URL",
|
||||
|
|
@ -70,33 +72,35 @@
|
|||
"MISCELLANEOUS": "Miscellaneous",
|
||||
"MODAL_RELOAD_REQUIRED": "Reload app?",
|
||||
"MODAL_RELOAD_REQUIRED_DESC": "A reload is required to see the changes. Do you want to reload now?",
|
||||
"MODAL_RELOAD_REQUIRED": "Reload app?",
|
||||
"MODAL_RELOAD_REQUIRED_DESC": "A reload is required to see the changes. Do you want to reload now?",
|
||||
"MODAL_THEME_REFETCHED": "Theme refetched",
|
||||
"MODAL_THEME_REFETCHED_DESC": "A reload is required to see the changes. Do you want to reload now?",
|
||||
"MODAL_UNPROXIED_PLUGIN_DESC": "The plugin you are trying to install has not been proxied/verified by staffs. Are you sure you want to continue?",
|
||||
"MODAL_UNPROXIED_PLUGIN_DESC": "The plugin you are trying to install has not been proxied/verified by staff. Are you sure you want to continue?",
|
||||
"MODAL_UNPROXIED_PLUGIN_HEADER": "Unproxied Plugin",
|
||||
"MODEL": "Model",
|
||||
"OPEN_IN_BROWSER": "Open in Browser",
|
||||
"OPERATING_SYSTEM": "Operating System",
|
||||
"OVERFLOW_PLUGIN_SETTINGS": "Plugin settings",
|
||||
"OVERFLOW_PLUGIN_SETTINGS": "Plugin Settings",
|
||||
"PLATFORM": "Platform",
|
||||
"PLUGIN_REFETCH_FAILED": "Failed to refetch plugin!",
|
||||
"PLUGIN_REFETCH_SUCCESSFUL": "Successfully refetched plugin!",
|
||||
"PLUGIN_REFETCH_FAILED": "Failed to refetch plugin",
|
||||
"PLUGIN_REFETCH_SUCCESSFUL": "Successfully refetched plugin",
|
||||
"PLUGINS": "Plugins",
|
||||
"REFETCH": "Refetch",
|
||||
"RELOAD": "Reload",
|
||||
"RELOAD_DISCORD": "Reload Discord",
|
||||
"RELOAD_IN_NORMAL_MODE": "Reload in Normal Mode",
|
||||
"RELOAD_IN_NORMAL_MODE_DESC": "This will reload Discord normally",
|
||||
"RELOAD_IN_NORMAL_MODE_DESC": "Safe mode currently enabled, tap to reload in normal mode",
|
||||
"RELOAD_IN_SAFE_MODE": "Reload in Safe Mode",
|
||||
"RELOAD_IN_SAFE_MODE_DESC": "This will reload Discord without loading addons",
|
||||
"RELOAD_IN_SAFE_MODE_DESC": "Tap to reload Discord without loading addons",
|
||||
"REMOVE": "Remove",
|
||||
"RESTART_REQUIRED_TO_TAKE_EFFECT": "Restart is required to take effect",
|
||||
"RETRY": "Retry",
|
||||
"RETRY_RENDER": "Retry Render",
|
||||
"SAFE_MODE": "Safe Mode",
|
||||
"SAFE_MODE_NOTICE_FONTS": "You are in Safe Mode, meaning fonts have been temporarily disabled. {enabled, select, true {If a font appears to be causing the issue, you can press below to disable it persistently.} other {}}",
|
||||
"SAFE_MODE_NOTICE_PLUGINS": "You are in Safe Mode, so plugins cannot be loaded. Disable any misbehaving plugins, then return to Normal Mode from the General settings page.",
|
||||
"SAFE_MODE_NOTICE_THEMES": "You are in Safe Mode, meaning themes have been temporarily disabled. {enabled, select, true {If a theme appears to be causing the issue, you can press below to disable it persistently.} other {}}",
|
||||
"SAFE_MODE_NOTICE_FONTS": "You are in safe mode, meaning fonts have been temporarily disabled. {enabled, select, true {If a font appears to be causing the issue, you can press below to disable it persistently.} other {}}",
|
||||
"SAFE_MODE_NOTICE_PLUGINS": "You are in safe mode, so plugins cannot be loaded. Disable any misbehaving plugins, then return to Normal Mode from the General settings page.",
|
||||
"SAFE_MODE_NOTICE_THEMES": "You are in safe mode, meaning themes have been temporarily disabled. {enabled, select, true {If a theme appears to be causing the issue, you can press below to disable it persistently.} other {}}",
|
||||
"SEARCH": "Search",
|
||||
"SEPARATOR": ", ",
|
||||
"SETTINGS_ACTIVATE_DISCORD_EXPERIMENTS": "Activate Discord Experiments",
|
||||
|
|
@ -104,16 +108,16 @@
|
|||
"STACK_TRACE": "Stack Trace",
|
||||
"SUCCESSFULLY_INSTALLED": "Successfully installed",
|
||||
"THEME_EXTRACTOR_DESC": "This pack overrides the following: {fonts}",
|
||||
"THEME_REFETCH_FAILED": "Failed to refetch theme!",
|
||||
"THEME_REFETCH_SUCCESSFUL": "Successfully refetched theme.",
|
||||
"THEME_REFETCH_FAILED": "Failed to refetch theme",
|
||||
"THEME_REFETCH_SUCCESSFUL": "Successfully refetched theme",
|
||||
"THEMES": "Themes",
|
||||
"THEMES_RELOAD_FOR_CHANGES": "Reload the app to fully apply changes!",
|
||||
"THEMES_RELOAD_FOR_CHANGES": "Reload the app to fully apply changes",
|
||||
"TOASTS_INSTALLED_PLUGIN": "Installed plugin",
|
||||
"TOASTS_PLUGIN_UPDATE": "{update, select, true {Enabled} other {Disabled}} updates for {name}.",
|
||||
"UH_OH": "Uh oh.",
|
||||
"TOASTS_PLUGIN_UPDATE": "{update, select, true {Enabled} other {Disabled}} updates for {name}",
|
||||
"UH_OH": "Uh Oh",
|
||||
"UNINSTALL": "Uninstall",
|
||||
"UNINSTALL_TITLE": "Uninstall {title}",
|
||||
"URL_PLACEHOLDER": "https://example.com",
|
||||
"URL_PLACEHOLDER": "https://github.com/revenge-mod",
|
||||
"VERSION": "Version",
|
||||
"VERSIONS": "Versions"
|
||||
}
|
||||
|
|
@ -6,13 +6,16 @@ import { findAssetId } from "@lib/api/assets";
|
|||
import { isThemeSupported } from "@lib/api/native/loader";
|
||||
import { after, instead } from "@lib/api/patcher";
|
||||
import { VD_PROXY_PREFIX, VD_THEMES_CHANNEL_ID } from "@lib/utils/constants";
|
||||
import { VD_PROXY_PREFIX, VD_THEMES_CHANNEL_ID } from "@lib/utils/constants";
|
||||
import { lazyDestructure } from "@lib/utils/lazy";
|
||||
import { channels } from "@metro/common";
|
||||
import { channels } from "@metro/common";
|
||||
import { byMutableProp } from "@metro/filters";
|
||||
import { findExports } from "@metro/finders";
|
||||
import { findByProps, findByPropsLazy } from "@metro/wrappers";
|
||||
import { showToast } from "@ui/toasts";
|
||||
import { Linking } from "react-native";
|
||||
import { Linking } from "react-native";
|
||||
|
||||
const showSimpleActionSheet = findExports(byMutableProp("showSimpleActionSheet"));
|
||||
const handleClick = findByPropsLazy("handleClick");
|
||||
|
|
@ -75,7 +78,7 @@ export default () => {
|
|||
confirmText: Strings.INSTALL,
|
||||
cancelText: Strings.CANCEL,
|
||||
secondaryConfirmText: Strings.OPEN_IN_BROWSER,
|
||||
onConfirmSecondary: () => Linking.openURL(url),
|
||||
onConfirmSecondary: () => Linking.openURL(url),
|
||||
});
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { settings } from "@lib/api/settings";
|
|||
import { dismissAlert, openAlert } from "@lib/ui/alerts";
|
||||
import { showSheet } from "@lib/ui/sheets";
|
||||
import isValidHttpUrl from "@lib/utils/isValidHttpUrl";
|
||||
import { lazyDestructure } from "@lib/utils/lazy";
|
||||
import { lazyDestructure, proxyLazy } from "@lib/utils/lazy";
|
||||
import { findByProps } from "@metro";
|
||||
import { clipboard, NavigationNative } from "@metro/common";
|
||||
import { AlertActionButton, AlertModal, Button, FlashList, FloatingActionButton, HelpMessage, IconButton, Stack, Text, TextInput, useSafeAreaInsets } from "@metro/common/components";
|
||||
|
|
|
|||
|
|
@ -17,12 +17,10 @@ const useStyles = createStyles({
|
|||
backgroundColor: tokens.colors.BG_BASE_SECONDARY,
|
||||
paddingHorizontal: 16,
|
||||
height: "100%",
|
||||
padding: 8,
|
||||
gap: 12
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default function ErrorBoundaryScreen(props: {
|
||||
error: Error;
|
||||
rerender: () => void;
|
||||
|
|
@ -31,12 +29,11 @@ export default function ErrorBoundaryScreen(props: {
|
|||
const debugInfo = getDebugInfo();
|
||||
|
||||
return <ErrorBoundary>
|
||||
{/* for iOS */}
|
||||
<SafeAreaProvider>
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={{ gap: 4 }}>
|
||||
<Text variant="display-lg">Uh oh.</Text>
|
||||
<Text variant="text-md/normal">A crash occurred while rendering a component. This could be caused by a plugin, Bunny or Discord itself.</Text>
|
||||
<Text variant="text-md/normal">A crash occurred while rendering a component. This could be caused by a plugin, Revenge, or Discord itself.</Text>
|
||||
<Text variant="text-sm/normal" color="text-muted">{debugInfo.os.name}; {debugInfo.discord.build} ({debugInfo.discord.version}); {debugInfo.bunny.version}</Text>
|
||||
</View>
|
||||
<ScrollView fadingEdgeLength={64} contentContainerStyle={{ gap: 12 }}>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { version } from "bunny-build-info";
|
|||
export { PupuIcon };
|
||||
|
||||
export default function initSettings() {
|
||||
|
||||
registerSection({
|
||||
name: "Kettu",
|
||||
items: [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import AssetDisplay from "@core/ui/settings/pages/Developer/AssetDisplay";
|
||||
import { iterateAssets } from "@lib/api/assets";
|
||||
import { LegacyFormDivider } from "@metro/common/components";
|
||||
import { Text } from "@metro/common/components";
|
||||
import { ErrorBoundary, Search } from "@ui/components";
|
||||
import { useMemo } from "react";
|
||||
import { FlatList, View } from "react-native";
|
||||
|
|
@ -16,12 +16,15 @@ export default function AssetBrowser() {
|
|||
style={{ margin: 10 }}
|
||||
onChangeText={(v: string) => setSearch(v)}
|
||||
/>
|
||||
<FlatList
|
||||
data={all.filter(a => a.name.includes(search) || a.id.toString() === search)}
|
||||
renderItem={({ item }) => <AssetDisplay asset={item} />}
|
||||
ItemSeparatorComponent={LegacyFormDivider}
|
||||
keyExtractor={item => item.name}
|
||||
/>
|
||||
<View style={{ flex: 1, borderRadius: 16, paddingHorizontal: 12, overflow: 'hidden', backgroundColor: 'transparent' }}>
|
||||
<Text variant='text-sm/medium' color='text-danger' style={{ marginBottom: 16 }}>Some assets types cannot be displayed and will be marked in red.</Text>
|
||||
<FlatList
|
||||
data={all.filter(a => a.name.includes(search) || a.id.toString() === search)}
|
||||
renderItem={({ item }: any) => <AssetDisplay asset={item} />}
|
||||
contentContainerStyle={{ overflow: 'hidden', backgroundColor: 'transparent', borderRadius: 16 }}
|
||||
keyExtractor={a => a.name}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,20 +1,65 @@
|
|||
import { Asset } from "@lib/api/assets";
|
||||
import { Asset, findAssetId } from "@lib/api/assets";
|
||||
import { lazyDestructure } from "@lib/utils/lazy";
|
||||
import { findByProps } from "@metro";
|
||||
import { clipboard } from "@metro/common";
|
||||
import { LegacyFormRow } from "@metro/common/components";
|
||||
import { Stack, TableRow, Text } from "@metro/common/components";
|
||||
import { showToast } from "@ui/toasts";
|
||||
import { Image } from "react-native";
|
||||
|
||||
const { openAlert } = lazyDestructure(() => findByProps("openAlert", "dismissAlert"));
|
||||
const { AlertModal, AlertActionButton } = lazyDestructure(() => findByProps("AlertModal", "AlertActions"));
|
||||
|
||||
const displayable = new Set(['png', 'jpg', 'svg']);
|
||||
|
||||
const iconMap = {
|
||||
jsona: 'ic_file_text',
|
||||
lottie: 'ic_image',
|
||||
webm: 'CirclePlayIcon-primary',
|
||||
ttf: 'ic_add_text',
|
||||
default: 'UnknownGameIcon'
|
||||
};
|
||||
|
||||
interface AssetDisplayProps { asset: Asset; }
|
||||
|
||||
export default function AssetDisplay({ asset }: AssetDisplayProps) {
|
||||
|
||||
return (
|
||||
<LegacyFormRow
|
||||
label={`${asset.name} - ${asset.id}`}
|
||||
trailing={<Image source={asset.id} style={{ width: 32, height: 32 }} />}
|
||||
onPress={() => {
|
||||
clipboard.setString(asset.name);
|
||||
showToast.showCopyToClipboard();
|
||||
}}
|
||||
<TableRow
|
||||
variant={displayable.has(asset.type) ? 'default' : 'danger'}
|
||||
label={asset.name}
|
||||
subLabel={`Index: ${asset.id} Type: ${asset.type}`}
|
||||
icon={
|
||||
displayable.has(asset.type)
|
||||
? <Image source={asset.id} style={{ width: 32, height: 32 }} />
|
||||
: <TableRow.Icon
|
||||
variant='danger'
|
||||
source={findAssetId(asset.type in iconMap ? iconMap[asset.type as keyof typeof iconMap] : iconMap.default)}
|
||||
/>
|
||||
}
|
||||
onPress={() =>
|
||||
openAlert("revenge-asset-display-details", <AlertModal
|
||||
title={asset.name}
|
||||
content={`Index: ${asset.id}\nModule ID: ${asset.moduleId}\nType: ${asset.type}`}
|
||||
extraContent={
|
||||
displayable.has(asset.type)
|
||||
? <Image resizeMode="contain" source={asset.id} style={{ flex: 1, width: 'auto', height: 192 }} />
|
||||
: (<Text variant='text-sm/medium' color="text-danger" style={{ width: '100%', textAlign: 'center' }}>
|
||||
Asset type {asset.type.toUpperCase()} is not supported for preview.
|
||||
</Text>)
|
||||
}
|
||||
actions={
|
||||
<Stack>
|
||||
<AlertActionButton text="Copy asset name" variant="primary" onPress={() => copyToClipboard(asset.name)} />
|
||||
<AlertActionButton text="Copy asset index" variant="secondary" onPress={() => copyToClipboard(asset.id.toString())} />
|
||||
</Stack>
|
||||
}
|
||||
/>)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
clipboard.setString(text);
|
||||
showToast.showCopyToClipboard();
|
||||
};
|
||||
|
|
@ -13,15 +13,15 @@ import { findByProps } from "@metro/wrappers";
|
|||
import { semanticColors } from "@ui/color";
|
||||
import { ErrorBoundary } from "@ui/components";
|
||||
import { createStyles, TextStyleSheet } from "@ui/styles";
|
||||
import { ScrollView, StyleSheet } from "react-native";
|
||||
import { NativeModules } from "react-native";
|
||||
import { ScrollView, StyleSheet } from "react-native";
|
||||
|
||||
const { hideActionSheet } = lazyDestructure(() => findByProps("openLazy", "hideActionSheet"));
|
||||
const { showSimpleActionSheet } = lazyDestructure(() => findByProps("showSimpleActionSheet"));
|
||||
const { AlertModal, AlertActionButton } = lazyDestructure(() => findByProps("AlertModal", "AlertActions"));
|
||||
const { openAlert } = lazyDestructure(() => findByProps("openAlert", "dismissAlert"));
|
||||
const { AlertModal, AlertActionButton } = lazyDestructure(() => findByProps("AlertModal", "AlertActions"));
|
||||
|
||||
const RDT_EMBED_LINK = "https://raw.githubusercontent.com/amsyarasyiq/rdt-embedder/main/dist.js";
|
||||
const RDT_EMBED_LINK = "https://github.com/revenge-mod/react-devtools-core/releases/latest/download/rdtc.js";
|
||||
|
||||
const useStyles = createStyles({
|
||||
leadingText: {
|
||||
|
|
@ -173,7 +173,7 @@ export default function Developer() {
|
|||
label={Strings.ENABLE_EVAL_COMMAND}
|
||||
subLabel={Strings.ENABLE_EVAL_COMMAND_DESC}
|
||||
icon={<TableRow.Icon source={findAssetId("PencilIcon")} />}
|
||||
value={settings.enableEvalCommand}
|
||||
value={!!settings.enableEvalCommand}
|
||||
onValueChange={(v: boolean) => {
|
||||
settings.enableEvalCommand = v;
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ export default function FontCard({ item: font }: CardWrapper<FontDefinition>) {
|
|||
size="sm"
|
||||
variant="secondary"
|
||||
disabled={selected}
|
||||
icon={findAssetId("PencilIcon")}
|
||||
icon={findAssetId("WrenchIcon")}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
|
|
|
|||
|
|
@ -1,17 +1,37 @@
|
|||
import { formatString, Strings } from "@core/i18n";
|
||||
import { createProxy, useProxy } from "@core/vendetta/storage";
|
||||
import { FontDefinition, fonts, removeFont, saveFont, validateFont } from "@lib/addons/fonts";
|
||||
import { FontDefinition, fonts, removeFont, saveFont, updateFont, validateFont } from "@lib/addons/fonts";
|
||||
import { getCurrentTheme } from "@lib/addons/themes";
|
||||
import { findAssetId } from "@lib/api/assets";
|
||||
import { safeFetch } from "@lib/utils";
|
||||
import { lazyDestructure } from "@lib/utils/lazy";
|
||||
import { NavigationNative } from "@metro/common";
|
||||
import { ActionSheet, BottomSheetTitleHeader, Button, IconButton, Stack, TableRow, TableRowGroup, Text, TextInput } from "@metro/common/components";
|
||||
import { findByPropsLazy } from "@metro/wrappers";
|
||||
import { findByProps, findByPropsLazy } from "@metro/wrappers";
|
||||
import { ErrorBoundary } from "@ui/components";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { ScrollView, View } from "react-native";
|
||||
|
||||
const actionSheet = findByPropsLazy("hideActionSheet");
|
||||
const { openAlert } = lazyDestructure(() => findByProps("openAlert", "dismissAlert"));
|
||||
const { AlertModal, AlertActionButton } = lazyDestructure(() => findByProps("AlertModal", "AlertActions"));
|
||||
|
||||
function promptDetachConfirmationForThen(fontName: string | undefined, cb: () => void) {
|
||||
if (fontName && fonts[fontName].source) openAlert("revenge-fonts-detach-source-confirmation", <AlertModal
|
||||
title="Detach font pack URL?"
|
||||
content="You need to detach the font pack URL from this font pack before you can manually edit its font entries. Do you want to detach the font pack URL?"
|
||||
actions={
|
||||
<Stack>
|
||||
<AlertActionButton text="Detach" variant="destructive" onPress={() => {
|
||||
delete fonts[fontName!].source;
|
||||
cb();
|
||||
}} />
|
||||
<AlertActionButton text={Strings.CANCEL} variant="secondary" />
|
||||
</Stack>
|
||||
}
|
||||
/>);
|
||||
else cb();
|
||||
};
|
||||
|
||||
function guessFontName(urls: string[]) {
|
||||
const fileNames = urls.map(url => {
|
||||
|
|
@ -121,7 +141,6 @@ function JsonFontImporter({ fonts, setName, setSource }: {
|
|||
.then(() => actionSheet.hideActionSheet())
|
||||
.catch(e => setError(String(e)))
|
||||
.finally(() => setSaving(false));
|
||||
|
||||
}}
|
||||
/>
|
||||
</View>;
|
||||
|
|
@ -130,6 +149,7 @@ function JsonFontImporter({ fonts, setName, setSource }: {
|
|||
function EntryEditorActionSheet(props: {
|
||||
fontEntries: Record<string, string>;
|
||||
name: string;
|
||||
onChange: () => void;
|
||||
}) {
|
||||
const [familyName, setFamilyName] = useState<string>(props.name);
|
||||
const [fontUrl, setFontUrl] = useState<string>(props.fontEntries[props.name]);
|
||||
|
|
@ -157,6 +177,8 @@ function EntryEditorActionSheet(props: {
|
|||
onPress={() => {
|
||||
delete props.fontEntries[props.name];
|
||||
props.fontEntries[familyName] = fontUrl;
|
||||
props.onChange();
|
||||
actionSheet.hideActionSheet();
|
||||
}}
|
||||
/>
|
||||
</View>;
|
||||
|
|
@ -182,7 +204,7 @@ function promptActionSheet(
|
|||
);
|
||||
}
|
||||
|
||||
function NewEntryRow({ fontEntry }: { fontEntry: Record<string, string>; }) {
|
||||
function NewEntryRow({ fontName, fontEntry }: { fontName: string | undefined, fontEntry: Record<string, string>; }) {
|
||||
const nameRef = useRef<string>();
|
||||
const urlRef = useRef<string>();
|
||||
|
||||
|
|
@ -215,7 +237,7 @@ function NewEntryRow({ fontEntry }: { fontEntry: Record<string, string>; }) {
|
|||
<IconButton
|
||||
size="md"
|
||||
variant="primary"
|
||||
onPress={() => {
|
||||
onPress={() => promptDetachConfirmationForThen(fontName, () => {
|
||||
if (!nameSet && nameRef.current) {
|
||||
setNameSet(true);
|
||||
} else if (nameSet && nameRef.current && urlRef.current) {
|
||||
|
|
@ -233,7 +255,7 @@ function NewEntryRow({ fontEntry }: { fontEntry: Record<string, string>; }) {
|
|||
setError(String(e));
|
||||
}
|
||||
}
|
||||
}}
|
||||
})}
|
||||
icon={findAssetId(nameSet ? "PlusSmallIcon" : "ArrowLargeRightIcon")}
|
||||
/>
|
||||
</View>;
|
||||
|
|
@ -243,8 +265,9 @@ export default function FontEditor(props: {
|
|||
name?: string;
|
||||
}) {
|
||||
const [name, setName] = useState<string | undefined>(props.name);
|
||||
const [source, setSource] = useState<string>();
|
||||
const [source, setSource] = useState<string | undefined>(props.name && fonts[props.name].source);
|
||||
const [importing, setIsImporting] = useState<boolean>(false);
|
||||
const [errors, setErrors] = useState<Array<Error | undefined> | undefined>();
|
||||
|
||||
const memoEntry = useMemo(() => {
|
||||
return createProxy(props.name ? { ...fonts[props.name].main } : {}).proxy;
|
||||
|
|
@ -254,6 +277,8 @@ export default function FontEditor(props: {
|
|||
|
||||
const navigation = NavigationNative.useNavigation();
|
||||
|
||||
const [, forceUpdate] = React.useReducer(() => ({}), 0);
|
||||
|
||||
return <ScrollView style={{ flex: 1 }} contentContainerStyle={{ paddingBottom: 38 }}>
|
||||
<Stack style={{ paddingVertical: 24, paddingHorizontal: 12 }} spacing={12}>
|
||||
{!props.name
|
||||
|
|
@ -277,15 +302,14 @@ export default function FontEditor(props: {
|
|||
label={"Refetch fonts from source"}
|
||||
icon={<TableRow.Icon source={findAssetId("RetryIcon")} />}
|
||||
onPress={async () => {
|
||||
const ftCopy = { ...fonts[props.name!] };
|
||||
await removeFont(props.name!);
|
||||
await saveFont(ftCopy);
|
||||
await updateFont(fonts[props.name!]);
|
||||
navigation.goBack();
|
||||
}}
|
||||
/>
|
||||
<TableRow
|
||||
variant="danger"
|
||||
label={"Delete font pack"}
|
||||
icon={<TableRow.Icon source={findAssetId("TrashIcon")} />}
|
||||
icon={<TableRow.Icon variant="danger" source={findAssetId("TrashIcon")} />}
|
||||
onPress={() => removeFont(props.name!).then(() => navigation.goBack())}
|
||||
/>
|
||||
</TableRowGroup>}
|
||||
|
|
@ -296,32 +320,50 @@ export default function FontEditor(props: {
|
|||
placeholder={"Whitney"}
|
||||
onChange={setName}
|
||||
/>
|
||||
{props.name && fonts[props.name].source && <TextInput
|
||||
size="lg"
|
||||
value={source}
|
||||
label="Font Pack URL"
|
||||
onChange={setSource}
|
||||
/>}
|
||||
<TableRowGroup title="Font Entries">
|
||||
{Object.entries(fontEntries).map(([name, url]) => {
|
||||
{Object.entries(fontEntries).map(([name, url], index) => {
|
||||
const error = errors?.[index];
|
||||
|
||||
return <TableRow
|
||||
label={name}
|
||||
subLabel={url}
|
||||
trailing={<Stack spacing={2} direction="horizontal">
|
||||
subLabel={error ? <Text variant='text-xs/medium' color='text-danger'>{error.message}</Text> : url}
|
||||
trailing={<Stack spacing={8} direction="horizontal">
|
||||
<IconButton
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon={findAssetId("PencilIcon")}
|
||||
onPress={() => promptActionSheet(EntryEditorActionSheet, fontEntries, {
|
||||
name,
|
||||
fontEntries,
|
||||
})}
|
||||
onPress={() => promptDetachConfirmationForThen(props.name, () =>
|
||||
promptActionSheet(EntryEditorActionSheet, fontEntries, {
|
||||
name,
|
||||
fontEntries,
|
||||
onChange: () => {
|
||||
setErrors(undefined);
|
||||
forceUpdate();
|
||||
}
|
||||
})
|
||||
)}
|
||||
/>
|
||||
<IconButton
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon={findAssetId("TrashIcon")}
|
||||
onPress={() => delete fontEntries[name]}
|
||||
onPress={() => promptDetachConfirmationForThen(props.name, () => {
|
||||
delete fontEntries[name];
|
||||
setErrors(undefined);
|
||||
})}
|
||||
/>
|
||||
</Stack>}
|
||||
/>;
|
||||
})}
|
||||
<TableRow label={<NewEntryRow fontEntry={fontEntries} />} />
|
||||
<TableRow label={<NewEntryRow fontName={props.name} fontEntry={fontEntries} />} />
|
||||
</TableRowGroup>
|
||||
{errors && <Text variant='text-sm/medium' color='text-danger'>Some font entries cannot be imported. Please modify the entries and try again.</Text>}
|
||||
<View style={{ flexDirection: "row", justifyContent: "flex-end", bottom: 0, left: 0 }}>
|
||||
<Button
|
||||
size="lg"
|
||||
|
|
@ -332,6 +374,7 @@ export default function FontEditor(props: {
|
|||
onPress={async () => {
|
||||
if (!name) return;
|
||||
|
||||
setErrors(undefined);
|
||||
setIsImporting(true);
|
||||
|
||||
if (!props.name) {
|
||||
|
|
@ -339,18 +382,21 @@ export default function FontEditor(props: {
|
|||
spec: 1,
|
||||
name: name,
|
||||
main: fontEntries,
|
||||
__source: source
|
||||
source
|
||||
})
|
||||
.then(() => navigation.goBack())
|
||||
.catch(e => setErrors(e))
|
||||
.finally(() => setIsImporting(false));
|
||||
} else {
|
||||
Object.assign(fonts[props.name], {
|
||||
name: name,
|
||||
main: fontEntries,
|
||||
__edited: true
|
||||
});
|
||||
setIsImporting(false);
|
||||
navigation.goBack();
|
||||
|
||||
updateFont(fonts[props.name])
|
||||
.then(() => navigation.goBack())
|
||||
.catch(e => setErrors(e))
|
||||
.finally(() => setIsImporting(false));
|
||||
}
|
||||
}}
|
||||
icon={findAssetId(props.name ? "toast_image_saved" : "DownloadIcon")}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { findAssetId } from "@lib/api/assets";
|
||||
import { clipboard } from "@metro/common";
|
||||
import { LegacyFormText, TableRow } from "@metro/common/components";
|
||||
import { TableRow, TableRowTrailingText } from "@metro/common/components";
|
||||
import { showToast } from "@ui/toasts";
|
||||
import { ImageURISource } from "react-native";
|
||||
|
||||
|
|
@ -14,8 +14,8 @@ export default function Version({ label, version, icon }: VersionProps) {
|
|||
return (
|
||||
<TableRow
|
||||
label={label}
|
||||
trailing={<TableRowTrailingText text={version} />}
|
||||
icon={<TableRow.Icon source={typeof icon === "string" ? findAssetId(icon) : icon} />}
|
||||
trailing={<LegacyFormText>{version}</LegacyFormText>}
|
||||
onPress={() => {
|
||||
clipboard.setString(`${label} - ${version}`);
|
||||
showToast.showCopyToClipboard();
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ import { settings } from "@lib/api/settings";
|
|||
import { openAlert } from "@lib/ui/alerts";
|
||||
import { DISCORD_SERVER, GITHUB } from "@lib/utils/constants";
|
||||
import { NavigationNative } from "@metro/common";
|
||||
import { NavigationNative } from "@metro/common";
|
||||
import { AlertActionButton, AlertActions, AlertModal, Stack, TableRow, TableRowGroup, TableSwitchRow } from "@metro/common/components";
|
||||
import { Linking, ScrollView } from "react-native";
|
||||
import { Linking, ScrollView } from "react-native";
|
||||
|
||||
export default function General() {
|
||||
useProxy(settings);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { UnifiedPluginModel } from "..";
|
||||
import { UnifiedPluginModel } from "../models";
|
||||
|
||||
export interface PluginInfoActionSheetProps {
|
||||
plugin: UnifiedPluginModel;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export async function createVdPluginObject(plugin: VendettaPlugin) {
|
|||
// Wrapping this with wrapSync is NOT an option.
|
||||
storage: await createStorage<Record<string, any>>(storage.createMMKVBackend(plugin.id)),
|
||||
},
|
||||
logger: new LoggerClass(`Bunny » ${plugin.manifest.name}`),
|
||||
logger: new LoggerClass(`Revenge » ${plugin.manifest.name}`),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ export const VdPluginManager = {
|
|||
// Wrapping this with wrapSync is NOT an option.
|
||||
storage: await createStorage<Record<string, any>>(createMMKVBackend(plugin.id)),
|
||||
},
|
||||
logger: new LoggerClass(`Bunny » ${plugin.manifest.name}`),
|
||||
logger: new LoggerClass(`Revenge » ${plugin.manifest.name}`),
|
||||
};
|
||||
const pluginString = `vendetta=>{return ${plugin.js}}\n//# sourceURL=${plugin.id}`;
|
||||
|
||||
|
|
|
|||
|
|
@ -66,11 +66,7 @@ if (typeof globalThis.__r !== "undefined") {
|
|||
_requireFunc = function patchedRequire(a: number) {
|
||||
// Initializing index.ts(x)
|
||||
if (a === 0) {
|
||||
// https://github.com/facebook/metro/commit/1361405ffe6f1bdef54295addfef0f30523aaab2
|
||||
if (window.modules instanceof Map) {
|
||||
window.modules = Object.fromEntries([...window.modules]);
|
||||
}
|
||||
|
||||
if (window.modules instanceof Map) window.modules = Object.fromEntries(window.modules);
|
||||
onceIndexRequired(v);
|
||||
_requireFunc = v;
|
||||
} else return v(a);
|
||||
|
|
|
|||
|
|
@ -4,13 +4,12 @@ import { safeFetch } from "@lib/utils";
|
|||
|
||||
type FontMap = Record<string, string>;
|
||||
|
||||
export interface FontDefinition {
|
||||
export type FontDefinition = {
|
||||
spec: 1;
|
||||
name: string;
|
||||
description?: string;
|
||||
main: FontMap;
|
||||
__source?: string;
|
||||
__edited?: boolean;
|
||||
source?: string
|
||||
}
|
||||
|
||||
type FontStorage = Record<string, FontDefinition> & { __selected?: string; };
|
||||
|
|
@ -40,12 +39,9 @@ export function validateFont(font: FontDefinition) {
|
|||
export async function saveFont(data: string | FontDefinition, selected = false) {
|
||||
let fontDefJson: FontDefinition;
|
||||
|
||||
if (typeof data === "object" && data.__source) data = data.__source;
|
||||
|
||||
if (typeof data === "string") {
|
||||
try {
|
||||
fontDefJson = await (await safeFetch(data)).json();
|
||||
fontDefJson.__source = data;
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to fetch fonts at ${data}`, { cause: e });
|
||||
}
|
||||
|
|
@ -55,16 +51,14 @@ export async function saveFont(data: string | FontDefinition, selected = false)
|
|||
|
||||
validateFont(fontDefJson);
|
||||
|
||||
try {
|
||||
await Promise.all(Object.entries(fontDefJson.main).map(async ([font, url]) => {
|
||||
let ext = url.split(".").pop();
|
||||
if (ext !== "ttf" && ext !== "otf") ext = "ttf";
|
||||
const path = `downloads/fonts/${fontDefJson.name}/${font}.${ext}`;
|
||||
if (!await fileExists(path)) await downloadFile(url, path);
|
||||
}));
|
||||
} catch (e) {
|
||||
throw new Error("Failed to download font assets", { cause: e });
|
||||
}
|
||||
const errors = await Promise.allSettled(Object.entries(fontDefJson.main).map(async ([font, url]) => {
|
||||
let ext = url.split(".").pop();
|
||||
if (ext !== "ttf" && ext !== "otf") ext = "ttf";
|
||||
const path = `downloads/fonts/${fontDefJson.name}/${font}.${ext}`;
|
||||
if (!await fileExists(path)) await downloadFile(url, path);
|
||||
})).then(it => it.map(it => it.status === 'fulfilled' ? undefined : it.reason));
|
||||
|
||||
if (errors.some(it => it)) throw errors
|
||||
|
||||
fonts[fontDefJson.name] = fontDefJson;
|
||||
|
||||
|
|
@ -72,14 +66,21 @@ export async function saveFont(data: string | FontDefinition, selected = false)
|
|||
return fontDefJson;
|
||||
}
|
||||
|
||||
export async function installFont(url: string, selected = false) {
|
||||
if (
|
||||
typeof url !== "string"
|
||||
|| Object.values(fonts).some(f => typeof f === "object" && f.__source === url)
|
||||
) {
|
||||
throw new Error("Invalid source or font was already installed");
|
||||
export async function updateFont(fontDef: FontDefinition) {
|
||||
let fontDefCopy = { ...fontDef }
|
||||
if (fontDefCopy.source) fontDefCopy = {
|
||||
...await fetch(fontDefCopy.source).then(it => it.json()),
|
||||
// Can't change these properties
|
||||
name: fontDef.name,
|
||||
source: fontDef.source
|
||||
}
|
||||
|
||||
const selected = fonts.__selected === fontDef.name
|
||||
await removeFont(fontDef.name)
|
||||
await saveFont(fontDefCopy, selected)
|
||||
}
|
||||
|
||||
export async function installFont(url: string, selected = false) {
|
||||
const font = await saveFont(url);
|
||||
if (selected) await selectFont(font.name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export interface Asset {
|
|||
id: number;
|
||||
name: string;
|
||||
moduleId: number;
|
||||
type: string;
|
||||
}
|
||||
|
||||
// Cache common usage
|
||||
|
|
|
|||
|
|
@ -18,6 +18,15 @@ export async function removeFile(path: string, { prefix = "pyoncord/" } = {}) {
|
|||
return void await NativeFileModule.removeFile("documents", `${prefix}${path}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove file from given path, currently no check for any failure
|
||||
* @param path Path to the file
|
||||
*/
|
||||
export async function removeCacheFile(path: string, prefix = "pyoncord/") {
|
||||
if (typeof NativeFileModule.removeFile !== "function") throw new Error("'fs.removeFile' is not supported");
|
||||
return void await NativeFileModule.removeFile("cache", `${prefix}${path}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file or directory given by the path exists
|
||||
* @param path Path to the file
|
||||
|
|
@ -57,7 +66,7 @@ export async function readFile(path: string, { prefix = "pyoncord/" } = {}): Pro
|
|||
export async function downloadFile(url: string, path: string, { prefix = "pyoncord/" } = {}) {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download file from ${url}: ${response.statusText}`);
|
||||
throw new Error(`Failed to download file from ${url}: ${response.status}`);
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { VdThemeInfo } from "@lib/addons/themes";
|
||||
import { removeCacheFile } from "./fs";
|
||||
|
||||
// @ts-ignore
|
||||
const pyonLoaderIdentity = globalThis.__PYON_LOADER__;
|
||||
|
|
@ -222,3 +223,8 @@ export function isFontSupported() {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function clearBundle() {
|
||||
// TODO: This should be not be hardcoded, maybe put in loader.json?
|
||||
return void await removeCacheFile("bundle.js");
|
||||
}
|
||||
|
|
@ -23,6 +23,12 @@ export function deleteJsxCreate(Component: string, callback: Callback) {
|
|||
*/
|
||||
export function patchJsx() {
|
||||
const callback = ([Component]: unknown[], ret: JSX.Element) => {
|
||||
// Band-aid fix for iOS invalid element type crashes
|
||||
if (typeof ret.type === 'undefined') {
|
||||
ret.type = 'RCTView'
|
||||
return ret
|
||||
}
|
||||
|
||||
// The check could be more complex, but this is fine for now to avoid overhead
|
||||
if (typeof Component === "function" && callbacks.has(Component.name)) {
|
||||
const cbs = callbacks.get(Component.name)!;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export interface SearchProps {
|
|||
}
|
||||
|
||||
function SearchIcon() {
|
||||
return <Image style={{ width: 16, height: 16 }} source={findAssetId("search")} />;
|
||||
return <Image style={{ width: 16, height: 16 }} source={findAssetId("search")!} />;
|
||||
}
|
||||
|
||||
export default ({ onChangeText, placeholder, style, isRound }: SearchProps) => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
import { lazy } from "react";
|
||||
import type { ImageURISource } from "react-native";
|
||||
|
||||
import { patchPanelUI } from "./patches/panel";
|
||||
import { patchTabsUI } from "./patches/tabs";
|
||||
|
||||
export interface RowConfig {
|
||||
|
|
@ -11,6 +10,7 @@ export interface RowConfig {
|
|||
onPress?: () => any;
|
||||
render?: Parameters<typeof lazy>[0];
|
||||
icon?: ImageURISource | number;
|
||||
IconComponent?: React.ReactNode,
|
||||
usePredicate?: () => boolean,
|
||||
useTrailing?: () => string | JSX.Element,
|
||||
rawTabsConfig?: Record<string, any>;
|
||||
|
|
@ -31,7 +31,6 @@ export function registerSection(section: { name: string; items: RowConfig[]; })
|
|||
export function patchSettings() {
|
||||
const unpatches = new Array<() => boolean>;
|
||||
|
||||
patchPanelUI(unpatches);
|
||||
patchTabsUI(unpatches);
|
||||
|
||||
return () => unpatches.forEach(u => u());
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { findByNameLazy, findByPropsLazy } from "@metro/wrappers";
|
|||
import { registeredSections } from "@ui/settings";
|
||||
|
||||
import { CustomPageRenderer, wrapOnPress } from "./shared";
|
||||
import { Strings } from "@core/i18n";
|
||||
import { TableRowIcon } from "@metro/common/components";
|
||||
|
||||
const settingConstants = findByPropsLazy("SETTING_RENDERER_CONFIG");
|
||||
const SettingsOverviewScreen = findByNameLazy("SettingsOverviewScreen", false);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { findByFilePathLazy, findByProps, findByPropsLazy } from "@metro/wrapper
|
|||
import { Linking } from "react-native";
|
||||
|
||||
import type { Dispatcher } from "./types/flux";
|
||||
import { Linking } from "react-native";
|
||||
|
||||
export * as components from "./components";
|
||||
|
||||
|
|
@ -10,6 +11,21 @@ export * as components from "./components";
|
|||
export const constants = findByPropsLazy("Fonts", "Permissions");
|
||||
export const channels = findByPropsLazy("getVoiceChannelId");
|
||||
export const i18n = findByPropsLazy("Messages");
|
||||
|
||||
// Polyfill LinkingUtils
|
||||
const openURL = (url: string) => Linking.openURL(url);
|
||||
export const url = nativeModuleProxy.NativeLinkingModule || nativeModuleProxy.DCDLinkingManager ? {
|
||||
openURL,
|
||||
openDeeplink: openURL,
|
||||
handleSupportedURL: openURL,
|
||||
isDiscordConnectOauth2Deeplink: () => {
|
||||
console.warn("url.isDiscordConnectOauth2Deeplink is not implemented and will always return false");
|
||||
return false;
|
||||
},
|
||||
showLongPressUrlActionSheet: () => console.warn("url.showLongPressUrlActionSheet is not implemented"),
|
||||
handleMessageLinking: findByFilePathLazy("modules/links/native/handleContentLinking.tsx", true),
|
||||
} : findByPropsLazy("openURL", "openDeeplink");
|
||||
|
||||
export const clipboard = findByPropsLazy("setString", "getString", "hasString");
|
||||
export const assets = findByPropsLazy("registerAsset");
|
||||
export const invites = findByPropsLazy("acceptInviteAndTransitionToInviteChannel");
|
||||
|
|
|
|||
|
|
@ -142,7 +142,6 @@ interface FABProps {
|
|||
style?: Style;
|
||||
onPress: () => void;
|
||||
positionBottom?: number;
|
||||
|
||||
}
|
||||
|
||||
export type FloatingActionButton = React.FC<FABProps>;
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ import { ModuleFlags, ModulesMapInternal } from "./enums";
|
|||
const { before, instead } = require("spitroast");
|
||||
|
||||
export const metroModules: Metro.ModuleList = window.modules;
|
||||
// Since modules registry is now a Map, the id is now strictly a number
|
||||
// Making the codebase to adapt to this change would be more ideal, though
|
||||
const metroRequire = (id: number | string) => window.__r(+id);
|
||||
const metroRequire = (id: string | number) => window.__r(+id);
|
||||
|
||||
// eslint-disable-next-line func-call-spacing
|
||||
const moduleSubscriptions = new Map<number, Set<() => void>>();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue