diff --git a/src/def.d.ts b/src/def.d.ts
index 6013689..180c15e 100644
--- a/src/def.d.ts
+++ b/src/def.d.ts
@@ -7,7 +7,6 @@ import _moment from "moment";
type MetroModules = { [id: number]: any };
// Component types
-// TODO: Make these not be here?
interface SummaryProps {
label: string;
icon?: string;
@@ -15,6 +14,10 @@ interface SummaryProps {
children: JSX.Element | JSX.Element[];
}
+interface ErrorBoundaryProps {
+ children: JSX.Element | JSX.Element[],
+}
+
// Helper types for API functions
type PropIntellisense
= Record
& Record;
type PropsFinder = (...props: T[]) => PropIntellisense;
@@ -331,6 +334,7 @@ interface VendettaObject {
Search: _React.ComponentType;
// Vendetta
Summary: (props: SummaryProps) => JSX.Element;
+ ErrorBoundary: (props: ErrorBoundaryProps) => JSX.Element;
}
toasts: {
showToast: (content: string, asset: number) => void;
diff --git a/src/ui/components/ErrorBoundary.tsx b/src/ui/components/ErrorBoundary.tsx
new file mode 100644
index 0000000..ef9c811
--- /dev/null
+++ b/src/ui/components/ErrorBoundary.tsx
@@ -0,0 +1,61 @@
+import { ErrorBoundaryProps } from "@types";
+import { React, ReactNative as RN, stylesheet, constants } from "@metro/common";
+import { findByProps } from "@metro/filters";
+import { Forms } from "@ui/components";
+import { semanticColors } from "@ui/color";
+
+interface ErrorBoundaryState {
+ hasErr: boolean;
+ errText?: string;
+}
+
+const Button = findByProps("Looks", "Colors", "Sizes") as any;
+
+const styles = stylesheet.createThemedStyleSheet({
+ view: {
+ flex: 1,
+ flexDirection: "column",
+ margin: 10,
+ },
+ title: {
+ fontSize: 20,
+ textAlign: "center",
+ marginBottom: 5,
+ },
+ codeblock: {
+ fontFamily: constants.Fonts.CODE_SEMIBOLD,
+ includeFontPadding: false,
+ fontSize: 12,
+ backgroundColor: semanticColors.BACKGROUND_SECONDARY,
+ padding: 5,
+ borderRadius: 5,
+ marginBottom: 5,
+ }
+});
+
+export default class ErrorBoundary extends React.Component {
+ constructor(props: ErrorBoundaryProps) {
+ super(props);
+ this.state = { hasErr: false };
+ }
+
+ static getDerivedStateFromError = (error: Error) => ({ hasErr: true, errText: error.message });
+
+ render() {
+ if (!this.state.hasErr) return this.props.children;
+
+ return (
+
+ Uh oh.
+ {this.state.errText}
+
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/ui/components/Summary.tsx b/src/ui/components/Summary.tsx
index cd46f46..ef9347b 100644
--- a/src/ui/components/Summary.tsx
+++ b/src/ui/components/Summary.tsx
@@ -1,7 +1,7 @@
import { SummaryProps } from "@types";
+import { Forms } from "@ui/components";
import { getAssetIDByName } from "@ui/assets";
import { ReactNative as RN } from "@metro/common";
-import { Forms } from "@ui/components";
// TODO: Animated would be awesome
// TODO: Destructuring Forms doesn't work here. Why?
diff --git a/src/ui/components/index.ts b/src/ui/components/index.ts
index 8eff4f1..c1a028a 100644
--- a/src/ui/components/index.ts
+++ b/src/ui/components/index.ts
@@ -6,4 +6,5 @@ export const General = findByProps("Button", "Text", "View");
export const Search = findByDisplayName("StaticSearchBarContainer");
// Vendetta
-export { default as Summary } from "@ui/components/Summary";
\ No newline at end of file
+export { default as Summary } from "@ui/components/Summary";
+export { default as ErrorBoundary } from "@ui/components/ErrorBoundary";
\ No newline at end of file
diff --git a/src/ui/settings/components/SettingsSection.tsx b/src/ui/settings/components/SettingsSection.tsx
index 5e3ac30..7946644 100644
--- a/src/ui/settings/components/SettingsSection.tsx
+++ b/src/ui/settings/components/SettingsSection.tsx
@@ -1,5 +1,5 @@
import { NavigationNative } from "@metro/common";
-import { Forms } from "@ui/components";
+import { ErrorBoundary, Forms } from "@ui/components";
import { getAssetIDByName } from "@ui/assets";
import { useProxy } from "@lib/storage";
import settings from "@lib/settings";
@@ -10,32 +10,34 @@ export default function SettingsSection() {
const navigation = NavigationNative.useNavigation();
useProxy(settings);
- return (
-
- }
- trailing={FormRow.Arrow}
- onPress={() => navigation.push("VendettaSettings")}
- />
-
- }
- trailing={FormRow.Arrow}
- onPress={() => navigation.push("VendettaPlugins")}
- />
- {settings.developerSettings && (
- <>
-
- }
- trailing={FormRow.Arrow}
- onPress={() => navigation.push("VendettaDeveloper")}
- />
- >
- )}
-
+ return (
+
+
+ }
+ trailing={FormRow.Arrow}
+ onPress={() => navigation.push("VendettaSettings")}
+ />
+
+ }
+ trailing={FormRow.Arrow}
+ onPress={() => navigation.push("VendettaPlugins")}
+ />
+ {settings.developerSettings && (
+ <>
+
+ }
+ trailing={FormRow.Arrow}
+ onPress={() => navigation.push("VendettaDeveloper")}
+ />
+ >
+ )}
+
+
)
}
\ No newline at end of file
diff --git a/src/ui/settings/index.tsx b/src/ui/settings/index.tsx
index aee0327..09aad11 100644
--- a/src/ui/settings/index.tsx
+++ b/src/ui/settings/index.tsx
@@ -2,6 +2,7 @@ import { NavigationNative, i18n } from "@metro/common";
import { findByDisplayName } from "@metro/filters";
import { after } from "@lib/patcher";
import findInReactTree from "@utils/findInReactTree";
+import ErrorBoundary from "@ui/components/ErrorBoundary";
import SettingsSection from "@ui/settings/components/SettingsSection";
import General from "@ui/settings/pages/General";
import Plugins from "@ui/settings/pages/Plugins";
@@ -38,7 +39,8 @@ export default function initSettings() {
render: ({ render: PageView, ...options }: { render: React.ComponentType }) => {
const navigation = NavigationNative.useNavigation();
React.useEffect(() => options && navigation.setOptions(options));
- return ;
+ // TODO: Is wrapping this in ErrorBoundary a good idea?
+ return ;
}
}
}
diff --git a/src/ui/settings/pages/AssetBrowser.tsx b/src/ui/settings/pages/AssetBrowser.tsx
index d784ad6..7508fdd 100644
--- a/src/ui/settings/pages/AssetBrowser.tsx
+++ b/src/ui/settings/pages/AssetBrowser.tsx
@@ -1,6 +1,7 @@
import { ReactNative as RN, stylesheet } from "@metro/common";
import { Forms, Search } from "@ui/components";
import { all } from "@ui/assets";
+import ErrorBoundary from "@ui/components/ErrorBoundary";
import AssetDisplay from "@ui/settings/components/AssetDisplay";
const { FormDivider } = Forms;
@@ -19,22 +20,24 @@ export default function AssetBrowser() {
const [search, setSearch] = React.useState("");
return (
-
- setSearch(v)}
- placeholder="Search"
- />
- a.name.includes(search) || a.id.toString() === search)}
- renderItem={({ item }) => (
- <>
-
-
- >
- )}
- keyExtractor={item => item.name}
- />
-
+
+
+ setSearch(v)}
+ placeholder="Search"
+ />
+ a.name.includes(search) || a.id.toString() === search)}
+ renderItem={({ item }) => (
+ <>
+
+
+ >
+ )}
+ keyExtractor={item => item.name}
+ />
+
+
)
}
\ No newline at end of file
diff --git a/src/ui/settings/pages/Developer.tsx b/src/ui/settings/pages/Developer.tsx
index 6769742..b8f688f 100644
--- a/src/ui/settings/pages/Developer.tsx
+++ b/src/ui/settings/pages/Developer.tsx
@@ -1,22 +1,14 @@
-import { ReactNative as RN, NavigationNative, stylesheet, constants } from "@metro/common";
-import { Forms, General } from "@ui/components";
+import { ReactNative as RN, NavigationNative } from "@metro/common";
+import { Forms } from "@ui/components";
import { getAssetIDByName } from "@ui/assets";
import { showToast } from "@ui/toasts";
import { connectToDebugger } from "@lib/debug";
import { useProxy } from "@lib/storage";
import settings, { loaderConfig } from "@lib/settings";
import logger from "@lib/logger";
+import ErrorBoundary from "@ui/components/ErrorBoundary";
const { FormSection, FormRow, FormSwitchRow, FormInput, FormDivider } = Forms;
-const { Text } = General;
-
-const styles = stylesheet.createThemedStyleSheet({
- code: {
- fontFamily: constants.Fonts.CODE_SEMIBOLD,
- includeFontPadding: false,
- fontSize: 12,
- }
-});
export default function Developer() {
const navigation = NavigationNative.useNavigation();
@@ -25,78 +17,80 @@ export default function Developer() {
useProxy(loaderConfig);
return (
-
-
- settings.debuggerUrl = v}
- placeholder="127.0.0.1:9090"
- title="DEBUGGER URL"
- />
-
- }
- onPress={() => connectToDebugger(settings.debuggerUrl)}
- />
- {window.__vendetta_rdc && <>
+
+
+
+ settings.debuggerUrl = v}
+ placeholder="127.0.0.1:9090"
+ title="DEBUGGER URL"
+ />
}
- onPress={() => {
- try {
- window.__vendetta_rdc?.connectToDevTools({
- host: settings.debuggerUrl.split(":")?.[0],
- resolveRNStyle: RN.StyleSheet.flatten,
- });
- } catch (e) {
- // TODO: Check if this ever actually catches anything
- logger.error("Failed to connect to React DevTools!", e);
- showToast("Failed to connect to React DevTools!", getAssetIDByName("Small"));
- }
+ label="Connect to debug websocket"
+ leading={}
+ onPress={() => connectToDebugger(settings.debuggerUrl)}
+ />
+ {window.__vendetta_rdc && <>
+
+ }
+ onPress={() => {
+ try {
+ window.__vendetta_rdc?.connectToDevTools({
+ host: settings.debuggerUrl.split(":")?.[0],
+ resolveRNStyle: RN.StyleSheet.flatten,
+ });
+ } catch (e) {
+ // TODO: Check if this ever actually catches anything
+ logger.error("Failed to connect to React DevTools!", e);
+ showToast("Failed to connect to React DevTools!", getAssetIDByName("Small"));
+ }
+ }}
+ />
+ >}
+
+ {window.__vendetta_loader?.features.loaderConfig &&
+ }
+ value={loaderConfig.customLoadUrl.enabled}
+ onValueChange={(v: boolean) => {
+ loaderConfig.customLoadUrl.enabled = v;
}}
/>
- >}
-
- {window.__vendetta_loader?.features.loaderConfig &&
- }
- value={loaderConfig.customLoadUrl.enabled}
- onValueChange={(v: boolean) => {
- loaderConfig.customLoadUrl.enabled = v;
- }}
- />
-
- {loaderConfig.customLoadUrl.enabled && <>
- loaderConfig.customLoadUrl.url = v}
- placeholder="http://localhost:4040/vendetta.js"
- title="VENDETTA URL"
- />
- >}
- {window.__vendetta_loader.features.devtools && }
- value={loaderConfig.loadReactDevTools}
- onValueChange={(v: boolean) => {
- loaderConfig.loadReactDevTools = v;
- }}
- />}
- }
-
- }
- trailing={FormRow.Arrow}
- onPress={() => navigation.push("VendettaAssetBrowser")}
- />
-
-
+ {loaderConfig.customLoadUrl.enabled && <>
+ loaderConfig.customLoadUrl.url = v}
+ placeholder="http://localhost:4040/vendetta.js"
+ title="VENDETTA URL"
+ />
+
+ >}
+ {window.__vendetta_loader.features.devtools && }
+ value={loaderConfig.loadReactDevTools}
+ onValueChange={(v: boolean) => {
+ loaderConfig.loadReactDevTools = v;
+ }}
+ />}
+ }
+
+ }
+ trailing={FormRow.Arrow}
+ onPress={() => navigation.push("VendettaAssetBrowser")}
+ />
+
+
+
)
}
diff --git a/src/ui/settings/pages/General.tsx b/src/ui/settings/pages/General.tsx
index 330fa1c..354dc38 100644
--- a/src/ui/settings/pages/General.tsx
+++ b/src/ui/settings/pages/General.tsx
@@ -6,6 +6,7 @@ import { getDebugInfo } from "@lib/debug";
import { useProxy } from "@lib/storage";
import settings from "@lib/settings";
import Version from "@ui/settings/components/Version";
+import ErrorBoundary from "@ui/components/ErrorBoundary";
const { FormRow, FormSwitchRow, FormSection, FormDivider } = Forms;
const debugInfo = getDebugInfo();
@@ -80,57 +81,59 @@ export default function General() {
];
return (
-
-
- }
- trailing={FormRow.Arrow}
- onPress={() => invites.acceptInviteAndTransitionToInviteChannel({ inviteKey: DISCORD_SERVER })}
- />
-
- }
- trailing={FormRow.Arrow}
- onPress={() => url.openURL(GITHUB)}
- />
-
-
- }
- onPress={() => RN.NativeModules.BundleUpdaterManager.reload()}
- />
-
- }
- value={settings.developerSettings}
- onValueChange={(v: boolean) => {
- settings.developerSettings = v;
- }}
- />
-
-
-
- {versions.map((v, i) => (
- <>
-
- {i !== versions.length - 1 && }
- >
- ))}
-
-
-
- {platformInfo.map((p, i) => (
- <>
-
- {i !== platformInfo.length - 1 && }
- >
- ))}
-
-
-
+
+
+
+ }
+ trailing={FormRow.Arrow}
+ onPress={() => invites.acceptInviteAndTransitionToInviteChannel({ inviteKey: DISCORD_SERVER })}
+ />
+
+ }
+ trailing={FormRow.Arrow}
+ onPress={() => url.openURL(GITHUB)}
+ />
+
+
+ }
+ onPress={() => RN.NativeModules.BundleUpdaterManager.reload()}
+ />
+
+ }
+ value={settings.developerSettings}
+ onValueChange={(v: boolean) => {
+ settings.developerSettings = v;
+ }}
+ />
+
+
+
+ {versions.map((v, i) => (
+ <>
+
+ {i !== versions.length - 1 && }
+ >
+ ))}
+
+
+
+ {platformInfo.map((p, i) => (
+ <>
+
+ {i !== platformInfo.length - 1 && }
+ >
+ ))}
+
+
+
+
)
}
\ No newline at end of file
diff --git a/src/ui/settings/pages/Plugins.tsx b/src/ui/settings/pages/Plugins.tsx
index 187701e..e045da1 100644
--- a/src/ui/settings/pages/Plugins.tsx
+++ b/src/ui/settings/pages/Plugins.tsx
@@ -5,6 +5,7 @@ import { getAssetIDByName } from "@ui/assets";
import { useProxy } from "@lib/storage";
import { plugins, installPlugin } from "@lib/plugins";
import PluginCard from "@ui/settings/components/PluginCard";
+import ErrorBoundary from "@ui/components/ErrorBoundary";
const { FormInput, FormRow } = Forms;
@@ -13,32 +14,34 @@ export default function Plugins() {
const [pluginUrl, setPluginUrl] = React.useState("");
return (
-
- setPluginUrl(v)}
- placeholder="https://example.com/"
- title="PLUGIN URL"
- />
- }
- onPress={() => {
- installPlugin(pluginUrl).then(() => {
- setPluginUrl("");
- }).catch((e: Error) => {
- showToast(e.message, getAssetIDByName("Small"));
- });
+
+
+ setPluginUrl(v)}
+ placeholder="https://example.com/"
+ title="PLUGIN URL"
+ />
+ }
+ onPress={() => {
+ installPlugin(pluginUrl).then(() => {
+ setPluginUrl("");
+ }).catch((e: Error) => {
+ showToast(e.message, getAssetIDByName("Small"));
+ });
+ }
}
- }
- />
- }
- keyExtractor={item => item.id}
- />
-
+ />
+ }
+ keyExtractor={item => item.id}
+ />
+
+
)
}
\ No newline at end of file