@samitouri / QOSami-HFS / commits / 798c241d

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) })