Skip to content

Commit

Permalink
feat: UNFINISHED UI mock manifest declared plugin settings
Browse files Browse the repository at this point in the history
I received the news while working on this, perhaps this is the last Bunny commit
  • Loading branch information
pylixonly committed Sep 14, 2024
1 parent f1c2a60 commit 2b93038
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 38 deletions.
174 changes: 156 additions & 18 deletions src/core/ui/settings/pages/Plugins/sheets/PluginInfoActionSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,180 @@ import PluginReporter from "@core/reporter/PluginReporter";
import { UnifiedPluginModel } from "@core/ui/settings/pages/Plugins/models/UnifiedPluginModel";
import { showConfirmationAlert } from "@core/vendetta/ui/alerts";
import PluginManager from "@lib/addons/plugins/PluginManager";
import { BunnyPluginManifest, OptionDefinition } from "@lib/addons/plugins/types";
import { findAssetId } from "@lib/api/assets";
import { purgeStorage } from "@lib/api/storage";
import { Codeblock } from "@lib/ui/components";
import { lazyDestructure } from "@lib/utils/lazy";
import { findByProps } from "@metro";
import { clipboard } from "@metro/common";
import { ActionSheet, Button, Card, Stack, TableRow, TableRowGroup, TableSwitch, Text } from "@metro/common/components";
import { ActionSheet, Button, Card, Stack, TableCheckboxRow, TableRadioGroup, TableRadioRow, TableRow, TableRowGroup, TableSwitch, TableSwitchRow, Text, TextArea, TextInput } from "@metro/common/components";
import { hideSheet } from "@ui/sheets";
import { showToast } from "@ui/toasts";
import { useReducer } from "react";
import { useReducer, useState } from "react";
import { View } from "react-native";

import { TitleComponent } from "./TitleComponent";
import { PluginInfoActionSheetProps } from "./types";

const { ScrollView } = lazyDestructure(() => findByProps("NativeViewGestureHandler"));

const TEMP_OPT: BunnyPluginManifest["options"] & {} = {
duh: {
type: "string",
label: "Duh.",
description: "Do the actual duh that duhing the duh.",
icon: "CopyIcon",
placeholder: "nhh"
},
dah: {
type: "boolean",
label: "Dah.",
description: "The beauty of the dah-wn",
icon: "DownloadIcon"
},
deh: {
type: "select",
label: "Deh.",
description: "Do that lah, deh!",
options: [
{
label: "kldsjkldf",
description: "kldjklfd, skdjfl, sjkdlf!",
value: 9
},
{
label: "kldnjhjkhsjkldf",
description: "dlkjf, sdkfsd, kjfasif~",
value: 99
}
]
},
dih: {
type: "radio",
label: "Dih",
description: "Wutchu dih dih ting?",
options: [
{
label: "Duhdshf",
description: "Djfdkfifoe kdfof dsi d & dkkjlsdaf!",
value: 0
},
{
label: "KJfjk",
description: "JKjkjfdljklfdkjlfd kjlfd jkldffjkl d.",
value: 1
}
]
}
};

function OptionDefRow({ opt }: { opt: OptionDefinition }) {
const [current, setCurrent] = useState<string | undefined>();

switch (opt.type) {
case "string":
const Input = opt.textArea === true ? TextArea : TextInput;
const isValid = () => opt.regexValidation ? current?.match(opt.regexValidation) : true;

return <TableRow
label={<Input
size="sm" // TextInput specific, but it's fine
label={opt.label}
placeholder={opt.placeholder}
value={current}
onChange={(v: string) => setCurrent(v)}
state={isValid() ? "error" : undefined}
errorMessage={isValid() ? undefined : "Invalid input"}
/>}
subLabel={opt.description}
icon={getIcon(opt.icon)}
/>;
case "boolean":
return <TableSwitchRow
label={opt.label}
subLabel={opt.description}
icon={getIcon(opt.icon)}
value={current === "true"}
onValueChange={() => {
// Absolute horror
setCurrent(v => String(String(v) !== "true"));
}}
/>;
case "select":
return <Card start={false} end={false} variant="secondary">
<TableRowGroup title={opt.label}>
{opt.options.map(def => {
return <TableCheckboxRow
label={def.label}
subLabel={def.description}
icon={getIcon(def.icon)}
checked={def.value === current}
onPress={() => {}}
/>;
})}
</TableRowGroup>
<Text style={{ marginTop: 8 }} color="text-secondary" variant="text-sm/normal">
{opt.description}
</Text>
</Card>;
case "radio":
return <Card start={false} end={false} variant="secondary">
<TableRadioGroup title={opt.label} value={String(opt.options[0].value)} onChange={() => {}}>
{opt.options.map(def => {
return <TableRadioRow
label={def.label}
subLabel={def.description}
icon={getIcon(def.icon)}
value={String(def.value)}
/>;
})}
</TableRadioGroup>
<Text style={{ marginTop: 8 }} color="text-secondary" variant="text-sm/normal">
{opt.description}
</Text>
</Card>;
case "slider":
}
}

function getIcon(icon: OptionDefinition["icon"]) {
if (!icon) return;

return <TableRow.Icon
source={typeof icon === "string" ? findAssetId(icon) : icon}
/>;
}

function OptionSection({ plugin, navigation }: { plugin: UnifiedPluginModel, navigation: any }) {
const manifest = PluginManager.getManifest(plugin.id);
const SettingsComponent = plugin.getPluginSettingsComponent();

return <TableRowGroup title="Configurations">
{Object.entries(manifest.options ?? {} /* ?? TEMP_OPT */).map(([name, def]) => {
return <OptionDefRow opt={def} />;
})}
<TableRow
arrow={true}
label="More..."
icon={<TableRow.Icon source={findAssetId("WrenchIcon")} />}
disabled={!SettingsComponent}
onPress={() => {
hideSheet("PluginInfoActionSheet");
navigation.push("BUNNY_CUSTOM_PAGE", {
title: plugin.name,
render: SettingsComponent,
});
}}
/>
</TableRowGroup>;
}


export default function PluginInfoActionSheet({ plugin, navigation }: PluginInfoActionSheetProps) {
plugin.usePluginState();

const pluginSettings = PluginManager.settings[plugin.id];
const SettingsComponent = plugin.getPluginSettingsComponent();

return <ActionSheet>
<ScrollView style={{ paddingVertical: 8 }} contentContainerStyle={{ gap: 18 }}>
Expand All @@ -38,21 +190,7 @@ export default function PluginInfoActionSheet({ plugin, navigation }: PluginInfo
<Codeblock selectable={true}>{String(PluginReporter.getError(plugin.id))}</Codeblock>
{/* <Button style={{ marginTop: 4 }} text="See more" onPress={() => {}} /> */}
</Card>}
<TableRowGroup title="Configurations">
<TableRow
arrow={true}
label="More..."
icon={<TableRow.Icon source={findAssetId("WrenchIcon")} />}
disabled={!SettingsComponent}
onPress={() => {
hideSheet("PluginInfoActionSheet");
navigation.push("BUNNY_CUSTOM_PAGE", {
title: plugin.name,
render: SettingsComponent,
});
}}
/>
</TableRowGroup>
<OptionSection plugin={plugin} navigation={navigation} />
<TableRowGroup title="Actions">
<Stack>
<ScrollView
Expand Down
66 changes: 49 additions & 17 deletions src/lib/addons/plugins/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,6 @@ import { logger } from "@core/logger";
import { BunnyManifest } from "@lib/addons/types";
import { createStorage } from "@lib/api/storage";

export interface PluginRepo {
[id: string]: {
version: string;

// For plugin developing convenience, plugins with this on will always get fetched
alwaysFetch?: boolean;
};
}

export interface PluginRepoStorage {
[repoUrl: string]: PluginRepo;
}

export interface PluginSettingsStorage {
[pluginId: string]: {
enabled: boolean;
Expand All @@ -33,13 +20,58 @@ export interface PluginTracesStorage {
export interface BunnyPluginManifest extends BunnyManifest {
main: string;
hash: string;
options?: Record<string, OptionDefinition>;
}

export type OptionDefinition = StringOptionDefinition | BooleanOptionDefinition | SelectOptionDefinition | RadioOptionDefinition | SliderOptionDefinition | SelectOptionDefinition;

type OptionType = "string" | "boolean" | "select" | "radio" | "slider";

interface OptionDefinitionBase {
type: OptionType;
label: string;
description?: string;
icon?: string | { uri: string };
}

interface StringOptionDefinition extends OptionDefinitionBase {
type: "string";
placeholder?: string;
defaults?: string;
textArea?: boolean;
regexValidation?: string;
}

interface BooleanOptionDefinition extends OptionDefinitionBase {
type: "boolean";
defaults?: boolean;
}

export interface BunnyPluginManifestInternal extends BunnyPluginManifest {
readonly parentRepository: string;
readonly jsPath?: string;
interface SelectOptionDefinition extends OptionDefinitionBase {
type: "select";
options: SelectRadioOptionRow[];
}

interface RadioOptionDefinition extends OptionDefinitionBase {
type: "radio";
options: SelectRadioOptionRow[];
}

interface SliderOptionDefinition extends OptionDefinitionBase {
type: "slider";
points: number[];
default?: number;
}

interface SelectRadioOptionRow {
label: string;
description?: string;
icon?: string | { uri: string };
value: string | number | boolean;
default?: boolean;
}


export interface PluginInstance {
start?(): void | Promise<void>;
stop?(): void | Promise<void>;
Expand All @@ -52,7 +84,7 @@ export interface PluginInstanceInternal extends PluginInstance {

export interface BunnyPluginProperty {
readonly logger: typeof logger;
readonly manifest: BunnyPluginManifestInternal;
readonly manifest: BunnyPluginManifest;
createStorage<T extends object>(): ReturnType<typeof createStorage<T>>;
}

Expand Down
2 changes: 2 additions & 0 deletions src/metro/common/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const TableRowGroup = findProp("TableRowGroup") as t.TableRowGroup;
export const TableRadioGroup = findProp("TableRadioGroup") as t.TableRadioGroup;
export const TableRadioRow = findProp("TableRadioRow") as t.TableRadioRow;
export const TableSwitchRow = findProp("TableSwitchRow") as t.TableSwitchRow;
export const TableCheckboxRow = findProp("TableCheckboxRow") as t.TableCheckboxRow;

export const TableSwitch = findSingular("FormSwitch");
export const TableRadio = findSingular("FormRadio");
Expand Down Expand Up @@ -69,6 +70,7 @@ export const Avatar = findProp("default", "AvatarSizes", "getStatusSize");

// Inputs
export const TextInput = findSingular("TextInput") as t.TextInput;
export const TextArea = findSingular("TextArea");

// SegmentedControl
export const SegmentedControl = findProp("SegmentedControl") as t.SegmentedControl;
Expand Down
12 changes: 9 additions & 3 deletions src/metro/common/types/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ interface TableRowBaseProps {
arrow?: boolean;
label: string | ReactNode;
subLabel?: string | ReactNode;
variant?: LiteralUnion<"danger", string>
icon?: JSX.Element | Falsey;
disabled?: boolean;
trailing?: ReactNode | React.ComponentType<any>;
Expand Down Expand Up @@ -223,9 +224,7 @@ interface TableRadioGroupProps {

export type TableRadioGroup = FC<TableRadioGroupProps>;

interface TableRadioRowProps {
icon: JSX.Element;
label: string;
interface TableRadioRowProps extends TableRowBaseProps {
value: string;
}

Expand All @@ -237,3 +236,10 @@ interface TableSwitchRowProps extends TableRowBaseProps {
}

export type TableSwitchRow = FC<TableSwitchRowProps>;

interface TableCheckboxRowProps extends TableRowBaseProps {
checked: boolean;
onPress: () => void;
}

export type TableCheckboxRow = FC<TableCheckboxRowProps>;

0 comments on commit 2b93038

Please sign in to comment.