Switch every plugin's settings UI from legacy Forms to real TableRow
This commit is contained in:
parent
d5364e3a62
commit
25393c9247
19 changed files with 183 additions and 209 deletions
|
|
@ -1,13 +1,12 @@
|
|||
import { React, ReactNative, moment } from "@vendetta/metro/common";
|
||||
import { storage } from "@vendetta/plugin";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TableRow, TableRowGroup, TableSwitchRow, TextInput } from "@shared/ui/table";
|
||||
import SettingsScaffold from "@shared/ui/SettingsScaffold";
|
||||
import { resolveSemanticColorSafe } from "@shared/lib/color";
|
||||
import renderTimestamp, { TimestampMode } from "../lib/renderTimestamp";
|
||||
|
||||
const { Text } = ReactNative;
|
||||
const { FormSection, FormRow, FormDivider, FormSwitchRow, FormInput } = Forms;
|
||||
|
||||
const MODES: { label: string; key: TimestampMode }[] = [
|
||||
{ label: "Calendar", key: "calendar" },
|
||||
|
|
@ -21,41 +20,40 @@ export default function Settings() {
|
|||
|
||||
return (
|
||||
<SettingsScaffold>
|
||||
<FormSection title="Format">
|
||||
{MODES.map((mode, i) => {
|
||||
<TableRowGroup title="Format">
|
||||
{MODES.map((mode) => {
|
||||
const selected = storage.selected === mode.key;
|
||||
let preview = "";
|
||||
try { preview = renderTimestamp(moment(), mode.key); } catch { preview = ""; }
|
||||
|
||||
return (
|
||||
<React.Fragment key={mode.key}>
|
||||
<FormRow
|
||||
<TableRow
|
||||
label={mode.label}
|
||||
subLabel={preview}
|
||||
trailing={selected ? <Text style={{ fontSize: 16, color: resolveSemanticColorSafe(["TEXT_NORMAL", "TEXT_DEFAULT"], "#dbdee1") }}>✓</Text> : undefined}
|
||||
onPress={() => { storage.selected = mode.key; }}
|
||||
/>
|
||||
{mode.key === "custom" && selected && (
|
||||
<FormInput
|
||||
title="Custom format (moment.js tokens)"
|
||||
<TextInput
|
||||
label="Custom format (moment.js tokens)"
|
||||
placeholder="dddd, MMMM Do YYYY, h:mm:ss a"
|
||||
value={storage.customFormat}
|
||||
onChange={(v: string) => { storage.customFormat = v; }}
|
||||
/>
|
||||
)}
|
||||
{i !== MODES.length - 1 && <FormDivider />}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</FormSection>
|
||||
<FormSection title="Layout">
|
||||
<FormSwitchRow
|
||||
</TableRowGroup>
|
||||
<TableRowGroup title="Layout">
|
||||
<TableSwitchRow
|
||||
label="Always show name & avatar"
|
||||
subLabel="Shows the username, avatar, and timestamp on every message instead of grouping consecutive messages"
|
||||
value={!!storage.separateMessages}
|
||||
onValueChange={(v: boolean) => { storage.separateMessages = v; }}
|
||||
/>
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
</SettingsScaffold>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { React, ReactNative } from "@vendetta/metro/common";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { storage } from "@vendetta/plugin";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TableRowGroup, TextInput } from "@shared/ui/table";
|
||||
import { showToast } from "@vendetta/ui/toasts";
|
||||
import SettingsScaffold from "@shared/ui/SettingsScaffold";
|
||||
import ListSection from "@shared/ui/ListSection";
|
||||
|
|
@ -11,7 +11,6 @@ import NoteBox from "@shared/ui/NoteBox";
|
|||
import { allTags, setUserTag, removeUserTag } from "../lib/tags";
|
||||
|
||||
const { View } = ReactNative;
|
||||
const { FormSection, FormInput } = Forms;
|
||||
|
||||
function AddTagForm() {
|
||||
const [userId, setUserId] = React.useState("");
|
||||
|
|
@ -21,13 +20,13 @@ function AddTagForm() {
|
|||
|
||||
return (
|
||||
<View style={{ paddingHorizontal: 16, paddingBottom: 12 }}>
|
||||
<FormInput
|
||||
title="User ID"
|
||||
<TextInput
|
||||
label="User ID"
|
||||
placeholder="long-press a user in Discord's own menu and Copy ID"
|
||||
value={userId}
|
||||
onChange={setUserId}
|
||||
/>
|
||||
<FormInput title="Tag text" placeholder="e.g. FRIEND" value={text} onChange={setText} />
|
||||
<TextInput label="Tag text" placeholder="e.g. FRIEND" value={text} onChange={setText} />
|
||||
<ColorInput title="Color" value={color} onChange={setColor} />
|
||||
<PrimaryButton
|
||||
label="Save tag"
|
||||
|
|
@ -57,9 +56,9 @@ export default function Settings() {
|
|||
one here by user ID - turn on Developer Mode (Discord Settings → Advanced) to get a
|
||||
"Copy ID" option when you long-press a user elsewhere.
|
||||
</NoteBox>
|
||||
<FormSection title="Add a tag">
|
||||
<TableRowGroup title="Add a tag">
|
||||
<AddTagForm />
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
<ListSection
|
||||
title="Tagged users"
|
||||
emptyText="No one's tagged yet."
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { React, ReactNative } from "@vendetta/metro/common";
|
||||
import { findByProps } from "@vendetta/metro";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TextInput } from "@shared/ui/table";
|
||||
import { showToast } from "@vendetta/ui/toasts";
|
||||
import ColorInput from "@shared/ui/ColorInput";
|
||||
import { getUserTag, setUserTag, removeUserTag } from "../lib/tags";
|
||||
|
||||
const { View } = ReactNative;
|
||||
const { FormInput } = Forms;
|
||||
|
||||
const ALERT_KEY = "custom-user-tags-editor";
|
||||
|
||||
|
|
@ -54,7 +53,7 @@ function TagEditor({ userId, username }: { userId: string; username: string }) {
|
|||
title={`Tag ${username}`}
|
||||
content={
|
||||
<View>
|
||||
<FormInput title="Tag text" placeholder="e.g. FRIEND" value={text} onChange={setText} />
|
||||
<TextInput label="Tag text" placeholder="e.g. FRIEND" value={text} onChange={setText} />
|
||||
<ColorInput title="Color" value={color} onChange={setColor} />
|
||||
</View>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import { showToast } from "@vendetta/ui/toasts";
|
||||
import { findByProps } from "@vendetta/metro";
|
||||
import React, { useMemo, useState, useEffect } from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import { View } from "react-native";
|
||||
import { buildFPTE, hasFPTE, stripFPTE } from "@fpte/lib/fpte";
|
||||
import { type ProfileEffectConfig, UserStore, UserProfileStore } from "@fpte/lib/stores";
|
||||
import { useAccentColor, usePrimaryColor, useShowPreview } from "@fpte/patches/patchUseProfileTheme";
|
||||
import { showColorPicker, showEffectPicker } from "@fpte/ui/actionSheets";
|
||||
import { resolveSemanticColorSafe, useAvatarColors, useThemeContext } from "@fpte/ui/color";
|
||||
import { BuilderButton, Button, StaticEffect } from "@fpte/ui/components";
|
||||
import { FormCardSection, FormSwitchRow } from "@fpte/ui/components/forms";
|
||||
import { FormSwitchRow } from "@fpte/ui/components/forms";
|
||||
import { TableRowGroup } from "@shared/ui/table";
|
||||
|
||||
const UserProfileActionCreators = findByProps("saveProfileChanges");
|
||||
|
||||
|
|
@ -103,23 +104,7 @@ export function Builder({ guildId }: BuilderProps) {
|
|||
const applyButtonVisible = hasSelection || fpteActive;
|
||||
|
||||
return (
|
||||
<FormCardSection
|
||||
title={
|
||||
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
||||
<Text style={{ fontSize: 16, color: "#FFFFFF" }}>FPTE Builder</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: fpteActive ? "#4CAF50" : "#F44336",
|
||||
fontSize: 17,
|
||||
marginLeft: 8,
|
||||
}}
|
||||
>
|
||||
{fpteActive ? "Active" : "Inactive"}
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
cardStyle={{ backgroundColor: "transparent" }}
|
||||
>
|
||||
<TableRowGroup title={`FPTE Builder — ${fpteActive ? "Active" : "Inactive"}`}>
|
||||
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
|
||||
<BuilderButton
|
||||
fgColor={fgColor}
|
||||
|
|
@ -183,6 +168,6 @@ export function Builder({ guildId }: BuilderProps) {
|
|||
value={buildLegacy}
|
||||
onValueChange={setBuildLegacy}
|
||||
/> */}
|
||||
</FormCardSection>
|
||||
</TableRowGroup>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TableRow, TableRowGroup, TableSwitchRow } from "@shared/ui/table";
|
||||
import type { ComponentType, ReactNode } from "react";
|
||||
import type { ColorValue, PressableProps, ViewProps } from "react-native";
|
||||
|
||||
|
|
@ -69,15 +69,6 @@ export interface FormRowProps extends Pick<ViewProps, "style">,
|
|||
hasError?: boolean | undefined;
|
||||
}
|
||||
|
||||
export interface FormRadioProps {
|
||||
selected?: boolean | undefined;
|
||||
}
|
||||
|
||||
export interface FormRadioRowProps extends FormRadioProps, FormRowProps {
|
||||
align?: "left" | "right";
|
||||
value?: boolean | undefined;
|
||||
}
|
||||
|
||||
export interface FormSwitchProps extends Pick<PressableProps, "accessible" | "accessibilityHint" | "accessibilityLabel" | "disabled" | "style"> {
|
||||
value?: boolean | undefined;
|
||||
onValueChange?: ((value: boolean) => void) | undefined;
|
||||
|
|
@ -90,19 +81,8 @@ export interface FormSwitchRowProps extends Pick<FormLabelProps, "numberOfLines"
|
|||
switchProps?: FormSwitchProps | undefined;
|
||||
}
|
||||
|
||||
export interface FormCardSectionProps extends Pick<ViewProps, "accessibilityLabel" | "accessibilityRole" | "children" | "style"> {
|
||||
title?: ReactNode;
|
||||
headerComponent?: ReactNode;
|
||||
titleStyle?: TextProps["style"];
|
||||
cardStyle?: ViewProps["style"];
|
||||
}
|
||||
export const FormSection: ComponentType<FormSection> = TableRowGroup;
|
||||
|
||||
export const FormSection: ComponentType<FormSection> = Forms.FormSection;
|
||||
export const FormRow: ComponentType<FormRowProps> = TableRow;
|
||||
|
||||
export const FormRow: ComponentType<FormRowProps> = Forms.FormRow;
|
||||
|
||||
export const FormRadioRow: ComponentType<FormRadioRowProps> = Forms.FormRadioRow;
|
||||
|
||||
export const FormSwitchRow: ComponentType<FormSwitchRowProps> = Forms.FormSwitchRow;
|
||||
|
||||
export const FormCardSection: ComponentType<FormCardSectionProps> = Forms.FormCardSection;
|
||||
export const FormSwitchRow: ComponentType<FormSwitchRowProps> = TableSwitchRow;
|
||||
|
|
|
|||
|
|
@ -3,25 +3,23 @@ import { useProxy } from "@vendetta/storage";
|
|||
import React from "react";
|
||||
import { ScrollView } from "react-native";
|
||||
|
||||
import { FormRadioRow, FormRow, FormSection, FormSwitchRow } from "@fpte/ui/components/forms";
|
||||
import { FormSwitchRow } from "@fpte/ui/components/forms";
|
||||
import { TableRadioGroup, TableRadioRow, TableRowGroup } from "@shared/ui/table";
|
||||
|
||||
export function Settings() {
|
||||
useProxy(storage);
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<FormSection title="Settings">
|
||||
<FormRow label="Source to prioritize" />
|
||||
<FormRadioRow
|
||||
label="Nitro"
|
||||
selected={!!storage.prioritizeNitro}
|
||||
onPress={() => { storage.prioritizeNitro = true; }}
|
||||
/>
|
||||
<FormRadioRow
|
||||
label="About Me"
|
||||
selected={!storage.prioritizeNitro}
|
||||
onPress={() => { storage.prioritizeNitro = false; }}
|
||||
/>
|
||||
<TableRowGroup title="Settings">
|
||||
<TableRadioGroup
|
||||
title="Source to prioritize"
|
||||
value={storage.prioritizeNitro ? "nitro" : "about_me"}
|
||||
onChange={(value: string) => { storage.prioritizeNitro = value === "nitro"; }}
|
||||
>
|
||||
<TableRadioRow label="Nitro" value="nitro" />
|
||||
<TableRadioRow label="About Me" value="about_me" />
|
||||
</TableRadioGroup>
|
||||
<FormSwitchRow
|
||||
label="Hide Builder"
|
||||
subLabel="Hide the FPTE Builder in the User Profile and Server Profiles settings pages"
|
||||
|
|
@ -33,7 +31,7 @@ export function Settings() {
|
|||
value={!!storage.forceFallbackEffectPicker}
|
||||
onValueChange={value => { storage.forceFallbackEffectPicker = value; }}
|
||||
/>
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { React, ReactNative, clipboard } from "@vendetta/metro/common";
|
||||
import { findByProps, findByName, findByPropsAll, findByNameAll } from "@vendetta/metro";
|
||||
import { showToast } from "@vendetta/ui/toasts";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TableRowGroup, TextInput } from "@shared/ui/table";
|
||||
import SettingsScaffold from "@shared/ui/SettingsScaffold";
|
||||
import NoteBox from "@shared/ui/NoteBox";
|
||||
import PrimaryButton from "@shared/ui/PrimaryButton";
|
||||
|
|
@ -14,7 +14,6 @@ import { captureFluxEvents } from "../lib/fluxLogger";
|
|||
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.
|
||||
|
|
@ -24,7 +23,7 @@ function FullScanSection() {
|
|||
const [summary, setSummary] = React.useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<FormSection title="Full diagnostic scan">
|
||||
<TableRowGroup title="Full diagnostic scan">
|
||||
<View style={{ paddingHorizontal: 16, paddingVertical: 8 }}>
|
||||
<PrimaryButton
|
||||
label="Run scan & copy full report"
|
||||
|
|
@ -40,7 +39,7 @@ function FullScanSection() {
|
|||
<Text style={{ marginTop: 8, fontSize: 12.5, opacity: 0.7, color: textColor() }}>{summary}</Text>
|
||||
)}
|
||||
</View>
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -63,10 +62,10 @@ function EvalSection() {
|
|||
};
|
||||
|
||||
return (
|
||||
<FormSection title="Eval">
|
||||
<TableRowGroup title="Eval">
|
||||
<View style={{ paddingHorizontal: 16, paddingVertical: 8 }}>
|
||||
<FormInput
|
||||
title="Code"
|
||||
<TextInput
|
||||
label="Code"
|
||||
placeholder={'e.g. return findByProps("getRootNavigationRef")'}
|
||||
value={code}
|
||||
onChange={setCode}
|
||||
|
|
@ -98,7 +97,7 @@ function EvalSection() {
|
|||
FluxDispatcher, and window. Nothing runs until you tap the button.
|
||||
</Text>
|
||||
</View>
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -125,16 +124,16 @@ function FluxLoggerSection() {
|
|||
};
|
||||
|
||||
return (
|
||||
<FormSection title="Flux event logger">
|
||||
<TableRowGroup title="Flux event logger">
|
||||
<View style={{ paddingHorizontal: 16, paddingVertical: 8 }}>
|
||||
<FormInput
|
||||
title="Filter (optional substring, e.g. GUILD)"
|
||||
<TextInput
|
||||
label="Filter (optional substring, e.g. GUILD)"
|
||||
placeholder="Leave blank to capture everything"
|
||||
value={filter}
|
||||
onChange={setFilter}
|
||||
/>
|
||||
<FormInput
|
||||
title="Capture duration (seconds)"
|
||||
<TextInput
|
||||
label="Capture duration (seconds)"
|
||||
placeholder="10"
|
||||
value={seconds}
|
||||
onChange={setSeconds}
|
||||
|
|
@ -158,7 +157,7 @@ function FluxLoggerSection() {
|
|||
instead of guessing from decompiled source.
|
||||
</Text>
|
||||
</View>
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -186,16 +185,16 @@ function RenderLoggerSection() {
|
|||
};
|
||||
|
||||
return (
|
||||
<FormSection title="Component render/props logger">
|
||||
<TableRowGroup title="Component render/props logger">
|
||||
<View style={{ paddingHorizontal: 16, paddingVertical: 8 }}>
|
||||
<FormInput
|
||||
title="Component name (findByTypeName / findByName)"
|
||||
<TextInput
|
||||
label="Component name (findByTypeName / findByName)"
|
||||
placeholder="e.g. YouBarNotificationsButton"
|
||||
value={componentName}
|
||||
onChange={setComponentName}
|
||||
/>
|
||||
<FormInput title="Max renders to capture" placeholder="3" value={maxCalls} onChange={setMaxCalls} keyboardType="numeric" />
|
||||
<FormInput title="Give up after (seconds)" placeholder="15" value={seconds} onChange={setSeconds} keyboardType="numeric" />
|
||||
<TextInput label="Max renders to capture" placeholder="3" value={maxCalls} onChange={setMaxCalls} keyboardType="numeric" />
|
||||
<TextInput label="Give up after (seconds)" placeholder="15" value={seconds} onChange={setSeconds} keyboardType="numeric" />
|
||||
<PrimaryButton
|
||||
label={capturing ? "Waiting for renders..." : "Start capture"}
|
||||
style={{ marginTop: 8 }}
|
||||
|
|
@ -214,7 +213,7 @@ function RenderLoggerSection() {
|
|||
property to intercept its own calls through.
|
||||
</Text>
|
||||
</View>
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -242,10 +241,10 @@ function RawSearchSection() {
|
|||
};
|
||||
|
||||
return (
|
||||
<FormSection title="Raw search (bypasses the negative-result cache)">
|
||||
<TableRowGroup title="Raw search (bypasses the negative-result cache)">
|
||||
<View style={{ paddingHorizontal: 16, paddingVertical: 8 }}>
|
||||
<FormInput
|
||||
title="rawFindByProps - comma-separated prop names"
|
||||
<TextInput
|
||||
label="rawFindByProps - comma-separated prop names"
|
||||
placeholder="e.g. getRootNavigationRef"
|
||||
value={propQuery}
|
||||
onChange={setPropQuery}
|
||||
|
|
@ -259,21 +258,21 @@ function RawSearchSection() {
|
|||
}}
|
||||
/>
|
||||
|
||||
<FormInput title="rawFindByName" placeholder="e.g. YouBarNotificationsButton" value={nameQuery} onChange={setNameQuery} />
|
||||
<TextInput label="rawFindByName" placeholder="e.g. YouBarNotificationsButton" value={nameQuery} onChange={setNameQuery} />
|
||||
<PrimaryButton
|
||||
label="Search & copy result"
|
||||
style={{ marginTop: 8, marginBottom: 16 }}
|
||||
onPress={() => nameQuery.trim() && run(`rawFindByName("${nameQuery.trim()}")`, () => rawFindByName(nameQuery.trim()))}
|
||||
/>
|
||||
|
||||
<FormInput title="rawFindByTypeName" placeholder="e.g. YouBarNotificationsButton" value={typeQuery} onChange={setTypeQuery} />
|
||||
<TextInput label="rawFindByTypeName" placeholder="e.g. YouBarNotificationsButton" value={typeQuery} onChange={setTypeQuery} />
|
||||
<PrimaryButton
|
||||
label="Search & copy result"
|
||||
style={{ marginTop: 8, marginBottom: 16 }}
|
||||
onPress={() => typeQuery.trim() && run(`rawFindByTypeName("${typeQuery.trim()}")`, () => rawFindByTypeName(typeQuery.trim()))}
|
||||
/>
|
||||
|
||||
<FormInput title="rawFindByStoreName" placeholder="e.g. GuildStore" value={storeQuery} onChange={setStoreQuery} />
|
||||
<TextInput label="rawFindByStoreName" placeholder="e.g. GuildStore" value={storeQuery} onChange={setStoreQuery} />
|
||||
<PrimaryButton
|
||||
label="Search & copy result"
|
||||
style={{ marginTop: 8 }}
|
||||
|
|
@ -290,7 +289,7 @@ function RawSearchSection() {
|
|||
that loads your target module will actually find it.
|
||||
</Text>
|
||||
</View>
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -362,7 +361,7 @@ function FiberCaptureSection() {
|
|||
};
|
||||
|
||||
return (
|
||||
<FormSection title="Fiber capture (for Eval)">
|
||||
<TableRowGroup 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, color: textColor() }} selectable>{status}</Text>
|
||||
|
|
@ -375,13 +374,13 @@ function FiberCaptureSection() {
|
|||
module-registry guessing) from window.__keyInspectorFiberRoot afterward.
|
||||
</Text>
|
||||
</View>
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
);
|
||||
}
|
||||
|
||||
function ApiTreeSection() {
|
||||
return (
|
||||
<FormSection title="Full Vendetta API tree">
|
||||
<TableRowGroup title="Full Vendetta API tree">
|
||||
<View style={{ paddingHorizontal: 16, paddingVertical: 8 }}>
|
||||
<PrimaryButton
|
||||
label="Copy the entire plugin API tree"
|
||||
|
|
@ -398,7 +397,7 @@ function ApiTreeSection() {
|
|||
are searchable individually below, since dumping all of them would be enormous).
|
||||
</Text>
|
||||
</View>
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -477,10 +476,10 @@ function ManualSearchSection() {
|
|||
};
|
||||
|
||||
return (
|
||||
<FormSection title="One-off search">
|
||||
<TableRowGroup title="One-off search">
|
||||
<View style={{ paddingHorizontal: 16, paddingVertical: 8 }}>
|
||||
<FormInput
|
||||
title="findByProps - comma-separated prop names"
|
||||
<TextInput
|
||||
label="findByProps - comma-separated prop names"
|
||||
placeholder="e.g. sendMessage, sendBotMessage"
|
||||
value={propQuery}
|
||||
onChange={setPropQuery}
|
||||
|
|
@ -488,8 +487,8 @@ function ManualSearchSection() {
|
|||
<PrimaryButton label="Search first match & copy" style={{ marginTop: 8 }} onPress={searchProps} />
|
||||
<PrimaryButton label="Search ALL matches & copy" style={{ marginTop: 8, marginBottom: 16 }} onPress={searchPropsAll} />
|
||||
|
||||
<FormInput
|
||||
title="findByName - component name"
|
||||
<TextInput
|
||||
label="findByName - component name"
|
||||
placeholder="e.g. ThemedRolePill"
|
||||
value={nameQuery}
|
||||
onChange={setNameQuery}
|
||||
|
|
@ -504,7 +503,7 @@ function ManualSearchSection() {
|
|||
component actually on screen).
|
||||
</Text>
|
||||
</View>
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { React, ReactNative } from "@vendetta/metro/common";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { storage } from "@vendetta/plugin";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TableRowGroup, TextInput } from "@shared/ui/table";
|
||||
import { showToast } from "@vendetta/ui/toasts";
|
||||
import SettingsScaffold from "@shared/ui/SettingsScaffold";
|
||||
import ListSection from "@shared/ui/ListSection";
|
||||
|
|
@ -9,7 +9,6 @@ import PrimaryButton from "@shared/ui/PrimaryButton";
|
|||
import { getSnippets, saveSnippet, deleteSnippet } from "../lib/snippets";
|
||||
|
||||
const { View } = ReactNative;
|
||||
const { FormSection, FormInput } = Forms;
|
||||
|
||||
function NewSnippetForm() {
|
||||
const [name, setName] = React.useState("");
|
||||
|
|
@ -18,8 +17,8 @@ function NewSnippetForm() {
|
|||
|
||||
return (
|
||||
<View style={{ paddingHorizontal: 16, paddingBottom: 12 }}>
|
||||
<FormInput title="Name" placeholder="e.g. rules" value={name} onChange={setName} />
|
||||
<FormInput title="Text" placeholder="What /snippet should send" value={text} onChange={setText} multiline />
|
||||
<TextInput label="Name" placeholder="e.g. rules" value={name} onChange={setName} />
|
||||
<TextInput label="Text" placeholder="What /snippet should send" value={text} onChange={setText} multiline />
|
||||
<PrimaryButton
|
||||
label="Save snippet"
|
||||
disabled={!canSave}
|
||||
|
|
@ -43,9 +42,9 @@ export default function Settings() {
|
|||
|
||||
return (
|
||||
<SettingsScaffold>
|
||||
<FormSection title="Add a snippet">
|
||||
<TableRowGroup title="Add a snippet">
|
||||
<NewSnippetForm />
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
<ListSection
|
||||
title="Saved snippets"
|
||||
emptyText="None yet. Add one above, then send it anywhere with /snippet name."
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
import { React, ReactNative } from "@vendetta/metro/common";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TableRowGroup } from "@shared/ui/table";
|
||||
import SettingsScaffold from "@shared/ui/SettingsScaffold";
|
||||
import NoteBox from "@shared/ui/NoteBox";
|
||||
import PrimaryButton from "@shared/ui/PrimaryButton";
|
||||
|
||||
const { View, Linking } = ReactNative;
|
||||
const { FormSection } = Forms;
|
||||
|
||||
const PRONOUNDB_URL = "https://pronoundb.org/";
|
||||
|
||||
export default function Settings() {
|
||||
return (
|
||||
<SettingsScaffold>
|
||||
<FormSection title="How this works">
|
||||
<TableRowGroup title="How this works">
|
||||
<NoteBox>
|
||||
This plugin doesn't have anything to configure here - there's no API key or toggle,
|
||||
because pronouns aren't Discord data. They come from PronounDB, a separate service
|
||||
|
|
@ -27,7 +26,7 @@ export default function Settings() {
|
|||
<View style={{ paddingHorizontal: 16, paddingTop: 4 }}>
|
||||
<PrimaryButton label="Open pronoundb.org" onPress={() => Linking.openURL(PRONOUNDB_URL)} />
|
||||
</View>
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
</SettingsScaffold>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import { React } from "@vendetta/metro/common";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { storage } from "@vendetta/plugin";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TableRowGroup, TextInput, TableSwitchRow } from "@shared/ui/table";
|
||||
import SettingsScaffold from "@shared/ui/SettingsScaffold";
|
||||
import ColorInput from "@shared/ui/ColorInput";
|
||||
import NoteBox from "@shared/ui/NoteBox";
|
||||
|
||||
const { FormSection, FormInput, FormSwitchRow } = Forms;
|
||||
|
||||
// The ring patch itself does a generic colors[status] lookup, not one hardcoded to three statuses -
|
||||
// offline just needed its own configurable entry here to work the same way the other three already
|
||||
// do. Discord doesn't show a colored dot for offline at all normally, so this is opt-in (blank by
|
||||
|
|
@ -21,8 +19,8 @@ const STATUSES: { key: string; label: string; defaultColor?: string }[] = [
|
|||
|
||||
function numberInput(title: string, key: "ringThickness", placeholder: string) {
|
||||
return (
|
||||
<FormInput
|
||||
title={title}
|
||||
<TextInput
|
||||
label={title}
|
||||
placeholder={placeholder}
|
||||
value={String(storage[key] ?? placeholder)}
|
||||
onChange={(v: string) => {
|
||||
|
|
@ -34,6 +32,7 @@ function numberInput(title: string, key: "ringThickness", placeholder: string) {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
export default function Settings() {
|
||||
useProxy(storage);
|
||||
storage.colors ??= {};
|
||||
|
|
@ -47,14 +46,14 @@ export default function Settings() {
|
|||
avatar sizes seen in each of those contexts, so other unrelated circular UI elements are
|
||||
left alone.
|
||||
</NoteBox>
|
||||
<FormSection title="Enable">
|
||||
<FormSwitchRow
|
||||
<TableRowGroup title="Enable">
|
||||
<TableSwitchRow
|
||||
label="Draw ring around avatars"
|
||||
value={!!storage.enabled}
|
||||
onValueChange={(v: boolean) => { storage.enabled = v; }}
|
||||
/>
|
||||
</FormSection>
|
||||
<FormSection title="Ring colors">
|
||||
</TableRowGroup>
|
||||
<TableRowGroup title="Ring colors">
|
||||
{STATUSES.map(({ key, label, defaultColor }) => (
|
||||
<ColorInput
|
||||
key={key}
|
||||
|
|
@ -66,10 +65,10 @@ export default function Settings() {
|
|||
}}
|
||||
/>
|
||||
))}
|
||||
</FormSection>
|
||||
<FormSection title="Ring size">
|
||||
</TableRowGroup>
|
||||
<TableRowGroup title="Ring size">
|
||||
{numberInput("Ring thickness", "ringThickness", "2")}
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
</SettingsScaffold>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,48 @@
|
|||
import { React } from "@vendetta/metro/common";
|
||||
import { storage } from "@vendetta/plugin";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TableRowGroup, TableSwitchRow, TextInput } from "@shared/ui/table";
|
||||
import SettingsScaffold from "@shared/ui/SettingsScaffold";
|
||||
import NoteBox from "@shared/ui/NoteBox";
|
||||
|
||||
const { FormSection, FormSwitchRow, FormInput } = Forms;
|
||||
|
||||
export default function Settings() {
|
||||
useProxy(storage);
|
||||
|
||||
return (
|
||||
<SettingsScaffold>
|
||||
<FormSection title="Where to show the top role color">
|
||||
<FormSwitchRow
|
||||
<TableRowGroup title="Where to show the top role color">
|
||||
<TableSwitchRow
|
||||
label="Typing indicator"
|
||||
subLabel='Color names in the "X is typing..." bar'
|
||||
value={!storage.hideTyping}
|
||||
onValueChange={(v: boolean) => { storage.hideTyping = !v; }}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
<TableSwitchRow
|
||||
label="Mentions"
|
||||
subLabel="Color @mentions in chat messages"
|
||||
value={!storage.noMention}
|
||||
onValueChange={(v: boolean) => { storage.noMention = !v; }}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
<TableSwitchRow
|
||||
label="Member list role headers"
|
||||
subLabel="Color the role section headers in the member list"
|
||||
value={!storage.noRole}
|
||||
onValueChange={(v: boolean) => { storage.noRole = !v; }}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
<TableSwitchRow
|
||||
label="Voice channel participant names"
|
||||
value={!storage.noVoice}
|
||||
onValueChange={(v: boolean) => { storage.noVoice = !v; }}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
<TableSwitchRow
|
||||
label="Message text"
|
||||
subLabel="Tints message text toward the author's role color - disables text selection on tinted messages"
|
||||
value={storage.chatInterpolation > 0}
|
||||
onValueChange={(v: boolean) => { storage.chatInterpolation = v ? 60 : 0; }}
|
||||
/>
|
||||
{storage.chatInterpolation > 0 && (
|
||||
<FormInput
|
||||
title="Message text strength (0-100)"
|
||||
<TextInput
|
||||
label="Message text strength (0-100)"
|
||||
value={String(storage.chatInterpolation)}
|
||||
keyboardType="numeric"
|
||||
onChange={(v: string) => {
|
||||
|
|
@ -52,7 +50,7 @@ export default function Settings() {
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
<NoteBox>
|
||||
Some of these surfaces (especially the member list headers) rely on component internals
|
||||
Discord doesn't officially expose, so a future Discord update may silently turn one of
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { findByName, findByProps } from "@vendetta/metro";
|
||||
import { React, ReactNative, clipboard } from "@vendetta/metro/common";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TextInput } from "@shared/ui/table";
|
||||
import { showToast } from "@vendetta/ui/toasts";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import { resolveSemanticColorSafe } from "@shared/lib/color";
|
||||
|
|
@ -17,7 +17,6 @@ const modalCloseButton =
|
|||
const Navigator = findByName("Navigator") ?? findByProps("Navigator")?.Navigator;
|
||||
|
||||
const { View, Text, ScrollView, TouchableOpacity } = ReactNative;
|
||||
const { FormInput } = Forms;
|
||||
|
||||
const PAGE_SIZE = 100;
|
||||
|
||||
|
|
@ -172,8 +171,8 @@ function LogList() {
|
|||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<View style={{ padding: 16 }}>
|
||||
<FormInput
|
||||
title="Search"
|
||||
<TextInput
|
||||
label="Search"
|
||||
placeholder="Search content, author, channel/server/user ID..."
|
||||
value={query}
|
||||
onChange={setQuery}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { React, ReactNative, clipboard } from "@vendetta/metro/common";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TableRowGroup, TableSwitchRow, TableRow, TextInput } from "@shared/ui/table";
|
||||
import { showToast } from "@vendetta/ui/toasts";
|
||||
import SettingsScaffold from "@shared/ui/SettingsScaffold";
|
||||
import NoteBox from "@shared/ui/NoteBox";
|
||||
|
|
@ -9,7 +9,6 @@ import modules from "../modules";
|
|||
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
|
||||
|
|
@ -50,7 +49,7 @@ function ModuleSettingRow({ module, settingKey }: { module: AnyModule; settingKe
|
|||
|
||||
if (setting.type === "toggle") {
|
||||
return (
|
||||
<FormSwitchRow
|
||||
<TableSwitchRow
|
||||
label={setting.label}
|
||||
subLabel={subLabel}
|
||||
value={!!value}
|
||||
|
|
@ -65,7 +64,7 @@ function ModuleSettingRow({ module, settingKey }: { module: AnyModule; settingKe
|
|||
|
||||
if (setting.type === "button") {
|
||||
return (
|
||||
<FormRow
|
||||
<TableRow
|
||||
label={setting.label}
|
||||
subLabel={subLabel}
|
||||
disabled={disabled}
|
||||
|
|
@ -76,8 +75,8 @@ function ModuleSettingRow({ module, settingKey }: { module: AnyModule; settingKe
|
|||
|
||||
if (setting.type === "text") {
|
||||
return (
|
||||
<FormInput
|
||||
title={setting.label}
|
||||
<TextInput
|
||||
label={setting.label}
|
||||
placeholder={setting.placeholder}
|
||||
value={typeof value === "string" ? value : ""}
|
||||
editable={!disabled}
|
||||
|
|
@ -90,7 +89,7 @@ function ModuleSettingRow({ module, settingKey }: { module: AnyModule; settingKe
|
|||
|
||||
// "choose" - cycles through choices on tap instead of a full picker sheet.
|
||||
return (
|
||||
<FormRow
|
||||
<TableRow
|
||||
label={setting.label}
|
||||
subLabel={[subLabel, value].filter(Boolean).join(" - ")}
|
||||
disabled={disabled}
|
||||
|
|
@ -116,8 +115,8 @@ function ModuleSection({ module }: { module: AnyModule }) {
|
|||
const errorEntries = Object.entries(module.errors);
|
||||
|
||||
return (
|
||||
<FormSection title={module.label}>
|
||||
<FormSwitchRow
|
||||
<TableRowGroup title={module.label}>
|
||||
<TableSwitchRow
|
||||
label="Enabled"
|
||||
subLabel={module.meta.sublabel}
|
||||
value={!!module.storage.enabled}
|
||||
|
|
@ -127,7 +126,7 @@ function ModuleSection({ module }: { module: AnyModule }) {
|
|||
<ModuleSettingRow key={key} module={module} settingKey={key} />
|
||||
))}
|
||||
{errorEntries.length > 0 && (
|
||||
<FormRow
|
||||
<TableRow
|
||||
label={`${errorEntries.length} error${errorEntries.length === 1 ? "" : "s"} - tap to copy`}
|
||||
onPress={() => {
|
||||
const report = errorEntries.map(([label, stack]) => `[${module.label}] ${label}\n${stack}`).join("\n\n");
|
||||
|
|
@ -136,7 +135,7 @@ function ModuleSection({ module }: { module: AnyModule }) {
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -212,8 +211,8 @@ export default function Settings() {
|
|||
</NoteBox>
|
||||
|
||||
<View style={{ paddingHorizontal: 16 }}>
|
||||
<FormInput
|
||||
title="Search"
|
||||
<TextInput
|
||||
label="Search"
|
||||
placeholder="Search modules and settings..."
|
||||
value={query}
|
||||
onChange={setQuery}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { React } from "@vendetta/metro/common";
|
||||
import { storage } from "@vendetta/plugin";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TableRowGroup, TableSwitchRow } from "@shared/ui/table";
|
||||
import SettingsScaffold from "./SettingsScaffold";
|
||||
import NoteBox from "./NoteBox";
|
||||
import { restart } from "../index";
|
||||
|
||||
const { FormSection, FormSwitchRow } = Forms;
|
||||
|
||||
export default function Settings() {
|
||||
useProxy(storage);
|
||||
|
|
@ -19,8 +18,8 @@ export default function Settings() {
|
|||
menu, the chat bubble jumps to DMs, the + button creates or joins a server through
|
||||
Discord's own modal, and the check button marks every channel in every server as read.
|
||||
</NoteBox>
|
||||
<FormSection title="Layout">
|
||||
<FormSwitchRow
|
||||
<TableRowGroup title="Layout">
|
||||
<TableSwitchRow
|
||||
label="Hide the left server rail"
|
||||
subLabel="Your servers and DMs already live in the drawer, so the rail is redundant"
|
||||
value={storage.hideGuildsBar !== false}
|
||||
|
|
@ -29,39 +28,39 @@ export default function Settings() {
|
|||
restart();
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
<TableSwitchRow
|
||||
label="Unread badges"
|
||||
subLabel="Show mention counts and unread dots on server icons in the drawer"
|
||||
value={storage.showUnreadBadges !== false}
|
||||
onValueChange={(v: boolean) => { storage.showUnreadBadges = v; }}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
<TableSwitchRow
|
||||
label="Show server names"
|
||||
subLabel="Word-wrapped name below each server's icon"
|
||||
value={storage.showGuildNames !== false}
|
||||
onValueChange={(v: boolean) => { storage.showGuildNames = v; }}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
<TableSwitchRow
|
||||
label="Hide the DMs tile"
|
||||
subLabel="Remove the DMs tile from the drawer entirely"
|
||||
value={!!storage.hideDmTile}
|
||||
onValueChange={(v: boolean) => { storage.hideDmTile = v; }}
|
||||
/>
|
||||
</FormSection>
|
||||
<FormSection title="Folders">
|
||||
<FormSwitchRow
|
||||
</TableRowGroup>
|
||||
<TableRowGroup title="Folders">
|
||||
<TableSwitchRow
|
||||
label="Auto-collapse other folders"
|
||||
subLabel="Opening a folder collapses any other folder that was already open"
|
||||
value={!!storage.autoCollapseFolders}
|
||||
onValueChange={(v: boolean) => { storage.autoCollapseFolders = v; }}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
<TableSwitchRow
|
||||
label="Hide icons in collapsed folders"
|
||||
subLabel="Show a plain folder icon instead of a 4-server preview"
|
||||
value={!!storage.hideFolderIcons}
|
||||
onValueChange={(v: boolean) => { storage.hideFolderIcons = v; }}
|
||||
/>
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
<NoteBox>
|
||||
The folder options above are ported from fres621's BetterFolders - its other half
|
||||
(hiding icons in the native left server rail) patched a component that no longer
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { React } from "@vendetta/metro/common";
|
||||
import { storage } from "@vendetta/plugin";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TableRowGroup, TableSwitchRow, TextInput } from "@shared/ui/table";
|
||||
import SettingsScaffold from "@shared/ui/SettingsScaffold";
|
||||
import ColorInput from "@shared/ui/ColorInput";
|
||||
import { TAG_DEFINITIONS, tagSettings } from "../../lib/getTag";
|
||||
|
||||
const { FormSection, FormSwitchRow, FormInput } = Forms;
|
||||
|
||||
function TagSettingsSection({ id, defaultText, defaultColor }: { id: string; defaultText: string; defaultColor: string }) {
|
||||
const settings = tagSettings(id);
|
||||
|
|
@ -15,20 +14,20 @@ function TagSettingsSection({ id, defaultText, defaultColor }: { id: string; def
|
|||
const enabled = settings.enabled !== false;
|
||||
|
||||
return (
|
||||
<FormSection title={defaultText}>
|
||||
<FormSwitchRow
|
||||
<TableRowGroup title={defaultText}>
|
||||
<TableSwitchRow
|
||||
label="Show this tag"
|
||||
value={enabled}
|
||||
onValueChange={(v: boolean) => { settings.enabled = v; }}
|
||||
/>
|
||||
<FormInput
|
||||
title="Tag text"
|
||||
<TextInput
|
||||
label="Tag text"
|
||||
placeholder={defaultText}
|
||||
value={settings.text ?? ""}
|
||||
editable={enabled}
|
||||
onChange={(v: string) => { settings.text = v; }}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
<TableSwitchRow
|
||||
label="Custom color"
|
||||
subLabel={`Default: ${defaultColor}`}
|
||||
value={!!settings.useCustomColor}
|
||||
|
|
@ -43,7 +42,7 @@ function TagSettingsSection({ id, defaultText, defaultColor }: { id: string; def
|
|||
onChange={(v: string) => { settings.color = v; }}
|
||||
/>
|
||||
)}
|
||||
<FormSwitchRow
|
||||
<TableSwitchRow
|
||||
label="Gradient"
|
||||
subLabel="Member list & profile only, chat tags stay solid"
|
||||
value={!!settings.useGradient}
|
||||
|
|
@ -57,7 +56,7 @@ function TagSettingsSection({ id, defaultText, defaultColor }: { id: string; def
|
|||
onChange={(v: string) => { settings.gradientColor = v; }}
|
||||
/>
|
||||
)}
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -66,14 +65,14 @@ export default function Settings() {
|
|||
|
||||
return (
|
||||
<SettingsScaffold>
|
||||
<FormSection title="General">
|
||||
<FormSwitchRow
|
||||
<TableRowGroup title="General">
|
||||
<TableSwitchRow
|
||||
label="Use top role color"
|
||||
subLabel="Used when a tag has no custom color set"
|
||||
value={!!storage.useRoleColor}
|
||||
onValueChange={(v: boolean) => { storage.useRoleColor = v; }}
|
||||
/>
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
{TAG_DEFINITIONS.map((def) => (
|
||||
<TagSettingsSection key={def.id} id={def.id} defaultText={def.defaultText} defaultColor={def.defaultColor} />
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { React } from "@vendetta/metro/common";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TableRowGroup, TableRow } from "@shared/ui/table";
|
||||
import { showToast } from "@vendetta/ui/toasts";
|
||||
import { clipboard } from "@vendetta/metro/common";
|
||||
import SettingsScaffold from "@shared/ui/SettingsScaffold";
|
||||
|
|
@ -7,8 +7,6 @@ import NoteBox from "@shared/ui/NoteBox";
|
|||
import PrimaryButton from "@shared/ui/PrimaryButton";
|
||||
import { getLastDetection } from "../lib/diagnostics";
|
||||
|
||||
const { FormSection, FormRow } = Forms;
|
||||
|
||||
const STRATEGY_LABEL: Record<string, string> = {
|
||||
buttons: "Strategy 1 (button list) - working normally",
|
||||
actionSheetRow: "Strategy 2 (row group) - working normally",
|
||||
|
|
@ -21,9 +19,9 @@ export default function Settings() {
|
|||
|
||||
return (
|
||||
<SettingsScaffold>
|
||||
<FormSection title="Diagnostics">
|
||||
<TableRowGroup title="Diagnostics">
|
||||
{detection ? (
|
||||
<FormRow
|
||||
<TableRow
|
||||
label={STRATEGY_LABEL[detection.strategy] ?? detection.strategy}
|
||||
subLabel={`Last checked ${new Date(detection.timestamp).toLocaleString()}${detection.detail ? ` - ${detection.detail}` : ""}`}
|
||||
/>
|
||||
|
|
@ -41,7 +39,7 @@ export default function Settings() {
|
|||
showToast("Copied - paste this if you're reporting an issue", undefined);
|
||||
}}
|
||||
/>
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
<NoteBox>
|
||||
"View Raw" is added to the message long-press menu by detecting Discord's internal
|
||||
action-sheet layout, which isn't an official API and can change between app versions.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { React } from "@vendetta/metro/common";
|
||||
import { storage } from "@vendetta/plugin";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TableRowGroup, TableSwitchRow } from "@shared/ui/table";
|
||||
import SettingsScaffold from "@shared/ui/SettingsScaffold";
|
||||
import NoteBox from "@shared/ui/NoteBox";
|
||||
import { updateYouBar } from "../patches/youBarButtons";
|
||||
|
||||
const { FormSection, FormSwitchRow } = Forms;
|
||||
|
||||
export default function Settings() {
|
||||
useProxy(storage);
|
||||
|
|
@ -21,8 +20,8 @@ export default function Settings() {
|
|||
original, this patches the YouBar the moment it loads - if you enable it while
|
||||
Discord's already running, you may need to restart once for the buttons to appear.
|
||||
</NoteBox>
|
||||
<FormSection title="Buttons">
|
||||
<FormSwitchRow
|
||||
<TableRowGroup title="Buttons">
|
||||
<TableSwitchRow
|
||||
label="Direct Messages button"
|
||||
subLabel="Show the DM button in the YouBar"
|
||||
value={!!storage.showDMButton}
|
||||
|
|
@ -31,7 +30,7 @@ export default function Settings() {
|
|||
updateYouBar();
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
<TableSwitchRow
|
||||
label="Settings button"
|
||||
subLabel="Show the Settings button in the YouBar"
|
||||
value={!!storage.showSettingsButton}
|
||||
|
|
@ -40,7 +39,7 @@ export default function Settings() {
|
|||
updateYouBar();
|
||||
}}
|
||||
/>
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
</SettingsScaffold>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { React, ReactNative } from "@vendetta/metro/common";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { TableRow, TableRowGroup } from "./table";
|
||||
import NoteBox from "./NoteBox";
|
||||
|
||||
const { Text } = ReactNative;
|
||||
const { FormSection, FormRow } = Forms;
|
||||
|
||||
export interface ListItem {
|
||||
key: string;
|
||||
|
|
@ -19,12 +18,12 @@ export interface ListItem {
|
|||
*/
|
||||
export default function ListSection({ title, items, emptyText }: { title: string; items: ListItem[]; emptyText: string }) {
|
||||
return (
|
||||
<FormSection title={title}>
|
||||
<TableRowGroup title={title}>
|
||||
{items.length === 0 ? (
|
||||
<NoteBox>{emptyText}</NoteBox>
|
||||
) : (
|
||||
items.map((item) => (
|
||||
<FormRow
|
||||
<TableRow
|
||||
key={item.key}
|
||||
label={item.label}
|
||||
subLabel={item.subLabel}
|
||||
|
|
@ -33,6 +32,6 @@ export default function ListSection({ title, items, emptyText }: { title: string
|
|||
/>
|
||||
))
|
||||
)}
|
||||
</FormSection>
|
||||
</TableRowGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
29
shared/ui/table.ts
Normal file
29
shared/ui/table.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { findByProps } from "@vendetta/metro";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
|
||||
/**
|
||||
* Discord's redesigned "Table" row family (TableRow/TableRowGroup/TableSwitchRow/...) replaced
|
||||
* the old Forms.Form* components in the app itself a while ago - Forms.* still works (Vendetta's
|
||||
* compat layer maps it to the legacy components, see metro/common/components.ts in the loader),
|
||||
* but it's the deprecated skin. These are found the same way ViewRaw finds ActionSheetRow: a
|
||||
* direct findByProps lookup for the real component, since the vendetta.ui.components compat
|
||||
* object never exposed the Table family at all. Falls back to the matching legacy Form
|
||||
* component if a lookup ever comes back empty, same "isolate the failure" approach as the rest
|
||||
* of this repo - the fallback's prop names don't perfectly match (e.g. `icon` vs `leading`), so a
|
||||
* few things would render without an icon/arrow in that fallback path, but nothing crashes.
|
||||
*/
|
||||
const find = (prop: string): any => findByProps(prop)?.[prop];
|
||||
|
||||
const RealTableRow: any = find("TableRow");
|
||||
|
||||
export const TableRow: any = RealTableRow ?? Forms.FormRow;
|
||||
export const TableRowGroup: any = find("TableRowGroup") ?? Forms.FormSection;
|
||||
export const TableSwitchRow: any = find("TableSwitchRow") ?? Forms.FormSwitchRow;
|
||||
export const TableRadioRow: any = find("TableRadioRow") ?? Forms.FormRadioRow;
|
||||
export const TableRadioGroup: any = find("TableRadioGroup") ?? Forms.FormRadioGroup;
|
||||
export const TableCheckboxRow: any = find("TableCheckboxRow") ?? Forms.FormCheckboxRow;
|
||||
export const TextInput: any = find("TextInput") ?? Forms.FormInput;
|
||||
|
||||
export const TableRowIcon: any = RealTableRow?.Icon ?? Forms.FormRow.Icon;
|
||||
export const TableRowArrow: any = RealTableRow?.Arrow ?? Forms.FormRow.Arrow;
|
||||
export const TableRowTrailingText: any = RealTableRow?.TrailingText;
|
||||
Loading…
Add table
Reference in a new issue