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