UI/UX improvements

This commit is contained in:
Rosie Val 2026-08-09 03:29:45 -04:00
parent 5b44ad0846
commit f7d769ef18
8 changed files with 164 additions and 16 deletions

View file

@ -32,6 +32,11 @@ export interface BrowseFilterAndSortActionSheetProps {
/** Checked repository URLs. */
checked: string[]
setChecked: (urls: string[]) => void
/** Available release channels across all listings. */
channels: string[]
/** Selected release channel. Empty string means default. */
channel: string
setChannel: (channel: string) => void
sort: BrowseSortKey
setSort: (sort: BrowseSortKey) => void
reverse: boolean
@ -42,6 +47,9 @@ export default function BrowseFilterAndSortActionSheet({
repos,
checked,
setChecked,
channels,
channel,
setChannel,
sort,
setSort,
reverse,
@ -76,6 +84,19 @@ export default function BrowseFilterAndSortActionSheet({
)
})}
</TableRowGroup>
<TableRadioGroup
title="Release channel"
defaultValue={channel}
onChange={v => setChannel(v as string)}
>
<TableRadioRow
label="Default (latest / first available)"
value=""
/>
{channels.map(c => (
<TableRadioRow key={c} label={c} value={c} />
))}
</TableRadioGroup>
<TableRadioGroup
title="Sort by"
defaultValue={sort}

View file

@ -0,0 +1,33 @@
import { TableRowAssetIcon } from '@revenge-mod/components'
import { Design } from '@revenge-mod/discord/design'
import type { AnyPlugin } from '@revenge-mod/plugins/_'
const { ActionSheet, Stack, TableRow, TableRowGroup } = Design
export interface PluginListActionSheetProps {
title: string
plugins: AnyPlugin[]
sheetKey: string
}
export default function PluginListActionSheet({
title,
plugins,
}: PluginListActionSheetProps) {
return (
<ActionSheet>
<Stack spacing={24} style={{ paddingTop: 8 }}>
<TableRowGroup title={title}>
{plugins.map(plugin => (
<TableRow
key={plugin.manifest.id}
icon={<TableRowAssetIcon name="ListBulletsIcon" />}
label={plugin.manifest.name}
subLabel={plugin.manifest.id}
/>
))}
</TableRowGroup>
</Stack>
</ActionSheet>
)
}

View file

@ -117,7 +117,7 @@ function StatusSection({ plugin }: { plugin: AnyPlugin }) {
<TableRowGroup title="Status">
<TableRow
icon={<TableRowAssetIcon name="CircleInformationIcon" />}
label="Status (TODO)"
label="Status"
subLabel={bitFieldToString(PluginStatus, status)}
/>
{errors.length > 0 && (
@ -175,15 +175,37 @@ function AdvancedSection({ plugin }: { plugin: AnyPlugin }) {
{dependencies.length > 0 && (
<TableRow
icon={<TableRowAssetIcon name="ListBulletsIcon" />}
label="Dependencies (TODO)"
label="Dependencies"
subLabel={`${name} depends on ${dependencies.length} other plugins`}
onPress={() => {
ActionSheetActionCreators.openLazy(
import('./PluginListActionSheet'),
`plugin-deps-${id}`,
{
title: `Dependencies of ${name}`,
plugins: dependencies,
sheetKey: `plugin-deps-${id}`,
},
)
}}
/>
)}
{dependents.length > 0 && (
<TableRow
icon={<TableRowAssetIcon name="ListBulletsIcon" />}
label="Dependents (TODO)"
label="Dependents"
subLabel={`${dependents.length} other plugins depend on ${name}`}
onPress={() => {
ActionSheetActionCreators.openLazy(
import('./PluginListActionSheet'),
`plugin-dependents-${id}`,
{
title: `Dependents of ${name}`,
plugins: dependents,
sheetKey: `plugin-dependents-${id}`,
},
)
}}
/>
)}
</TableRowGroup>

View file

@ -0,0 +1,37 @@
import { Design } from '@revenge-mod/discord/design'
import type { Repo } from '@revenge-mod/plugins/_/repositories'
const { AlertModal, AlertActionButton, Text } = Design
export default function RepoRemoveConfirmationAlert({
repo,
action,
}: {
repo: Repo
action: () => Promise<void> | void
}) {
return (
<AlertModal
title="Remove repository?"
content={
<Text color="text-default">
<Text variant="text-md/semibold" color="text-default">
{repo.name ?? repo.url}
</Text>{' '}
will be removed. You can restore the default repositories by
clearing Plugin Settings data.
</Text>
}
actions={
<>
<AlertActionButton
onPress={action}
text="Remove"
variant="destructive"
/>
<AlertActionButton text="Cancel" variant="secondary" />
</>
}
/>
)
}

View file

@ -96,7 +96,6 @@ function autoUpdateService(settings: Storage) {
}, AUTO_UPDATE_CHECK_DELAY)
}
// TODO: Add UI for this?
function defaultRepoRestoreService(
settings: Storage,
storage: JsonStorage<Storage>,

View file

@ -20,8 +20,9 @@ import { lookupGeneratedIconComponent } from '@revenge-mod/utils/discord'
import { useCallback, useEffect, useState } from 'react'
import { ScrollView, View } from 'react-native'
import { api } from '..'
import { toConfig } from '../repos'
import { addDefaultRepoIfNeeded, toConfig } from '../repos'
import { formatBytes, messageOf, showErrorToast } from '../utils/repos'
import { showRemoveRepoConfirmation } from '../utils/alerts'
import type {
DownloadProgressEvent,
Repo,
@ -101,7 +102,7 @@ function UserRepoRow({
label: 'Delete',
IconComponent: TrashIconComponent,
variant: 'destructive' as const,
action: () => onRemove(repo),
action: () => showRemoveRepoConfirmation(repo, () => onRemove(repo)),
},
],
]
@ -212,7 +213,6 @@ export default function RevengePluginsAdvancedSettingScreen() {
const removeRepo = useCallback(
async (repo: Repo) => {
// TODO: Add warning for default repo removal, user can clear "Plugin Settings" data to restore the default repo
await commit(toConfig(userRepos.filter(r => r.url !== repo.url)))
@ -370,6 +370,16 @@ export default function RevengePluginsAdvancedSettingScreen() {
)
}
/>
<TableRow
icon={<TableRowAssetIcon name="GlobeEarthIcon" />}
label="Restore default repositories"
subLabel="Add the bundled default repository if it's missing"
onPress={() => {
addDefaultRepoIfNeeded().catch(e =>
showErrorToast(messageOf(e)),
)
}}
/>
</TableRowGroup>
</Stack>
</ScrollView>

View file

@ -49,9 +49,9 @@ export default function RevengePluginsBrowseSettingScreen() {
)
}
// TODO: Let the user pick a release channel
/** The channel a listing displays: `latest`, else its first channel. */
function displayChannelOf(listing: RepoPluginListing): string | undefined {
/** The channel a listing displays: preferred, `latest`, else its first channel. */
function displayChannelOf(listing: RepoPluginListing, preferredChannel: string): string | undefined {
if (preferredChannel && listing.channels[preferredChannel]) return preferredChannel
if (listing.channels.latest) return 'latest'
return Object.keys(listing.channels)[0]
}
@ -82,15 +82,18 @@ function Screen() {
const [excluded, setExcluded] = useState<string[]>([])
const [sort, setSort] = useState<BrowseSortKey>('name')
const [reverse, setReverse] = useState(false)
const [channel, setChannel] = useState('')
const [channels, setChannels] = useState<string[]>([])
const hasFilter = useMemo(
() => excluded.length > 0 || sort !== 'name' || reverse,
[excluded, sort, reverse],
() => excluded.length > 0 || sort !== 'name' || reverse || channel !== '',
[excluded, sort, reverse, channel],
)
const load = useCallback(async () => {
const repos = await listRepos()
const all: BrowseEntry[] = []
const allChannels = new Set<string>()
setInternalRepos(
repos.filter(repo => repo.internal).map(repo => repo.url),
@ -108,7 +111,9 @@ function Screen() {
for (const listing of listings) {
const plugin = pList.get(listing.id)
const displayChannel = displayChannelOf(listing)
for (const c of Object.keys(listing.channels))
allChannels.add(c)
const displayChannel = displayChannelOf(listing, channel)
const displayVersion = displayChannel
? (listing.channels[displayChannel] ?? '')
: ''
@ -119,7 +124,6 @@ function Screen() {
repoName: repo.name ?? null,
repositoryText,
version: displayVersion,
// TODO: Let the user pick a release channel
channel: displayChannel,
size:
listing.versions[displayVersion]
@ -139,8 +143,9 @@ function Screen() {
),
)
setChannels([...allChannels].sort())
setEntries(all)
}, [])
}, [channel, setChannels])
useEffect(() => {
// Show cached indexes right away, then refresh everything
@ -234,6 +239,9 @@ function Screen() {
.map(repo => repo.url)
.filter(url => !urls.includes(url)),
),
channels,
channel,
setChannel,
sort,
setSort,
reverse,

View file

@ -11,8 +11,10 @@ import PluginClearDataConfirmationAlert from '../components/PluginClearDataConfi
import PluginHasDependenciesAlert from '../components/PluginHasDependenciesAlert'
import PluginHasDependentsAlert from '../components/PluginHasDependentsAlert'
import PluginMissingDependenciesAlert from '../components/PluginMissingDependenciesAlert'
import PluginStatesProvider from '../components/PluginStateProvider'
import PluginStatesProvider from '../components/PluginStatesProvider'
import PluginUninstallConfirmationAlert from '../components/PluginUninstallConfirmationAlert'
import RepoRemoveConfirmationAlert from '../components/RepoRemoveConfirmationAlert'
import type { Repo } from '@revenge-mod/plugins/_/repositories'
import type { AnyPlugin } from '@revenge-mod/plugins/_'
export function showPluginClearDataConfirmation(
@ -100,6 +102,22 @@ export function showPluginMissingDependenciesAlert(
)
}
export function showRemoveRepoConfirmation(
repo: Repo,
callback: () => Promise<void> | void,
) {
const KEY = 'repo-remove-confirmation'
async function action() {
await callback()
}
AlertActionCreators.openAlert(
KEY,
<RepoRemoveConfirmationAlert repo={repo} action={action} />,
)
}
export function showPluginHasDependentsAlert(
plugin: AnyPlugin,
dependents: AnyPlugin[],