fix: admin/options: bad layout of "block" on mobile
Massimo Melina committed
Sep 2, 2024 at 15:14 UTC
522147b128c61ede36a1d409beb76d7a1f988548
3 files changed
+40
-16
admin/src/ArrayField.ts
+23
-9
@@ -4,12 +4,13 @@ import { createElement as h, Fragment, useMemo, useState } from 'react'
4
import { Dict, isOrderedEqual, setHidden, swap } from './misc'
5
import { Add, Edit, Delete, ArrowUpward, ArrowDownward, Undo, Check } from '@mui/icons-material'
6
import { formDialog } from './dialog'
7
-import { DataGrid, GridActionsCellItem, GridAlignment, GridColDef } from '@mui/x-data-grid'
7
+import { GridActionsCellItem, GridAlignment, GridColDef } from '@mui/x-data-grid'
8
import { BoolField, FieldDescriptor, FieldProps, labelFromKey } from '@hfs/mui-grid-form'
9
import { Box, FormHelperText, FormLabel } from '@mui/material'
10
import { DateTimeField } from './DateTimeField'
11
import _ from 'lodash'
12
import { Center, IconBtn } from './mui'
13
+import { DataTable } from './DataTable'
14
15
type ArrayFieldProps<T> = FieldProps<T[]> & { fields: FieldDescriptor[], height?: number, reorder?: boolean, prepend?: boolean, autoRowHeight?: boolean }
16
export function ArrayField<T extends object>({ label, helperText, fields, value, onChange, onError, setApi, reorder, prepend, noRows, valuesForAdd, autoRowHeight, ...rest }: ArrayFieldProps<T>) {
@@ -19,7 +20,7 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
20
setHidden({ ...x } as any, x.hasOwnProperty('id') ? { $idx } : { id: $idx })),
21
[JSON.stringify(value)]) //eslint-disable-line
22
const form = {
22
- fields: fields.map(({ $width, $column, $type, ...rest }) => _.defaults(rest, byType[$type]?.field))
23
+ fields: fields.map(({ $width, $column, $type, $hideUnder, ...rest }) => _.defaults(rest, byType[$type]?.field))
24
}
25
setApi?.({ isEqual: isOrderedEqual }) // don't rely on stringify, as it wouldn't work with non-json values
26
const [undo, setUndo] = useState<typeof value>()
@@ -27,12 +28,14 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
28
label && h(FormLabel, { sx: { ml: 1 } }, label),
29
helperText && h(FormHelperText, {}, helperText),
30
h(Box, { ...rest },
30
- h(DataGrid, {
31
+ h(DataTable, {
32
rows,
33
...autoRowHeight && { getRowHeight: () => 'auto' as const },
33
- sx: { '.MuiDataGrid-virtualScroller': { minHeight: '3em' },
34
+ sx: {
35
+ '.MuiDataGrid-virtualScroller': { minHeight: '3em' },
36
...autoRowHeight && { '.MuiDataGrid-cell': { minHeight: '52px !important' } }
37
},
38
+ style: undefined, // override style making it fill the flex
39
hideFooterSelectedRowCount: true,
40
hideFooter: true,
41
slots: {
@@ -56,6 +59,7 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
59
},
60
...def,
61
...f.$width ? { [f.$width >= 8 ? 'width' : 'flex']: f.$width } : (!def?.width && !def?.flex && { flex: 1 }),
62
+ hideUnder: f.$hideUnder,
63
...f.$column,
64
}
65
}),
@@ -96,10 +100,11 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
100
icon: h(Edit),
101
label: title,
102
title,
99
- onClick(event: MouseEvent) {
103
+ onClick(ev: MouseEvent) {
104
+ ev.stopPropagation()
105
formDialog<T>({ values: row as any, form, title }).then(x => {
106
if (x)
102
- set(value!.map((oldRec, i) => i === $idx ? x : oldRec), event)
107
+ set(value!.map((oldRec, i) => i === $idx ? x : oldRec), ev)
108
})
109
}
110
}),
@@ -107,19 +112,28 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
112
icon: h(Delete),
113
label: "Delete",
114
showInMenu: reorder,
110
- onClick: ev => set(value!.filter((rec, i) => i !== $idx), ev),
115
+ onClick: ev => {
116
+ ev.stopPropagation()
117
+ set(value!.filter((rec, i) => i !== $idx), ev)
118
+ },
119
}),
120
reorder && $idx && h(GridActionsCellItem as any, {
121
icon: h(ArrowUpward),
122
label: "Move up",
123
showInMenu: true,
116
- onClick: ev => set(swap(value!.slice(), $idx, $idx - 1), ev),
124
+ onClick: ev => {
125
+ ev.stopPropagation()
126
+ set(swap(value!.slice(), $idx, $idx - 1), ev)
127
+ },
128
}),
129
reorder && $idx < rows.length - 1 && h(GridActionsCellItem as any, {
130
icon: h(ArrowDownward),
131
label: "Move down",
132
showInMenu: true,
122
- onClick: ev => set(swap(value!.slice(), $idx, $idx + 1), ev),
133
+ onClick: ev => {
134
+ ev.stopPropagation()
135
+ set(swap(value!.slice(), $idx, $idx + 1), ev)
136
+ },
137
}),
138
].filter(Boolean)
139
}
admin/src/DataTable.ts
+10
-4
@@ -34,6 +34,7 @@ export function DataTable({ columns, initialState={}, actions, actionsProps, ini
34
const theme = useTheme()
35
const apiRef = useGridApiRef()
36
const [actionsLength, setActionsLength] = useState(0)
37
+ const [merged, setMerged] = useState(0)
38
const manipulatedColumns = useMemo(() => {
39
const { localeText } = enUS.components.MuiDataGrid.defaultProps as any
40
const ret = columns.map(col => {
@@ -100,6 +101,8 @@ export function DataTable({ columns, initialState={}, actions, actionsProps, ini
101
&& field))
102
const o = Object.fromEntries(fields.map(x => [x, false]))
103
_.merge(initialState, { columns: { columnVisibilityModel: o } })
104
+ // count the hidden columns that are merged into visible columns
105
+ setMerged(_.sumBy(fields, k => _.find(columns, col => !fields.includes(col.field) && col.mergeRender?.[k]) ? 1 : 0))
106
return fields
107
}, [manipulatedColumns, width])
108
const [vis, setVis] = useState({})
@@ -128,6 +131,9 @@ export function DataTable({ columns, initialState={}, actions, actionsProps, ini
131
columns: manipulatedColumns,
132
apiRef,
133
...rest,
134
+ sx: {
135
+ '& .MuiDataGrid-virtualScroller': { minHeight: '3em' } // without this, no-entries gets just 1px
136
+ },
137
slots: {
138
noRowsOverlay: () => initializing ? null : h(Center, {}, noRows || "No entries"),
139
footer: CustomFooter,
@@ -142,10 +148,10 @@ export function DataTable({ columns, initialState={}, actions, actionsProps, ini
148
onCellClick({ field, row }) {
149
if (field === ACTIONS) return
150
if (window.getSelection()?.type === 'Range') return // not a click but a drag
145
- const n = apiRef.current.getVisibleColumns().length
146
- const showCols = manipulatedColumns.filter(x =>
151
+ const visibleInList = merged + apiRef.current.getVisibleColumns().length
152
+ const showInDialog = manipulatedColumns.filter(x =>
153
!x.dialogHidden && (x.renderCell || x.field === ACTIONS || row[x.field] !== undefined))
148
- if (showCols.length <= n) return
154
+ if (showInDialog.length <= visibleInList) return // no need for dialog
155
newDialog({
156
title: "Details",
157
onClose() {
@@ -163,7 +169,7 @@ export function DataTable({ columns, initialState={}, actions, actionsProps, ini
169
gridAutoFlow: 'dense',
170
minWidth: 'max(16em, 40vw)',
171
sx: { opacity: curRow ? undefined : .5 },
166
- }, showCols.map(col =>
172
+ }, showInDialog.map(col =>
173
h(Box, { key: col.field, gridColumn: col.flex && '1/-1' },
174
h(Box, { bgcolor: '#0003', p: 1 }, col.headerName || col.field),
175
h(Flex, { minHeight: '2.5em', px: 1, wordBreak: 'break-word' },
admin/src/OptionsPage.ts
+7
-3
@@ -155,8 +155,11 @@ export default function OptionsPage() {
155
156
{ k: 'block', label: false, comp: ArrayField, prepend: true, sm: true, autoRowHeight: true,
157
fields: [
158
- { k: 'ip', label: "Blocked IP", sm: 12, required: true, wrap: true, helperText: h(WildcardsSupported) },
159
- { k: 'expire', $type: 'dateTime', minDate: new Date(), sm: 6, helperText: "Leave empty for no expiration" },
158
+ { k: 'ip', label: "Blocked IP", sm: 12, required: true, wrap: true, $width: 2,
159
+ $column: { mergeRender: { comment: {}, expire: {} } },
160
+ helperText: h(WildcardsSupported) },
161
+ { k: 'expire', $type: 'dateTime', minDate: new Date(), sm: 6, $hideUnder: 'sm',
162
+ helperText: "Leave empty for no expiration" },
163
{
164
k: 'disabled',
165
$type: 'boolean',
@@ -165,8 +168,9 @@ export default function OptionsPage() {
168
toField: (x: any) => !x,
169
fromField: (x: any) => x ? undefined : true,
170
sm: 6,
171
+ $width: 80,
172
},
169
- { k: 'comment' },
173
+ { k: 'comment', $hideUnder: 'sm' },
174
],
175
},
176