Add startup update-confirmation alert with plugin details

This commit is contained in:
Rosie Val 2026-08-09 00:54:54 -04:00
parent 2cf998542f
commit a7385a58df
4 changed files with 159 additions and 6 deletions

View file

@ -160,6 +160,8 @@ export interface RepoUpdate {
installed: string
available: string
channel: string
/** Repository URL this update was found in, added by {@link listAllUpdates}. */
repo?: string
}
/**
@ -227,7 +229,7 @@ export async function listAllUpdates(): Promise<{
.map(repo =>
listUpdates(repo.url).then(
result => {
updates.push(...result)
updates.push(...result.map(u => ({ ...u, repo: repo.url })))
},
error => {
errors.push({ url: repo.url, error })

View file

@ -0,0 +1,112 @@
import { Design } from '@revenge-mod/discord/design'
import {
listRepoPlugins,
type RepoPluginListing,
type RepoUpdate,
} from '@revenge-mod/plugins/_/repositories'
import { useEffect, useState } from 'react'
import { View } from 'react-native'
const { AlertModal, AlertActionButton, Text } = Design
interface UpdateDetail {
id: string
name: string
author: string
installed: string
available: string
channel: string
}
export default function PluginUpdatesFoundAlert({
updates,
action,
}: {
updates: RepoUpdate[]
action: () => Promise<void>
}) {
const [details, setDetails] = useState<UpdateDetail[] | null>(null)
useEffect(() => {
const repoMap = new Map<string, RepoUpdate[]>()
for (const update of updates) {
const repo = update.repo!
if (!repoMap.has(repo)) repoMap.set(repo, [])
repoMap.get(repo)!.push(update)
}
const allDetails: UpdateDetail[] = []
Promise.all(
[...repoMap.entries()].map(async ([repo, repoUpdates]) => {
let listings: RepoPluginListing[]
try {
listings = await listRepoPlugins(repo)
} catch {
listings = []
}
for (const update of repoUpdates) {
const listing = listings.find(l => l.id === update.id)
allDetails.push({
id: update.id,
name: listing?.name ?? update.id,
author: listing?.author ?? 'Unknown',
installed: update.installed,
available: update.available,
channel: update.channel,
})
}
}),
).then(() => setDetails(allDetails))
}, [updates])
return (
<AlertModal
title="Updates Found"
content={
<Text color="text-default">
The following plugins have updates available:
</Text>
}
extraContent={
<View style={{ paddingTop: 8 }}>
{details === null ? (
<Text color="text-muted">Loading...</Text>
) : (
details.map(detail => (
<View
key={detail.id}
style={{ marginBottom: 12 }}
>
<Text
variant="text-md/semibold"
color="text-default"
>
{detail.name}
</Text>
<Text color="text-muted">
{detail.installed} → {detail.available} (
{detail.channel})
</Text>
<Text color="text-muted">
by {detail.author}
</Text>
</View>
))
)}
</View>
}
actions={
<>
<AlertActionButton
onPress={action}
text="Update"
variant="primary"
/>
<AlertActionButton text="Not now" variant="secondary" />
</>
}
/>
)
}

View file

@ -7,12 +7,15 @@ import {
registerInternalPlugin,
} from '@revenge-mod/plugins/_'
import {
listAllUpdates,
refreshAllRepos,
updateAllPlugins,
} from '@revenge-mod/plugins/_/repositories'
import { lookupGeneratedIconComponent } from '@revenge-mod/utils/discord'
import pluginSettings from '../settings'
import { showPluginUpdatesFoundAlert } from './utils/alerts'
import { addDefaultRepoIfNeeded } from './repos'
import { showErrorToast } from './utils/repos'
import type { JsonStorage } from '@revenge-mod/json-storage'
import type { PluginApi } from '@revenge-mod/plugins/types'
@ -78,12 +81,37 @@ function autoUpdateService(settings: Storage) {
setTimeout(async () => {
try {
const { errors } = await refreshAllRepos()
await updateAllPlugins()
if (!errors.length)
await api.jsonStorage.set({
lastUpdateCheck: Date.now(),
if (errors.length) {
ToastActionCreators.open({
key: 'PLUGIN_UPDATE_CHECK_FAILED',
content: 'Failed to refresh some plugin repositories',
IconComponent: CircleXIconComponent,
})
return
}
const { updates } = await listAllUpdates()
await api.jsonStorage.set({ lastUpdateCheck: Date.now() })
if (!updates.length) return
showPluginUpdatesFoundAlert(updates, async () => {
const { errors: updateErrors } = await updateAllPlugins()
if (updateErrors.length) {
showErrorToast(
updateErrors
.map(e => `${e.id}: ${e.error}`)
.join('\n'),
)
} else {
ToastActionCreators.open({
key: 'PLUGIN_UPDATES_INSTALLED',
content: 'Plugins will update on the next reload',
})
}
})
} catch (e) {
ToastActionCreators.open({
key: 'PLUGIN_UPDATE_CHECK_FAILED',

View file

@ -13,8 +13,9 @@ import PluginHasDependentsAlert from '../components/PluginHasDependentsAlert'
import PluginMissingDependenciesAlert from '../components/PluginMissingDependenciesAlert'
import PluginStatesProvider from '../components/PluginStateProvider'
import PluginUninstallConfirmationAlert from '../components/PluginUninstallConfirmationAlert'
import PluginUpdatesFoundAlert from '../components/PluginUpdatesFoundAlert'
import RepoRemoveConfirmationAlert from '../components/RepoRemoveConfirmationAlert'
import type { Repo } from '@revenge-mod/plugins/_/repositories'
import type { Repo, RepoUpdate } from '@revenge-mod/plugins/_/repositories'
import type { AnyPlugin } from '@revenge-mod/plugins/_'
export function showPluginClearDataConfirmation(
@ -134,3 +135,13 @@ export function showPluginHasDependentsAlert(
</PluginStatesProvider>,
)
}
export function showPluginUpdatesFoundAlert(
updates: RepoUpdate[],
action: () => Promise<void>,
) {
AlertActionCreators.openAlert(
'plugin-updates-found',
<PluginUpdatesFoundAlert updates={updates} action={action} />,
)
}