diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2303c89..3824a70 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,14 +42,14 @@ jobs: echo '--- scripts/build.mjs (snippet) ---' >&2 sed -n '1,200p' scripts/build.mjs >&2 || true - # Primary extraction: find the first quoted semver (may include prerelease suffix, but we strip it) - raw=$(grep -m1 -oE '\"[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?\"' scripts/build.mjs | tr -d '\"' | head -n1 || true) + # Primary extraction: find the first quoted version (supports major.minor.patch or major.minor.patch.patch formats, may include prerelease suffix) + raw=$(grep -m1 -oE '\"[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?(-[0-9A-Za-z.-]+)?\"' scripts/build.mjs | tr -d '\"' | head -n1 || true) - # Strip any prerelease suffix, keep only major.minor.patch - version=$(printf '%s' "$raw" | sed -E 's/^([0-9]+\.[0-9]+\.[0-9]+).*/\1/') + # Strip any prerelease suffix, keep only major.minor.patch or major.minor.patch.patch + version=$(printf '%s' "$raw" | sed -E 's/^([0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?).*/\1/') - # Validate final result is exactly major.minor.patch - if ! printf '%s' "$version" | grep -xE '^[0-9]+\.[0-9]+\.[0-9]+$' >/dev/null; then + # Validate final result is either major.minor.patch or major.minor.patch.patch + if ! printf '%s' "$version" | grep -xE '^[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?$' >/dev/null; then echo "Failed to extract a valid base version from scripts/build.mjs" >&2 sed -n '1,200p' scripts/build.mjs >&2 || true exit 1 diff --git a/scripts/build.mjs b/scripts/build.mjs index fa3cb3a..16bec0a 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -75,7 +75,7 @@ const config = { constModules: { globals: { "bunny-build-info": { - version: `"1.4.1"`, + version: `"1.4.1.1"`, }, }, }, diff --git a/src/core/i18n/default.json b/src/core/i18n/default.json index d0efbe0..4597d9a 100644 --- a/src/core/i18n/default.json +++ b/src/core/i18n/default.json @@ -5,6 +5,9 @@ "ARE_YOU_SURE_TO_DELETE_PLUGIN": "Are you sure you wish to delete {name}? This will clear all of the plugin's data.", "ARE_YOU_SURE_TO_DELETE_THEME": "Are you sure you wish to delete {name}?", "ASSET_BROWSER": "Asset Browser", + "ASSET_TYPES": "Asset Types", + "IMAGE_FILES": "Image Files", + "TEXT_FILES": "Text Files", "BRAND": "Brand", "SHIGGYCORD": "💦GoonCord💦", "SHIGGYCORD_URL": "GoonCord URL", diff --git a/src/core/ui/settings/pages/Developer/AssetBrowser.tsx b/src/core/ui/settings/pages/Developer/AssetBrowser.tsx index 639dcb5..c85dd37 100644 --- a/src/core/ui/settings/pages/Developer/AssetBrowser.tsx +++ b/src/core/ui/settings/pages/Developer/AssetBrowser.tsx @@ -1,21 +1,111 @@ import AssetDisplay from "@core/ui/settings/pages/Developer/AssetDisplay"; import { iterateAssets } from "@lib/api/assets"; -import { Text } from "@metro/common/components"; +import { findAssetId } from "@lib/api/assets"; +import { Strings } from "@core/i18n"; +import { IconButton, TableCheckboxRow, TableRowGroup, ActionSheet, BottomSheetTitleHeader } from "@metro/common/components"; import { ErrorBoundary, Search } from "@ui/components"; -import { useMemo } from "react"; +import { showSheet } from "@lib/ui/sheets"; +import { useProxy, createProxy } from "@core/vendetta/storage"; +import { useState, useMemo, useCallback } from "react"; import { FlatList, View } from "react-native"; +const IMAGE_FILES = [ + { id: "png", label: "PNG", defaultEnabled: true }, + { id: "jpg", label: "JPG", defaultEnabled: true }, + { id: "jpeg", label: "JPEG", defaultEnabled: true }, + { id: "svg", label: "SVG", defaultEnabled: true }, + { id: "gif", label: "GIF", defaultEnabled: true }, +]; + +const TEXT_FILES = [ + { id: "jsona", label: "JSONA", defaultEnabled: false }, + { id: "json", label: "JSON", defaultEnabled: false }, + { id: "lottie", label: "Lottie", defaultEnabled: false }, +]; + +const ALL_FILE_TYPES = [...IMAGE_FILES, ...TEXT_FILES]; + +// Create initial enabled filters state +const createInitialFilters = () => { + const filters: Record = {}; + ALL_FILE_TYPES.forEach(type => { + filters[type.id] = type.defaultEnabled; + }); + return filters; +}; + +const { proxy: assetBrowserStorage } = createProxy({ + enabledFilters: createInitialFilters() +}); + export default function AssetBrowser() { - const [search, setSearch] = React.useState(""); - const all = useMemo(() => Array.from(iterateAssets()), []); + const [search, setSearch] = useState(""); + const [updateTick, setUpdateTick] = useState(0); + + // Use useProxy to make the state reactive + useProxy(assetBrowserStorage); + + const getFilteredAssets = useCallback(() => { + return Array.from(iterateAssets()).filter((asset) => { + const type = String(asset.type ?? "").toLowerCase(); + return assetBrowserStorage.enabledFilters[type] === true; + }); + }, [updateTick]); + + const all = useMemo(() => getFilteredAssets(), [getFilteredAssets]); + + const toggleFilter = (filterId: string) => { + assetBrowserStorage.enabledFilters[filterId] = !assetBrowserStorage.enabledFilters[filterId]; + setUpdateTick(prev => prev + 1); + }; + + const handleFilterPress = () => { + showSheet("AssetBrowserFilter", () => { + useProxy(assetBrowserStorage); + + return ( + + + + {IMAGE_FILES.map(fileType => ( + toggleFilter(fileType.id)} + /> + ))} + + + {TEXT_FILES.map(fileType => ( + toggleFilter(fileType.id)} + /> + ))} + + + ); + }); + }; return ( - setSearch(v)} - /> + + setSearch(v)} + /> + + - - Some assets types cannot be displayed and will be marked in red. - a.name.includes(search) || a.id.toString() === search, + (a) => (a.name.includes(search) || a.id.toString() === search), )} renderItem={({ item }: any) => } contentContainerStyle={{ diff --git a/src/core/ui/settings/pages/Developer/index.tsx b/src/core/ui/settings/pages/Developer/index.tsx index 1fd71d2..5ef3c0f 100644 --- a/src/core/ui/settings/pages/Developer/index.tsx +++ b/src/core/ui/settings/pages/Developer/index.tsx @@ -7,6 +7,9 @@ import { connectToDebugger, disconnectFromDebugger, isConnectedToDebugger, + connectRdt, + disconnectRdt, + useIsRdtConnected, } from "@lib/api/debug"; import { getReactDevToolsProp, @@ -33,7 +36,7 @@ import { ErrorBoundary } from "@ui/components"; import ErrorBoundaryScreen from "@core/ui/reporter/components/ErrorBoundaryScreen"; import { createStyles, TextStyleSheet } from "@ui/styles"; import { NativeModules } from "react-native"; -import { ScrollView, StyleSheet } from "react-native"; +import { ScrollView } from "react-native"; import { showToast } from "@ui/toasts"; import { useState, useEffect } from "react"; @@ -72,6 +75,7 @@ export default function Developer() { const [isDebuggerConnected, setIsDebuggerConnected] = useState( isConnectedToDebugger(), ); + const isRdtConnected = useIsRdtConnected(); const styles = useStyles(); const navigation = NavigationNative.useNavigation(); @@ -96,6 +100,18 @@ export default function Developer() { } }; + const handleReactDevToolsConnect = () => { + if (isRdtConnected) { + disconnectRdt(); + } else { + if (!settings.devToolsUrl?.trim()) { + showToast("Invalid devTools URL!", findAssetId("Small")); + return; + } + connectRdt(settings.devToolsUrl); + } + }; + return ( - - ( - - ws:// - - )} - defaultValue={settings.debuggerUrl} - onChange={(v: string) => (settings.debuggerUrl = v)} - /> - } - value={settings.autoDebugger} - onValueChange={(v: boolean) => { - settings.autoDebugger = v; - }} - /> - } - onPress={() => connectToDebugger(settings.debuggerUrl)} - /> - + + + + ( + + ws:// + + )} + defaultValue={settings.debuggerUrl} + onChange={(v: string) => (settings.debuggerUrl = v)} + /> + + - {isReactDevToolsPreloaded() && ( - - ( - - ws:// - - )} - defaultValue={settings.devToolsUrl} - onChange={(v: string) => (settings.devToolsUrl = v)} - /> + } - value={settings.autoDevTools} + label={Strings.AUTO_DEBUGGER} + icon={} + value={settings.autoDebugger} onValueChange={(v: boolean) => { - settings.autoDevTools = v; + settings.autoDebugger = v; }} /> } - onPress={async () => { - if (!settings.devToolsUrl?.trim()) { - showToast("Invalid devTools URL!", findAssetId("Small")); - return; - } - - try { - const devTools = - window[getReactDevToolsProp() || "__vendetta_rdc"]; - - if (!devTools?.connectToDevTools) { - showToast( - "React DevTools not available", - findAssetId("Small"), - ); - return; - } - - await devTools.connectToDevTools({ - host: settings.devToolsUrl.split(":")?.[0], - resolveRNStyle: StyleSheet.flatten, - }); - showToast( - "Connected to React DevTools!", - findAssetId("Check"), - ); - } catch (error) { - showToast( - "Failed to connect to React DevTools", - findAssetId("Small"), - ); - } - }} + label={Strings.CONNECT_TO_DEBUG_WEBSOCKET} + subLabel="Connect DevTools for debugging" + icon={} + onPress={handleDebuggerConnect} /> + + - {isLoaderConfigSupported() && isVendettaLoader() && ( + {isReactDevToolsPreloaded() && ( + + + + ( + + ws:// + + )} + defaultValue={settings.devToolsUrl} + onChange={(v: string) => (settings.devToolsUrl = v)} + /> + + + + - } - value={loaderConfig.loadReactDevTools} + label={Strings.AUTO_DEVTOOLS} + icon={} + value={settings.autoDevTools} onValueChange={(v: boolean) => { - loaderConfig.loadReactDevTools = v; + settings.autoDevTools = v; }} /> - )} - + } + onPress={handleReactDevToolsConnect} + /> + + {isLoaderConfigSupported() && isVendettaLoader() && ( + + } + value={loaderConfig.loadReactDevTools} + onValueChange={(v: boolean) => { + loaderConfig.loadReactDevTools = v; + }} + /> + )} + + )} {isLoaderConfigSupported() && ( diff --git a/src/lib/api/debug.ts b/src/lib/api/debug.ts index 4bdf681..17d7b9a 100644 --- a/src/lib/api/debug.ts +++ b/src/lib/api/debug.ts @@ -224,13 +224,14 @@ export function connectToDebugger(url: string) { } }); - socket.addEventListener("close", () => { - showToast("Disconnected from debugger.", findAssetId("Small")); + socket.addEventListener("close", (e: any) => { + logger.error(`Debugger disconnected. (${e?.code}, ${e?.reason || "no reason specified"})`); unpatchConsoleAndLogger(); }); - socket.addEventListener("error", (err: any) => { - console.log(`Debugger error: ${err.message}`); + socket.addEventListener("error", (e: any) => { + const err = e?.message || e?.error?.message || "WebSocket Connection Errored"; + logger.error('Debugger error:', err); showToast("An error occurred with the debugger connection!", findAssetId("Small")); unpatchConsoleAndLogger(); }); @@ -281,13 +282,14 @@ export function connectRdt(url: string, quiet?: boolean) { bump(); }); - ws.addEventListener('close', () => { + ws.addEventListener('close', (e: any) => { + logger.error(`React DevTools disconnected. (${e?.code}, ${e?.reason || "no reason specified"})`); cleanupRdt(); }); ws.addEventListener('error', (e: any) => { cleanupRdt(); - const err = e?.message ?? e?.stack ?? String(e); + const err = e?.message || e?.error?.message || "WebSocket Connection Errored"; logger.error('React DevTools error:', err); if (!quiet) showToast(err, findAssetId("CircleXIcon-primary")); }); diff --git a/src/metro/common/types/components.ts b/src/metro/common/types/components.ts index 254fa34..87a3bf3 100644 --- a/src/metro/common/types/components.ts +++ b/src/metro/common/types/components.ts @@ -211,7 +211,7 @@ interface TableRowIconProps { export type TableRowIcon = React.FC; interface TableRowGroupProps { - title: string; + title?: string; children: ReactNode; }