UI/UX improvements
This commit is contained in:
parent
5b44ad0846
commit
f7d769ef18
8 changed files with 164 additions and 16 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -96,7 +96,6 @@ function autoUpdateService(settings: Storage) {
|
|||
}, AUTO_UPDATE_CHECK_DELAY)
|
||||
}
|
||||
|
||||
// TODO: Add UI for this?
|
||||
function defaultRepoRestoreService(
|
||||
settings: Storage,
|
||||
storage: JsonStorage<Storage>,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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[],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue