admin/fs: sorted accounts list
Massimo Melina committed
Mar 5, 2025 at 22:15 UTC
798c241db9ee53a69358dd4695fb96635b6bb104
2 files changed
+18
-7
admin/src/VfsPage.ts
+2
-1
@@ -41,6 +41,7 @@ export default function VfsPage({ setTitleSide }: PageProps) {
41
}, [status, config])
42
const single = selectedFiles?.length < 2 && (selectedFiles[0] as VfsNode || vfs)
43
const accountsApi = useApiEx<{ list: Account[] }>('get_accounts') // load accounts once and for all, or !isSideBreakpoint will cause a call for each selection
44
+ const accounts = useMemo(() => _.sortBy(accountsApi?.data?.list, 'username'), [accountsApi.data])
45
46
// this will take care of closing the dialog, for user's convenience, after "cut" button is pressed
47
const closeDialogRef = useRef(_.noop)
@@ -72,7 +73,7 @@ export default function VfsPage({ setTitleSide }: PageProps) {
73
addToBar: isSideBreakpoint && h(Box, { flex: 1, textAlign: 'right', mr: 1, color: '#8883' }, vfsNodeIcon(single)),
74
statusApi,
75
saved: () => closeDialogRef.current(),
75
- accounts: accountsApi?.data?.list ?? [],
76
+ accounts: accounts ?? [],
77
file: single // it's actually Snapshot<VfsNode> but it's easier this way
78
})
79
: h(Fragment, {},
mui-grid-form/SelectField.ts
+16
-6
@@ -2,9 +2,12 @@
2
3
import { createElement as h, Fragment, ReactNode, useId, useMemo } from 'react'
4
import { FieldProps } from '.'
5
-import { FormControl, FormControlLabel, FormLabel, MenuItem, Radio, InputLabel, Select, LinearProgress,
6
- ListItemText, Checkbox, FilledInput, RadioGroup, TextField, FormHelperText, Button } from '@mui/material'
5
+import {
6
+ FormControl, FormControlLabel, FormLabel, MenuItem, Radio, InputLabel, Select, LinearProgress,
7
+ ListItemText, Checkbox, FilledInput, RadioGroup, TextField, FormHelperText, Button, Box
8
+} from '@mui/material'
9
import { SxProps } from '@mui/system'
10
+import { useIsMobile } from '@hfs/shared'
11
12
type SelectOptions<T> = { [label:string]: T } | SelectOption<T>[]
13
type SelectOption<T> = SelectOptionNormalized<T> | (T extends string | number ? T : never)
@@ -54,9 +57,10 @@ export function MultiSelectField<T>({ renderOption, ...props }: MultiSelectField
57
: value.map(x => normalizedOptions?.find(o => o.value === x) || { value: x, label: String(x) }),
58
[value, normalizedOptions])
59
const valueAsJsons = useMemo(() => value?.map(x => JSON.stringify(x)) || [], [value])
60
+ const isMobile = useIsMobile()
61
const labelId = useId()
62
const helperId = useId()
59
- const showClear = valueAsOptions.length > 0
63
+ const isEmpty = !valueAsOptions.length
64
renderOption ??= x => x.label ?? String(x.value)
65
return h(FormControl, { fullWidth: true, variant: 'filled', hiddenLabel: !label },
66
h(InputLabel, {
@@ -73,6 +77,7 @@ export function MultiSelectField<T>({ renderOption, ...props }: MultiSelectField
77
v = v.map(x => x && JSON.parse(x)) // x can be undefined because of the clear-button
78
onChange(v as any, { was: value, event })
79
},
80
+ sx: { '& .MuiSelect-select': { maxHeight: '15em', overflowY: 'auto' } },
81
input: h(FilledInput, {
82
placeholder,
83
hiddenLabel: !label,
@@ -90,13 +95,18 @@ export function MultiSelectField<T>({ renderOption, ...props }: MultiSelectField
95
sx: { fontStyle: 'italic', ml: 1 },
96
onClickCapture(ev) { ev.stopPropagation() }
97
}, "No options available")),
98
+ !isMobile && normalizedOptions?.length! > 20 && h(Box, {
99
+ sx: { float: 'right' }, fontSize: 'small', width: '8em', textAlign: 'right', marginRight: '.5em'
100
+ }, "ⓘ You can type the name"),
101
normalizedOptions?.length! > 1 && h(Button, {
94
- ref: x => x && Object.assign(x, { role: undefined }), // cancel the role=option on this
102
+ size: 'small',
103
+ sx: { ml: 1 },
104
+ ref: x => x && Object.assign(x, { role: undefined }) && setTimeout(() => x.focus()), // cancel the role=option on this
105
onClickCapture(event) {
106
event.stopPropagation()
97
- onChange(showClear ? [] : normalizedOptions!.map(x => x.value), { was: value, event })
107
+ onChange(isEmpty ? normalizedOptions!.map(x => x.value) : [], { was: value, event })
108
},
99
- }, showClear ? "Unselect all" : "Select all"),
109
+ }, isEmpty ? "Select all" : "Unselect all"),
110
...normalizedOptions?.map(o => h(MenuItem, { value: JSON.stringify(o?.value) }, // encode, as this supports only string|number
111
h(Checkbox, { checked: value?.includes(o.value) || false }),
112
h(ListItemText, { primary: renderOption!(o) })