diff --git a/src/core/ui/settings/pages/Developer/AssetBrowser.tsx b/src/core/ui/settings/pages/Developer/AssetBrowser.tsx index b1bdcbb..785cb78 100644 --- a/src/core/ui/settings/pages/Developer/AssetBrowser.tsx +++ b/src/core/ui/settings/pages/Developer/AssetBrowser.tsx @@ -1,30 +1,70 @@ import AssetDisplay from "@core/ui/settings/pages/Developer/AssetDisplay"; -import { iterateAssets } from "@lib/api/assets"; +import { iterateAssets, findAssetId } from "@lib/api/assets"; import { Text } from "@metro/common/components"; import { ErrorBoundary, Search } from "@ui/components"; import { useMemo } from "react"; -import { FlatList, View } from "react-native"; +import { FlatList, View, ScrollView, TouchableOpacity, Image } from "react-native"; + +const displayable = new Set(['png', 'jpg', 'svg']); export default function AssetBrowser() { const [search, setSearch] = React.useState(""); + const [showNonImages, setShowNonImages] = React.useState(false); + const all = useMemo(() => Array.from(iterateAssets()), []); + const filteredData = useMemo(() => { + let result = all.filter(a => a.name.includes(search) || a.id.toString() === search); + + if (!showNonImages) { + result = result.filter(a => displayable.has(a.type)); + } + + return result; + }, [all, search, showNonImages]); + + return ( - setSearch(v)} - /> - - Some assets types cannot be displayed and will be marked in red. - a.name.includes(search) || a.id.toString() === search)} - renderItem={({ item }: any) => } - contentContainerStyle={{ overflow: 'hidden', backgroundColor: 'transparent', borderRadius: 16 }} - keyExtractor={a => a.name} + + + setSearch(v)} /> + setShowNonImages(!showNonImages)} + > + + + + + + {showNonImages && ( + Some assets types cannot be displayed and will be marked in red. + )} + } + contentContainerStyle={{ overflow: 'hidden', backgroundColor: 'transparent', borderRadius: 16 }} + scrollEnabled={false} + keyExtractor={a => a.id.toString()} + /> + + ); diff --git a/src/lib/addons/themes/colors/parser.ts b/src/lib/addons/themes/colors/parser.ts index e7e5c40..e2d7ced 100644 --- a/src/lib/addons/themes/colors/parser.ts +++ b/src/lib/addons/themes/colors/parser.ts @@ -90,7 +90,6 @@ export function parseColorManifest(manifest: ColorManifest): InternalColorDefini manifest.rawColors = draft; } - return { spec: 2, reference: resolveType(), @@ -100,9 +99,97 @@ export function parseColorManifest(manifest: ColorManifest): InternalColorDefini }; } + if (manifest.spec === 1 || (manifest.theme_color_map && !manifest.spec)) { + + const semanticDefinitions: InternalColorDefinition["semantic"] = {}; + const rawDefinitions: Record = {}; + + const themeIndex = resolveType() === "light" ? 1 : 0; + + if (manifest.theme_color_map) { + for (const [key, colorArray] of Object.entries(manifest.theme_color_map)) { + if (!Array.isArray(colorArray) || colorArray.length < 2) { + console.warn(`[Parser] Invalid color array for ${key}:`, colorArray); + continue; + } + + const colorValue = colorArray[themeIndex]; + + if (!colorValue) { + console.warn(`[Parser] Missing color value for ${key} at index ${themeIndex}`); + continue; + } + + const normalized = normalizeToHex(colorValue); + if (normalized) { + semanticDefinitions[key] = { + value: normalized, + opacity: 1 + }; + } + } + } + + const rawColors = manifest.colours || manifest.colors; + if (rawColors) { + for (const [key, colorValue] of Object.entries(rawColors)) { + if (typeof colorValue !== "string") continue; + + const normalized = normalizeToHex(colorValue); + if (normalized) { + const discordKey = convertEnmityToDiscordRawKey(key); + rawDefinitions[discordKey] = normalized; + + if (discordKey !== key) { + rawDefinitions[key] = normalized; + } + } + } + } + + if (manifest.unsafe_colors) { + for (const [key, colorValue] of Object.entries(manifest.unsafe_colors)) { + if (typeof colorValue !== "string") continue; + + const normalized = normalizeToHex(colorValue); + if (normalized) { + rawDefinitions[key] = normalized; + } + } + } + + if (Platform.OS === "android") applyAndroidAlphaKeys(rawDefinitions); + + return { + spec: 2, + reference: resolveType(), + semantic: semanticDefinitions, + raw: rawDefinitions, + background: manifest.background + }; + } throw new Error("Invalid theme spec"); } + function convertEnmityToDiscordRawKey(enmityKey: string): string { + const conversions: Record = { + "PRIMARY_DARK": "PRIMARY_100", + "PRIMARY_DARK_100": "PRIMARY_100", + "PRIMARY_DARK_200": "PRIMARY_200", + "PRIMARY_DARK_300": "PRIMARY_300", + "PRIMARY_DARK_360": "PRIMARY_360", + "PRIMARY_DARK_400": "PRIMARY_400", + "PRIMARY_DARK_500": "PRIMARY_500", + "PRIMARY_DARK_600": "PRIMARY_600", + "PRIMARY_DARK_630": "PRIMARY_630", + "PRIMARY_DARK_700": "PRIMARY_700", + "PRIMARY_DARK_800": "PRIMARY_800", + "PRIMARY_DARK_900": "PRIMARY_900", + }; + + return conversions[enmityKey] || enmityKey; + } + export function applyAndroidAlphaKeys(rawColors?: Record) { if (!rawColors) return; @@ -129,6 +216,11 @@ export function applyAndroidAlphaKeys(rawColors?: Record) { export function normalizeToHex(colorString: string | undefined): string | undefined { if (colorString === undefined) return undefined; + + if (colorString.toLowerCase() === "transparent") { + return "#00000000"; + } + if (chroma.valid(colorString)) return chroma(colorString).hex(); const color = Number(processColor(colorString)); @@ -140,4 +232,3 @@ export function normalizeToHex(colorString: string | undefined): string | undefi color >> 24 & 0xff // alpha ).hex(); } - diff --git a/src/lib/addons/themes/colors/types.ts b/src/lib/addons/themes/colors/types.ts index 99b6e39..203f3c0 100644 --- a/src/lib/addons/themes/colors/types.ts +++ b/src/lib/addons/themes/colors/types.ts @@ -39,7 +39,7 @@ export interface VendettaThemeManifest { /** @internal */ export interface InternalColorDefinition { - spec: 2 | 3; + spec: 1 | 2 | 3; reference: "darker" | "light"; semantic: Record