RosePlugs: build the quick-install browser
This commit is contained in:
parent
3e5943e09c
commit
986f9a6352
3 changed files with 130 additions and 5 deletions
|
|
@ -1,22 +1,19 @@
|
|||
import { logger } from "@vendetta";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import Settings from "./ui/Settings";
|
||||
import PluginsBrowser from "./ui/PluginsBrowser";
|
||||
import patchRosiesPlugsSection, { SectionRow } from "./patches/settings";
|
||||
import { discoverRosiesPlugins } from "./patches/discoverPlugins";
|
||||
|
||||
let unpatch: (() => void) | undefined;
|
||||
|
||||
function StubPluginsScreen() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildRows(): SectionRow[] {
|
||||
const rows: SectionRow[] = [
|
||||
{
|
||||
key: "ROSES_PLUGS_BROWSER",
|
||||
title: () => "Plugins",
|
||||
icon: getAssetIDByName("SettingsIcon"),
|
||||
page: StubPluginsScreen,
|
||||
page: PluginsBrowser,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
43
plugins/rose-plugs/src/ui/PluginDetailSheet.tsx
Normal file
43
plugins/rose-plugs/src/ui/PluginDetailSheet.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { React, ReactNative } from "@vendetta/metro/common";
|
||||
import { plugins as installedPlugins, installPlugin } from "@vendetta/plugins";
|
||||
import { showToast } from "@vendetta/ui/toasts";
|
||||
import PrimaryButton from "@shared/ui/PrimaryButton";
|
||||
import { NexusPlugin } from "../lib/nexusApi";
|
||||
|
||||
const { ScrollView, Text } = ReactNative;
|
||||
|
||||
export default function PluginDetailSheet({ plugin }: { plugin: NexusPlugin }) {
|
||||
const [installing, setInstalling] = React.useState(false);
|
||||
const alreadyInstalled = plugin.installUrl in installedPlugins;
|
||||
|
||||
const onInstall = React.useCallback(() => {
|
||||
setInstalling(true);
|
||||
installPlugin(plugin.installUrl)
|
||||
.then(() => {
|
||||
setInstalling(false);
|
||||
showToast(`${plugin.name} installed`);
|
||||
})
|
||||
.catch((e: any) => {
|
||||
setInstalling(false);
|
||||
showToast(`Install failed: ${e?.message ?? e}`);
|
||||
});
|
||||
}, [plugin.installUrl]);
|
||||
|
||||
return (
|
||||
<ScrollView style={{ maxHeight: 400, padding: 16 }}>
|
||||
<Text style={{ fontSize: 18, fontWeight: "700", marginBottom: 8 }}>{plugin.name}</Text>
|
||||
<Text style={{ fontSize: 13, opacity: 0.8, marginBottom: 4 }}>{plugin.category}</Text>
|
||||
<Text style={{ fontSize: 14, marginBottom: 16 }}>{plugin.tagline || plugin.description}</Text>
|
||||
{plugin.status && plugin.status !== "default" && (
|
||||
<Text style={{ fontSize: 12, fontWeight: "700", marginBottom: 16 }}>
|
||||
{plugin.status.toUpperCase()}
|
||||
</Text>
|
||||
)}
|
||||
<PrimaryButton
|
||||
label={alreadyInstalled ? "Installed" : installing ? "Installing…" : "Install"}
|
||||
disabled={alreadyInstalled || installing}
|
||||
onPress={onInstall}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
85
plugins/rose-plugs/src/ui/PluginsBrowser.tsx
Normal file
85
plugins/rose-plugs/src/ui/PluginsBrowser.tsx
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import { React, ReactNative } from "@vendetta/metro/common";
|
||||
import { plugins as installedPlugins } from "@vendetta/plugins";
|
||||
import { showCustomAlert } from "@vendetta/ui/alerts";
|
||||
import SettingsScaffold from "@shared/ui/SettingsScaffold";
|
||||
import NoteBox from "@shared/ui/NoteBox";
|
||||
import PrimaryButton from "@shared/ui/PrimaryButton";
|
||||
import { TableRow, TableRowGroup } from "@shared/ui/table";
|
||||
import { fetchNexusPlugins, NexusPlugin } from "../lib/nexusApi";
|
||||
import PluginDetailSheet from "./PluginDetailSheet";
|
||||
|
||||
const { Text } = ReactNative;
|
||||
|
||||
interface BrowserState {
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
plugins: NexusPlugin[];
|
||||
}
|
||||
|
||||
export default function PluginsBrowser() {
|
||||
const [state, setState] = React.useState<BrowserState>({ loading: true, error: null, plugins: [] });
|
||||
|
||||
const load = React.useCallback(() => {
|
||||
setState((s) => ({ ...s, loading: true, error: null }));
|
||||
fetchNexusPlugins()
|
||||
.then((plugins) => setState({ loading: false, error: null, plugins }))
|
||||
.catch((e) => setState({ loading: false, error: String(e?.message ?? e), plugins: [] }));
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
load();
|
||||
}, [load]);
|
||||
|
||||
if (state.loading) {
|
||||
return (
|
||||
<SettingsScaffold>
|
||||
<NoteBox>Loading Rosie's plugins…</NoteBox>
|
||||
</SettingsScaffold>
|
||||
);
|
||||
}
|
||||
|
||||
if (state.error) {
|
||||
return (
|
||||
<SettingsScaffold>
|
||||
<NoteBox>Couldn't load the plugin list: {state.error}</NoteBox>
|
||||
<PrimaryButton label="Retry" onPress={load} style={{ margin: 16 }} />
|
||||
</SettingsScaffold>
|
||||
);
|
||||
}
|
||||
|
||||
const byCategory = new Map<string, NexusPlugin[]>();
|
||||
for (const plugin of state.plugins) {
|
||||
const list = byCategory.get(plugin.category) ?? [];
|
||||
list.push(plugin);
|
||||
byCategory.set(plugin.category, list);
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsScaffold>
|
||||
{[...byCategory.entries()].map(([category, plugins]) => (
|
||||
<TableRowGroup key={category} title={category}>
|
||||
{plugins.map((plugin) => {
|
||||
const installed = plugin.installUrl in installedPlugins;
|
||||
return (
|
||||
<TableRow
|
||||
key={plugin.id}
|
||||
label={plugin.name}
|
||||
subLabel={installed ? "Installed" : plugin.tagline || plugin.description}
|
||||
trailing={
|
||||
plugin.status && plugin.status !== "default" ? (
|
||||
<Text style={{ fontSize: 11, fontWeight: "700", opacity: 0.7 }}>
|
||||
{plugin.status.toUpperCase()}
|
||||
</Text>
|
||||
) : installed ? (
|
||||
<Text style={{ fontSize: 14, fontWeight: "700", color: "#23A55A" }}>✓</Text>
|
||||
) : undefined
|
||||
}
|
||||
onPress={() => showCustomAlert(PluginDetailSheet, { plugin })}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TableRowGroup>
|
||||
))}
|
||||
</SettingsScaffold>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue