Fix illegible black text across most plugins on Discord's dark theme
This commit is contained in:
parent
3574c4b21e
commit
4ab8ed6880
9 changed files with 113 additions and 52 deletions
|
|
@ -3,6 +3,7 @@ import { storage } from "@vendetta/plugin";
|
|||
import { useProxy } from "@vendetta/storage";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import SettingsScaffold from "@shared/ui/SettingsScaffold";
|
||||
import { resolveSemanticColorSafe } from "@shared/lib/color";
|
||||
import renderTimestamp, { TimestampMode } from "../lib/renderTimestamp";
|
||||
|
||||
const { Text } = ReactNative;
|
||||
|
|
@ -31,7 +32,7 @@ export default function Settings() {
|
|||
<FormRow
|
||||
label={mode.label}
|
||||
subLabel={preview}
|
||||
trailing={selected ? <Text style={{ fontSize: 16 }}>✓</Text> : undefined}
|
||||
trailing={selected ? <Text style={{ fontSize: 16, color: resolveSemanticColorSafe(["TEXT_NORMAL", "TEXT_DEFAULT"], "#dbdee1") }}>✓</Text> : undefined}
|
||||
onPress={() => { storage.selected = mode.key; }}
|
||||
/>
|
||||
{mode.key === "custom" && selected && (
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import SettingsScaffold from "@shared/ui/SettingsScaffold";
|
|||
import NoteBox from "@shared/ui/NoteBox";
|
||||
import PrimaryButton from "@shared/ui/PrimaryButton";
|
||||
import { rawFindByProps, rawFindByName, rawFindByTypeName, rawFindByStoreName } from "@shared/lib/rawFind";
|
||||
import { resolveSemanticColorSafe } from "@shared/lib/color";
|
||||
import { runAllChecks, formatReport } from "../lib/checks";
|
||||
import { dumpVendettaApiTree } from "../lib/apiTree";
|
||||
import { runEval } from "../lib/evalTool";
|
||||
|
|
@ -15,6 +16,10 @@ import { captureComponentRenders } from "../lib/renderLogger";
|
|||
const { View, Text } = ReactNative;
|
||||
const { FormSection, FormInput } = Forms;
|
||||
|
||||
// Every raw Text below used to have no explicit color at all - illegible black-on-black on
|
||||
// Discord's dark theme, since RN's Text defaults to black with no theming of its own.
|
||||
const textColor = () => resolveSemanticColorSafe(["TEXT_NORMAL", "TEXT_DEFAULT"], "#dbdee1");
|
||||
|
||||
function FullScanSection() {
|
||||
const [summary, setSummary] = React.useState<string | null>(null);
|
||||
|
||||
|
|
@ -32,7 +37,7 @@ function FullScanSection() {
|
|||
}}
|
||||
/>
|
||||
{summary && (
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7 }}>{summary}</Text>
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7, color: textColor() }}>{summary}</Text>
|
||||
)}
|
||||
</View>
|
||||
</FormSection>
|
||||
|
|
@ -77,11 +82,11 @@ function EvalSection() {
|
|||
onPress={run}
|
||||
/>
|
||||
{result != null && (
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.85 }} selectable>
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.85, color: textColor() }} selectable>
|
||||
{result.length > 2000 ? `${result.slice(0, 2000)}\n... (truncated, full result copied)` : result}
|
||||
</Text>
|
||||
)}
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7 }}>
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7, color: textColor() }}>
|
||||
Runs as a plain (non-async) function body - a bare expression or `return ...`
|
||||
works, but not the async/await keywords (Hermes doesn't support those in
|
||||
dynamically-eval'd code). For timed/async checks, return a Promise built with a
|
||||
|
|
@ -142,11 +147,11 @@ function FluxLoggerSection() {
|
|||
onPress={capture}
|
||||
/>
|
||||
{result != null && (
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.85 }} selectable>
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.85, color: textColor() }} selectable>
|
||||
{result.length > 2000 ? `${result.slice(0, 2000)}\n... (truncated, full report copied)` : result}
|
||||
</Text>
|
||||
)}
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7 }}>
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7, color: textColor() }}>
|
||||
Tap start, then go do the thing you're trying to understand (open a screen, send a
|
||||
message, tap a button) before the window closes. Reports every action type that
|
||||
dispatched, sorted by count - the fastest way to find which event to hook into
|
||||
|
|
@ -198,11 +203,11 @@ function RenderLoggerSection() {
|
|||
onPress={capture}
|
||||
/>
|
||||
{result != null && (
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.85 }} selectable>
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.85, color: textColor() }} selectable>
|
||||
{result.length > 2000 ? `${result.slice(0, 2000)}\n... (truncated, full result copied)` : result}
|
||||
</Text>
|
||||
)}
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7 }}>
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7, color: textColor() }}>
|
||||
Patches the component to log its actual props on every render, up to the call cap
|
||||
or the time limit. Only works for React.memo/forwardRef-wrapped components (most
|
||||
of what findByTypeName/findByName return) - a bare function component has no
|
||||
|
|
@ -275,7 +280,7 @@ function RawSearchSection() {
|
|||
onPress={() => storeQuery.trim() && run(`rawFindByStoreName("${storeQuery.trim()}")`, () => rawFindByStoreName(storeQuery.trim()))}
|
||||
/>
|
||||
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7 }}>
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7, color: textColor() }}>
|
||||
Revenge's own findByProps/findByName/findByTypeName/findByStoreName cache a
|
||||
negative result forever the first time a search comes up empty, and never rescan
|
||||
- so if a module hasn't been required yet when you search, it can look "missing"
|
||||
|
|
@ -360,8 +365,8 @@ function FiberCaptureSection() {
|
|||
<FormSection title="Fiber capture (for Eval)">
|
||||
<View style={{ paddingHorizontal: 16, paddingVertical: 8 }}>
|
||||
<View ref={captureRef} style={{ width: 1, height: 1 }} />
|
||||
<Text style={{ fontSize: 12.5, opacity: 0.85 }} selectable>{status}</Text>
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7 }}>
|
||||
<Text style={{ fontSize: 12.5, opacity: 0.85, color: textColor() }} selectable>{status}</Text>
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7, color: textColor() }}>
|
||||
Reads React's own internal fiber pointer directly off a native view instance -
|
||||
works without React DevTools (confirmed absent on this build), since it's the
|
||||
same internal linkage React itself relies on to route touch events, not an
|
||||
|
|
@ -387,7 +392,7 @@ function ApiTreeSection() {
|
|||
showToast(`Copied ${lineCount} lines - every key path under window.vendetta`, undefined);
|
||||
}}
|
||||
/>
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7 }}>
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7, color: textColor() }}>
|
||||
Every key path under the stable plugin API (window.vendetta), 3 levels deep - this
|
||||
is everything Revenge itself exposes to plugins, not Discord's raw internals (those
|
||||
are searchable individually below, since dumping all of them would be enormous).
|
||||
|
|
@ -492,7 +497,7 @@ function ManualSearchSection() {
|
|||
<PrimaryButton label="Search first match & copy" style={{ marginTop: 8 }} onPress={searchName} />
|
||||
<PrimaryButton label="Search ALL matches & copy" style={{ marginTop: 8 }} onPress={searchNameAll} />
|
||||
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7 }}>
|
||||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7, color: textColor() }}>
|
||||
"ALL matches" is worth checking even when the first-match search succeeds - more
|
||||
than one module matching the same name/props is a common source of "found the
|
||||
wrong one" bugs (patching a re-export or an unrelated duplicate instead of the
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { React, ReactNative } from "@vendetta/metro/common";
|
||||
import { findByName } from "@vendetta/metro";
|
||||
import { resolveSemanticColorSafe } from "@shared/lib/color";
|
||||
import { fetchPronouns, getCachedPronouns } from "../lib/pronounStore";
|
||||
|
||||
const { Text } = ReactNative;
|
||||
|
|
@ -12,9 +13,13 @@ export default function PronounSection({ userId }: { userId: string }) {
|
|||
|
||||
if (!pronouns || !UserProfileSection) return null;
|
||||
|
||||
// Had no explicit color at all - illegible black-on-black on Discord's dark theme, since RN's
|
||||
// Text defaults to black with no theming of its own.
|
||||
const textColor = resolveSemanticColorSafe(["TEXT_NORMAL", "TEXT_DEFAULT"], "#dbdee1");
|
||||
|
||||
return (
|
||||
<UserProfileSection title="Pronouns">
|
||||
<Text style={{ fontSize: 16 }}>{pronouns}</Text>
|
||||
<Text style={{ fontSize: 16, color: textColor }}>{pronouns}</Text>
|
||||
</UserProfileSection>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { React, ReactNative, clipboard } from "@vendetta/metro/common";
|
|||
import { Forms } from "@vendetta/ui/components";
|
||||
import { showToast } from "@vendetta/ui/toasts";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import { resolveSemanticColorSafe } from "@shared/lib/color";
|
||||
import { getLog, removeLogEntry } from "../lib/store";
|
||||
import type { LoggedMessage } from "../lib/types";
|
||||
|
||||
|
|
@ -20,6 +21,13 @@ const { FormInput } = Forms;
|
|||
|
||||
const PAGE_SIZE = 100;
|
||||
|
||||
// Every raw Text below used to have no explicit color at all - illegible black-on-black on
|
||||
// Discord's dark theme, since RN's Text defaults to black with no theming of its own. Same
|
||||
// resolveSemanticColorSafe fallback-chain pattern used everywhere else in this repo.
|
||||
function textColor(): string {
|
||||
return resolveSemanticColorSafe(["TEXT_NORMAL", "TEXT_DEFAULT"], "#dbdee1");
|
||||
}
|
||||
|
||||
function formatTime(iso: string | undefined): string {
|
||||
if (!iso) return "unknown time";
|
||||
try {
|
||||
|
|
@ -45,34 +53,36 @@ function entryMatches(entry: LoggedMessage, query: string): boolean {
|
|||
}
|
||||
|
||||
function EntryDetail({ entry, onClose, onDeleted }: { entry: LoggedMessage; onClose: () => void; onDeleted: () => void }) {
|
||||
const color = textColor();
|
||||
|
||||
return (
|
||||
<ScrollView style={{ flex: 1, padding: 16 }}>
|
||||
<Text style={{ fontSize: 12, opacity: 0.6, marginBottom: 4 }}>
|
||||
<Text style={{ fontSize: 12, opacity: 0.6, marginBottom: 4, color }}>
|
||||
{entry.kind.toUpperCase()} · {formatTime(entry.messageTimestamp)} · logged {formatTime(entry.loggedAt)}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 15, fontWeight: "700", marginBottom: 8 }}>
|
||||
<Text style={{ fontSize: 15, fontWeight: "700", marginBottom: 8, color }}>
|
||||
{entry.authorDisplayName || entry.authorUsername || entry.authorId || "Unknown author"}
|
||||
{entry.authorIsBot ? " [BOT]" : ""}
|
||||
</Text>
|
||||
|
||||
{entry.kind === "edited" ? (
|
||||
<>
|
||||
<Text style={{ fontSize: 12, opacity: 0.6, marginBottom: 2 }}>Before:</Text>
|
||||
<Text style={{ fontSize: 14, marginBottom: 10 }} selectable>{entry.content || "(empty)"}</Text>
|
||||
<Text style={{ fontSize: 12, opacity: 0.6, marginBottom: 2 }}>After:</Text>
|
||||
<Text style={{ fontSize: 14, marginBottom: 10 }} selectable>{entry.newContent || "(empty)"}</Text>
|
||||
<Text style={{ fontSize: 12, opacity: 0.6, marginBottom: 2, color }}>Before:</Text>
|
||||
<Text style={{ fontSize: 14, marginBottom: 10, color }} selectable>{entry.content || "(empty)"}</Text>
|
||||
<Text style={{ fontSize: 12, opacity: 0.6, marginBottom: 2, color }}>After:</Text>
|
||||
<Text style={{ fontSize: 14, marginBottom: 10, color }} selectable>{entry.newContent || "(empty)"}</Text>
|
||||
</>
|
||||
) : (
|
||||
<Text style={{ fontSize: 14, marginBottom: 10 }} selectable>{entry.content || "(empty)"}</Text>
|
||||
<Text style={{ fontSize: 14, marginBottom: 10, color }} selectable>{entry.content || "(empty)"}</Text>
|
||||
)}
|
||||
|
||||
{entry.attachments.length > 0 && (
|
||||
<View style={{ marginBottom: 10 }}>
|
||||
<Text style={{ fontSize: 12, opacity: 0.6, marginBottom: 2 }}>
|
||||
<Text style={{ fontSize: 12, opacity: 0.6, marginBottom: 2, color }}>
|
||||
Attachments ({entry.attachments.length}):
|
||||
</Text>
|
||||
{entry.attachments.map((a, i) => (
|
||||
<Text key={i} style={{ fontSize: 13 }} selectable>
|
||||
<Text key={i} style={{ fontSize: 13, color }} selectable>
|
||||
{a.filename}{a.url ? ` - ${a.url}` : ""}
|
||||
</Text>
|
||||
))}
|
||||
|
|
@ -81,11 +91,11 @@ function EntryDetail({ entry, onClose, onDeleted }: { entry: LoggedMessage; onCl
|
|||
|
||||
{entry.embeds.length > 0 && (
|
||||
<View style={{ marginBottom: 10 }}>
|
||||
<Text style={{ fontSize: 12, opacity: 0.6, marginBottom: 2 }}>
|
||||
<Text style={{ fontSize: 12, opacity: 0.6, marginBottom: 2, color }}>
|
||||
Embeds ({entry.embeds.length}):
|
||||
</Text>
|
||||
{entry.embeds.map((e, i) => (
|
||||
<Text key={i} style={{ fontSize: 13 }} selectable>
|
||||
<Text key={i} style={{ fontSize: 13, color }} selectable>
|
||||
{e.title || e.url || "(untitled embed)"}
|
||||
</Text>
|
||||
))}
|
||||
|
|
@ -93,12 +103,12 @@ function EntryDetail({ entry, onClose, onDeleted }: { entry: LoggedMessage; onCl
|
|||
)}
|
||||
|
||||
{entry.reactions.length > 0 && (
|
||||
<Text style={{ fontSize: 13, marginBottom: 10 }}>
|
||||
<Text style={{ fontSize: 13, marginBottom: 10, color }}>
|
||||
Reactions: {entry.reactions.map((r) => `${r.emoji} x${r.count}`).join(" ")}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Text style={{ fontSize: 11, opacity: 0.5, marginBottom: 16 }} selectable>
|
||||
<Text style={{ fontSize: 11, opacity: 0.5, marginBottom: 16, color }} selectable>
|
||||
Message ID: {entry.id}{"\n"}Channel ID: {entry.channelId}{entry.guildId ? `\nServer ID: ${entry.guildId}` : ""}
|
||||
{entry.referencedMessageId ? `\nReplying to: ${entry.referencedMessageId}` : ""}
|
||||
</Text>
|
||||
|
|
@ -110,7 +120,7 @@ function EntryDetail({ entry, onClose, onDeleted }: { entry: LoggedMessage; onCl
|
|||
showToast("Copied entry as JSON", getAssetIDByName("ic_copy_24px"));
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 14, fontWeight: "600" }}>Copy JSON</Text>
|
||||
<Text style={{ fontSize: 14, fontWeight: "600", color }}>Copy JSON</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
|
|
@ -122,7 +132,7 @@ function EntryDetail({ entry, onClose, onDeleted }: { entry: LoggedMessage; onCl
|
|||
<Text style={{ fontSize: 14, fontWeight: "600", color: "#F23F42" }}>Delete entry</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={onClose}>
|
||||
<Text style={{ fontSize: 14, fontWeight: "600" }}>Back</Text>
|
||||
<Text style={{ fontSize: 14, fontWeight: "600", color }}>Back</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
|
@ -135,6 +145,7 @@ function LogList() {
|
|||
const [selected, setSelected] = React.useState<LoggedMessage | null>(null);
|
||||
const [visibleCount, setVisibleCount] = React.useState(PAGE_SIZE);
|
||||
const [, forceUpdate] = React.useReducer((n: number) => n + 1, 0);
|
||||
const color = textColor();
|
||||
|
||||
const all = getLog();
|
||||
const filtered = React.useMemo(() => {
|
||||
|
|
@ -172,13 +183,13 @@ function LogList() {
|
|||
<View style={{ flexDirection: "row", marginTop: 8, gap: 8 }}>
|
||||
{(["all", "deleted", "edited"] as const).map((k) => (
|
||||
<TouchableOpacity key={k} onPress={() => setKindFilter(k)}>
|
||||
<Text style={{ fontSize: 13, fontWeight: kindFilter === k ? "700" : "400", opacity: kindFilter === k ? 1 : 0.6 }}>
|
||||
<Text style={{ fontSize: 13, fontWeight: kindFilter === k ? "700" : "400", opacity: kindFilter === k ? 1 : 0.6, color }}>
|
||||
{k === "all" ? "All" : k === "deleted" ? "Deleted" : "Edited"}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
<Text style={{ fontSize: 12, opacity: 0.5, marginTop: 8 }}>
|
||||
<Text style={{ fontSize: 12, opacity: 0.5, marginTop: 8, color }}>
|
||||
{filtered.length} of {all.length} total
|
||||
</Text>
|
||||
</View>
|
||||
|
|
@ -190,20 +201,20 @@ function LogList() {
|
|||
onPress={() => setSelected(entry)}
|
||||
style={{ paddingHorizontal: 16, paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: "rgba(128,128,128,0.15)" }}
|
||||
>
|
||||
<Text style={{ fontSize: 12, opacity: 0.6 }}>
|
||||
<Text style={{ fontSize: 12, opacity: 0.6, color }}>
|
||||
{entry.kind === "deleted" ? "🗑" : "✎"} {entry.authorDisplayName || entry.authorUsername || entry.authorId || "Unknown"} · {formatTime(entry.messageTimestamp)}
|
||||
</Text>
|
||||
<Text numberOfLines={2} style={{ fontSize: 14 }}>
|
||||
<Text numberOfLines={2} style={{ fontSize: 14, color }}>
|
||||
{(entry.kind === "deleted" ? entry.content : entry.newContent) || "(no text content)"}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
{filtered.length === 0 && (
|
||||
<Text style={{ padding: 16, opacity: 0.6 }}>No log entries match.</Text>
|
||||
<Text style={{ padding: 16, opacity: 0.6, color }}>No log entries match.</Text>
|
||||
)}
|
||||
{visibleCount < filtered.length && (
|
||||
<TouchableOpacity onPress={() => setVisibleCount((n) => n + PAGE_SIZE)} style={{ padding: 16, alignItems: "center" }}>
|
||||
<Text style={{ fontWeight: "600" }}>Load more ({filtered.length - visibleCount} remaining)</Text>
|
||||
<Text style={{ fontWeight: "600", color }}>Load more ({filtered.length - visibleCount} remaining)</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,21 @@
|
|||
import { React, ReactNative, clipboard } from "@vendetta/metro/common";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { showToast } from "@vendetta/ui/toasts";
|
||||
import SettingsScaffold from "@shared/ui/SettingsScaffold";
|
||||
import NoteBox from "@shared/ui/NoteBox";
|
||||
import { resolveSemanticColorSafe } from "@shared/lib/color";
|
||||
import modules from "../modules";
|
||||
import { ModuleCategory, type AnyModule, type ModuleSetting } from "../lib/Module";
|
||||
import { ModuleCategory, vstorage, type AnyModule, type ModuleSetting } from "../lib/Module";
|
||||
|
||||
const { View, Text, TouchableOpacity } = ReactNative;
|
||||
const { FormSection, FormSwitchRow, FormRow, FormInput } = Forms;
|
||||
|
||||
// Raw Text below this point (the category header and bulk-action links) had no explicit color at
|
||||
// all - illegible black-on-black on Discord's dark theme, since RN's Text defaults to black with no
|
||||
// theming of its own. Same resolveSemanticColorSafe fallback-chain pattern used everywhere else.
|
||||
const textColor = () => resolveSemanticColorSafe(["TEXT_NORMAL", "TEXT_DEFAULT"], "#dbdee1");
|
||||
|
||||
function resolveSubLabel(setting: ModuleSetting, value: any): string | undefined {
|
||||
return typeof setting.subLabel === "function" ? setting.subLabel(value) : setting.subLabel;
|
||||
}
|
||||
|
|
@ -98,6 +105,13 @@ function ModuleSettingRow({ module, settingKey }: { module: AnyModule; settingKe
|
|||
|
||||
function ModuleSection({ module }: { module: AnyModule }) {
|
||||
module.useRefresh();
|
||||
// Belt and suspenders alongside the custom refresh() pub-sub above: module.storage.enabled is
|
||||
// read for the switch's `value` prop right below, and useProxy is the same storage-reactivity
|
||||
// mechanism every other plugin's settings screen in this repo already relies on for a toggle to
|
||||
// visually update immediately - this repo's own custom listeners system covers module.errors
|
||||
// too (plain in-memory state, not part of storage, so useProxy alone can't see it), which is
|
||||
// why that mechanism exists at all rather than being replaceable by useProxy outright.
|
||||
useProxy(module.storage);
|
||||
|
||||
const errorEntries = Object.entries(module.errors);
|
||||
|
||||
|
|
@ -151,16 +165,16 @@ function CategoryHeader({
|
|||
}}
|
||||
>
|
||||
<TouchableOpacity onPress={onToggleCollapse} style={{ flexDirection: "row", alignItems: "center", flexShrink: 1 }}>
|
||||
<Text style={{ fontSize: 12, fontWeight: "700", opacity: 0.6 }}>
|
||||
<Text style={{ fontSize: 12, fontWeight: "700", opacity: 0.6, color: textColor() }}>
|
||||
{collapsed ? "▸" : "▾"} {category.toUpperCase()} · {enabledCount}/{mods.length}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<TouchableOpacity onPress={() => setModulesEnabled(mods, true)}>
|
||||
<Text style={{ fontSize: 12, fontWeight: "600", opacity: 0.6 }}>All</Text>
|
||||
<Text style={{ fontSize: 12, fontWeight: "600", opacity: 0.6, color: textColor() }}>All</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => setModulesEnabled(mods, false)} style={{ marginLeft: 16 }}>
|
||||
<Text style={{ fontSize: 12, fontWeight: "600", opacity: 0.6 }}>None</Text>
|
||||
<Text style={{ fontSize: 12, fontWeight: "600", opacity: 0.6, color: textColor() }}>None</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
|
@ -210,12 +224,12 @@ export default function Settings() {
|
|||
|
||||
<View style={{ flexDirection: "row", justifyContent: "flex-end", paddingHorizontal: 16, paddingTop: 8 }}>
|
||||
<TouchableOpacity onPress={() => setModulesEnabled(allVisible, true)}>
|
||||
<Text style={{ fontSize: 13, fontWeight: "600", opacity: 0.75 }}>
|
||||
<Text style={{ fontSize: 13, fontWeight: "600", opacity: 0.75, color: textColor() }}>
|
||||
Enable {searching ? "matching" : "all"}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => setModulesEnabled(allVisible, false)} style={{ marginLeft: 16 }}>
|
||||
<Text style={{ fontSize: 13, fontWeight: "600", opacity: 0.75 }}>
|
||||
<Text style={{ fontSize: 13, fontWeight: "600", opacity: 0.75, color: textColor() }}>
|
||||
Disable {searching ? "matching" : "all"}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
import { React, ReactNative } from "@vendetta/metro/common";
|
||||
import { resolveSemanticColorSafe } from "../lib/color";
|
||||
|
||||
const { View, Text } = ReactNative;
|
||||
|
||||
/** Small bordered callout for explanatory text in a settings screen - theme-agnostic by design. */
|
||||
/**
|
||||
* Small bordered callout for explanatory text in a settings screen. Text color used to be unset
|
||||
* entirely - React Native's Text defaults to black with no explicit color, illegible on Discord's
|
||||
* dark theme (the default for most users).
|
||||
*/
|
||||
export default function NoteBox({ children, style }: { children: any; style?: any }) {
|
||||
const textColor = resolveSemanticColorSafe(["TEXT_NORMAL", "TEXT_DEFAULT", "TEXT_MUTED"], "#96989d");
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
|
|
@ -18,7 +25,7 @@ export default function NoteBox({ children, style }: { children: any; style?: an
|
|||
style
|
||||
]}
|
||||
>
|
||||
<Text style={{ fontSize: 12.5, lineHeight: 18, opacity: 0.75 }}>{children}</Text>
|
||||
<Text style={{ fontSize: 12.5, lineHeight: 18, opacity: 0.75, color: textColor }}>{children}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { ReactNative, clipboard, React } from "@vendetta/metro/common";
|
|||
import { showToast } from "@vendetta/ui/toasts";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import { Button } from "@vendetta/ui/components";
|
||||
import { resolveSemanticColorSafe } from "@shared/lib/color";
|
||||
import { cleanMessage } from "./cleanMessage";
|
||||
import JsonView from "./JsonView";
|
||||
|
||||
|
|
@ -10,6 +11,10 @@ const { View, ScrollView, TextInput, Text } = ReactNative;
|
|||
export default function RawPage({ message }: { message: any }) {
|
||||
const [query, setQuery] = React.useState("");
|
||||
const stringMessage = React.useMemo(() => JSON.stringify(cleanMessage(message), null, 4), [message.id]);
|
||||
// The search box previously hardcoded white text (illegible on a light theme) and the two
|
||||
// section labels below had no color at all (illegible black-on-black on a dark theme, RN's
|
||||
// Text default with no theming of its own).
|
||||
const textColor = resolveSemanticColorSafe(["TEXT_NORMAL", "TEXT_DEFAULT"], "#dbdee1");
|
||||
|
||||
return (
|
||||
<ScrollView style={{ flex: 1, marginHorizontal: 13, marginVertical: 10 }}>
|
||||
|
|
@ -51,18 +56,18 @@ export default function RawPage({ message }: { message: any }) {
|
|||
paddingHorizontal: 10,
|
||||
paddingVertical: 8,
|
||||
marginBottom: 10,
|
||||
color: "#fff"
|
||||
color: textColor
|
||||
}}
|
||||
/>
|
||||
|
||||
{!!message.content && (
|
||||
<View style={{ marginBottom: 12 }}>
|
||||
<Text style={{ fontSize: 11, opacity: 0.6, marginBottom: 4, fontWeight: "700" }}>MESSAGE CONTENT</Text>
|
||||
<Text selectable style={{ fontFamily: "monospace", fontSize: 13 }}>{message.content}</Text>
|
||||
<Text style={{ fontSize: 11, opacity: 0.6, marginBottom: 4, fontWeight: "700", color: textColor }}>MESSAGE CONTENT</Text>
|
||||
<Text selectable style={{ fontFamily: "monospace", fontSize: 13, color: textColor }}>{message.content}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<Text style={{ fontSize: 11, opacity: 0.6, marginBottom: 4, fontWeight: "700" }}>RAW DATA</Text>
|
||||
<Text style={{ fontSize: 11, opacity: 0.6, marginBottom: 4, fontWeight: "700", color: textColor }}>RAW DATA</Text>
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator>
|
||||
<JsonView text={stringMessage} query={query} />
|
||||
</ScrollView>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { React, ReactNative } from "@vendetta/metro/common";
|
||||
import { isValidHex } from "../lib/color";
|
||||
import { isValidHex, resolveSemanticColorSafe } from "../lib/color";
|
||||
|
||||
const { View, Text, TextInput, TouchableOpacity } = ReactNative;
|
||||
|
||||
|
|
@ -24,6 +24,9 @@ interface ColorInputProps {
|
|||
export default function ColorInput({ title, value, placeholder, onChange }: ColorInputProps) {
|
||||
const valid = !value || isValidHex(value);
|
||||
const swatchColor = isValidHex(value) ? value : (isValidHex(placeholder) ? placeholder : "#5865F2");
|
||||
// Same missing-color bug as NoteBox - Text with no explicit color defaults to black, illegible
|
||||
// on Discord's dark theme.
|
||||
const textColor = resolveSemanticColorSafe(["TEXT_NORMAL", "TEXT_DEFAULT"], "#dbdee1");
|
||||
|
||||
return (
|
||||
<View style={{ paddingHorizontal: 16, paddingVertical: 8 }}>
|
||||
|
|
@ -39,7 +42,7 @@ export default function ColorInput({ title, value, placeholder, onChange }: Colo
|
|||
borderColor: "rgba(128,128,128,0.35)"
|
||||
}}
|
||||
/>
|
||||
<Text style={{ fontSize: 15, fontWeight: "600" }}>{title}</Text>
|
||||
<Text style={{ fontSize: 15, fontWeight: "600", color: textColor }}>{title}</Text>
|
||||
</View>
|
||||
<TextInput
|
||||
value={value ?? ""}
|
||||
|
|
@ -47,6 +50,7 @@ export default function ColorInput({ title, value, placeholder, onChange }: Colo
|
|||
onChangeText={onChange}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
placeholderTextColor="rgba(128,128,128,0.6)"
|
||||
style={{
|
||||
borderWidth: 1,
|
||||
borderColor: valid ? "rgba(128,128,128,0.35)" : "#F23F42",
|
||||
|
|
@ -54,7 +58,8 @@ export default function ColorInput({ title, value, placeholder, onChange }: Colo
|
|||
paddingHorizontal: 10,
|
||||
paddingVertical: 8,
|
||||
fontSize: 14,
|
||||
marginBottom: 8
|
||||
marginBottom: 8,
|
||||
color: textColor
|
||||
}}
|
||||
/>
|
||||
<View style={{ flexDirection: "row", flexWrap: "wrap" }}>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,17 @@
|
|||
import { React, ReactNative } from "@vendetta/metro/common";
|
||||
import { resolveSemanticColorSafe } from "../lib/color";
|
||||
|
||||
const { View, Text } = ReactNative;
|
||||
|
||||
/** Small bordered callout for explanatory text in a settings screen - theme-agnostic by design. */
|
||||
/**
|
||||
* Small bordered callout for explanatory text in a settings screen. Text color used to be
|
||||
* unset entirely - React Native's Text defaults to black with no explicit color, which is
|
||||
* illegible on Discord's dark theme (the default for most users). Explicit theme-aware color now,
|
||||
* same resolveSemanticColorSafe fallback-chain pattern used everywhere else in this repo.
|
||||
*/
|
||||
export default function NoteBox({ children, style }: { children: any; style?: any }) {
|
||||
const textColor = resolveSemanticColorSafe(["TEXT_NORMAL", "TEXT_DEFAULT", "TEXT_MUTED"], "#96989d");
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
|
|
@ -18,7 +26,7 @@ export default function NoteBox({ children, style }: { children: any; style?: an
|
|||
style
|
||||
]}
|
||||
>
|
||||
<Text style={{ fontSize: 12.5, lineHeight: 18, opacity: 0.75 }}>{children}</Text>
|
||||
<Text style={{ fontSize: 12.5, lineHeight: 18, opacity: 0.75, color: textColor }}>{children}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue