admin/plugins: keep sorted even if a plugin is added after the list is sent

Massimo Melina committed Nov 24, 2024 at 18:17 UTC 9688a8f9d4248d38c655b201f3e74aebd50f68ac
2 files changed +14 -25
admin/src/InstalledPlugins.ts
+5 -13
@@ -5,14 +5,7 @@ import { createElement as h, Fragment, useEffect } from 'react'
5 import { Box, Link } from '@mui/material'
6 import { DataTable, DataTableColumn } from './DataTable'
7 import {
8 - Clear,
9 - Delete,
10 - Error as ErrorIcon,
11 - FormatPaint as ThemeIcon,
12 - PlayCircle,
13 - Settings,
14 - StopCircle,
15 - Upgrade
8 + Clear, Delete, Error as ErrorIcon, FormatPaint as ThemeIcon, PlayCircle, Settings, StopCircle, Upgrade
9 } from '@mui/icons-material'
10 import { HTTP_FAILED_DEPENDENCY, md, newObj, prefix, with_, xlate } from './misc'
11 import { alertDialog, confirmDialog, formDialog, toast } from './dialog'
@@ -26,12 +19,11 @@ import { Btn, hTooltip, IconBtn, iconTooltip } from './mui'
19 import VfsPathField from './VfsPathField'
20
21 export default function InstalledPlugins({ updates }: { updates?: true }) {
29 - const { list, error, updateList, initializing } = useApiList(updates ? 'get_plugin_updates' : 'get_plugins')
22 + const { list, error, setList, initializing } = useApiList(updates ? 'get_plugin_updates' : 'get_plugins')
23 useEffect(() => {
31 - if (!initializing)
32 - updateList(list =>
33 - _.sortBy(list, x => (x.started || x.error ? '0' : '1') + treatPluginName(x.id)))
34 - }, [initializing]);
24 + setList(list =>
25 + _.sortBy(list, x => (x.started || x.error ? '0' : '1') + treatPluginName(x.id)))
26 + }, [list.length]);
27 const size = 'small'
28 return h(DataTable, {
29 error: xlate(error, PLUGIN_ERRORS),
admin/src/api.ts
+9 -12
@@ -1,6 +1,6 @@
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 -import { createElement as h, useEffect, useMemo, useRef, useState } from 'react'
3 +import { createElement as h, useCallback, useEffect, useMemo, useRef, useState } from 'react'
4 import { Dict, err2msg, Falsy, LIST, useStateMounted, wantArray, xlate, objSameKeys,
5 HTTP_FORBIDDEN, HTTP_UNAUTHORIZED } from './misc'
6 import { IconBtn, spinner } from './mui'
@@ -161,21 +161,18 @@ export function useApiList<T=any, S=T>(cmd:string|Falsy, params: Dict={}, { map,
161 apply.flush()
162 }
163 }, [reloader, cmd, JSON.stringify(params)]) //eslint-disable-line
164 - return { list: pausedList ?? list, props, loading, error, initializing, connecting, setList, updateList, updateEntry, reload }
165 -
166 - function reload() {
167 - setReloader(x => x + 1)
168 - }
169 -
170 - function updateList(cb: (toModify: Draft<typeof list>) => void) {
171 - setList(produce(list, cb))
172 - }
173 -
174 - function updateEntry(search: T, change: T) {
164 + const updateList = useCallback((cb: (toModify: Draft<typeof list>) => void) => setList(list => produce(list, cb)),
165 + [setList])
166 + const updateEntry = useCallback((search: T, change: T) => {
167 updateList(list => {
168 const res = _.find(list, search as any)
169 if (res)
170 Object.assign(res, change)
171 })
172 + }, [updateList])
173 + return { list: pausedList ?? list, props, loading, error, initializing, connecting, setList, updateList, updateEntry, reload }
174 +
175 + function reload() {
176 + setReloader(x => x + 1)
177 }
178 }