diff --git a/frontend/src/components/theme/SizedInstallButton.jsx b/frontend/src/components/theme/SizedInstallButton.jsx index 780aedb5..7bcb1819 100644 --- a/frontend/src/components/theme/SizedInstallButton.jsx +++ b/frontend/src/components/theme/SizedInstallButton.jsx @@ -71,7 +71,7 @@ const SizedInstallButton = ({ : 'var(--mantine-primary-color-filled-hover)' : colorVar, filter: isDisabled - ? 'brightness(0.65) saturate(0.7)' + ? 'grayscale(1) brightness(0.55)' : hovered ? 'brightness(0.86)' : 'brightness(0.82)', diff --git a/frontend/src/pages/PluginBrowse.jsx b/frontend/src/pages/PluginBrowse.jsx index ccc81b28..dd4a3a6d 100644 --- a/frontend/src/pages/PluginBrowse.jsx +++ b/frontend/src/pages/PluginBrowse.jsx @@ -8,6 +8,7 @@ import { Group, Loader, Modal, + NativeSelect, NumberInput, Pagination, Select, @@ -68,6 +69,7 @@ export default function PluginBrowsePage() { const saveIntervalTimer = useRef(null); const recentlyInstalledSlugs = useRef(new Set()); + const recentlyUpdatedSlugs = useRef(new Set()); const recentlyUninstalledSlugs = useRef(new Set()); const [searchQuery, setSearchQuery] = useState(''); @@ -75,7 +77,14 @@ export default function PluginBrowsePage() { const [filterRepo, setFilterRepo] = useState('all'); const [filterStatus, setFilterStatus] = useState('all'); const [page, setPage] = useState(1); - const perPage = 9; + const [perPage, setPerPage] = useState(() => { + const stored = localStorage.getItem('pluginBrowsePerPage'); + return stored && !isNaN(Number(stored)) ? Number(stored) : 9; + }); + const handlePerPageChange = (value) => { + setPerPage(Number(value)); + localStorage.setItem('pluginBrowsePerPage', value); + }; const hasFetched = useRef(false); @@ -294,6 +303,8 @@ export default function PluginBrowsePage() { // Pre-sort weights: deprecated → installed → incompatible sink to bottom (in that order) // Recently installed plugins are exempt so they don't jump away after install const weight = (p) => { + if (p.install_status === 'update_available') return -1; + if (recentlyUpdatedSlugs.current.has(p.slug)) return -1; if (recentlyInstalledSlugs.current.has(p.slug)) return 0; if (recentlyUninstalledSlugs.current.has(p.slug)) return 2; const meetsMin = @@ -335,10 +346,10 @@ export default function PluginBrowsePage() { appVersion, ]); - // Reset to page 1 when filters/search change + // Reset to page 1 when filters/search/page-size change React.useEffect(() => { setPage(1); - }, [searchQuery, filterRepo, filterStatus, sortBy]); + }, [searchQuery, filterRepo, filterStatus, sortBy, perPage]); const totalPages = Math.ceil(filteredPlugins.length / perPage); const paginatedPlugins = filteredPlugins.slice( @@ -347,7 +358,15 @@ export default function PluginBrowsePage() { ); return ( - + + @@ -467,38 +486,69 @@ export default function PluginBrowsePage() { )} {!loading && filteredPlugins.length > 0 && ( - <> - - {paginatedPlugins.map((p) => ( - 1} - onBeforeInstall={(slug) => { - if (slug) recentlyInstalledSlugs.current.add(slug); - }} - onInstalled={(slug) => { - if (slug) recentlyInstalledSlugs.current.add(slug); - fetchAvailablePlugins(); - }} - onUninstalled={(slug) => { - if (slug) recentlyUninstalledSlugs.current.add(slug); - }} - /> - ))} - - {totalPages > 1 && ( - - - - )} - + + {paginatedPlugins.map((p) => ( + 1} + onBeforeInstall={(slug) => { + if (slug) { + if (p.install_status === 'update_available') { + recentlyUpdatedSlugs.current.add(slug); + } else { + recentlyInstalledSlugs.current.add(slug); + } + } + }} + onInstalled={(slug) => { + if (slug) recentlyInstalledSlugs.current.add(slug); + fetchAvailablePlugins(); + }} + onUninstalled={(slug) => { + if (slug) recentlyUninstalledSlugs.current.add(slug); + }} + /> + ))} + + )} + + + + {!loading && filteredPlugins.length > 0 && ( + + + Page Size + handlePerPageChange(e.target.value)} + styles={{ input: { textAlignLast: 'center' } }} + /> + + + {`${(page - 1) * perPage + 1} to ${Math.min(page * perPage, filteredPlugins.length)} of ${filteredPlugins.length}`} + + + )} {/* Manage Repos Modal */}