plugins: support showIf in config.array.fields
Massimo Melina committed
Mar 17, 2025 at 14:47 UTC
81a54e551c1064a0b103bd093baa0fcb1c77ed4b
4 files changed
+15
-19
admin/src/ArrayField.ts
+7
-7
@@ -1,7 +1,7 @@
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, Fragment, useMemo, useState } from 'react'
4
-import { Dict, isOrderedEqual, setHidden, swap } from './misc'
4
+import { callable, Dict, Functionable, 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 { GridActionsCellItem, GridAlignment, GridColDef } from '@mui/x-data-grid'
@@ -12,16 +12,16 @@ import _ from 'lodash'
12
import { Center, Flex, IconBtn, useBreakpoint } from './mui'
13
import { DataTable } from './DataTable'
14
15
-type ArrayFieldProps<T> = FieldProps<T[]> & { fields: FieldDescriptor[], height?: number, reorder?: boolean, prepend?: boolean, autoRowHeight?: boolean }
15
+type ArrayFieldProps<T> = FieldProps<T[]> & { fields: Functionable<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>) {
17
if (!Array.isArray(value)) // avoid crash if non-array values are passed, especially developing plugins
18
value = []
19
const rows = useMemo(() => value!.map((x,$idx) =>
20
setHidden({ ...x } as any, x.hasOwnProperty('id') ? { $idx } : { id: $idx })),
21
[JSON.stringify(value)]) //eslint-disable-line
22
- const form = {
23
- fields: fields.map(({ $width, $column, $type, $hideUnder, ...rest }) => _.defaults(rest, byType[$type]?.field))
24
- }
22
+ const form = (values: any) => ({
23
+ fields: callable(fields, values).map(({ $width, $column, $type, $hideUnder, showIf, ...rest }) => (!showIf || showIf(values)) && _.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
return h(Fragment, {},
@@ -50,7 +50,7 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
50
}
51
},
52
columns: [
53
- ...fields.map(f => {
53
+ ...callable(fields, false).map(f => {
54
const def = byType[f.$type]?.column
55
return {
56
field: f.k,
@@ -116,7 +116,7 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
116
showInMenu: reorder,
117
onClick: ev => {
118
ev.stopPropagation()
119
- set(value!.filter((rec, i) => i !== $idx), ev)
119
+ set(value!.filter((_rec, i) => i !== $idx), ev)
120
},
121
}),
122
reorder && $idx && h(GridActionsCellItem as any, {
admin/src/InstalledPlugins.ts
+6
-7
@@ -240,24 +240,23 @@ export function renderName({ row, value }: any) {
240
241
function makeFields(config: any, values: any) {
242
return Object.entries(config).map(([k,o]: [string,any]) => {
243
- let { type, defaultValue, fields, frontend, helperText, showIf, ...rest } = o
243
+ let { type, defaultValue, frontend, showIf, ...rest } = o
244
try {
245
if (typeof showIf === 'string') // compile once
246
- o.showIf = showIf = eval(showIf) // eval is normally considered a threat, but this code is coming from a plugin that's already running on your server, so you already decided to trust it. Here it will run in your browser, and inside the page that administrating the same server.
246
+ rest.showIf = showIf = eval(showIf) // eval is normally considered a threat, but this code is coming from a plugin that's already running on your server, so you already decided to trust it. Here it will run in your browser, and inside the page that administrating the same server.
247
if (showIf && !showIf(values))
248
return
249
}
250
catch {}
251
- if (helperText)
252
- helperText = md(helperText, { html: false })
251
+ rest.helperText &&= md(rest.helperText, { html: false })
252
const comp = (type2comp as any)[type] as Field<any> | undefined
253
if (comp === ArrayField) {
255
- rest.valuesForAdd = newObj(fields, x => x.defaultValue)
256
- fields = makeFields(fields, values)
254
+ rest.valuesForAdd = newObj(rest.fields, x => x.defaultValue)
255
+ rest.fields = makeFields(rest.fields, false)
256
}
257
if (defaultValue !== undefined && type === 'boolean')
258
rest.placeholder = `Default value is ${JSON.stringify(defaultValue)}`
260
- return { k, comp, fields, helperText, ...rest }
259
+ return { k, comp, ...rest }
260
})
261
}
262
src/api.plugins.ts
+1
-4
@@ -15,7 +15,6 @@ import {
15
} from './github'
16
import { HTTP_FAILED_DEPENDENCY, HTTP_NOT_FOUND, HTTP_SERVER_ERROR } from './const'
17
import { SendListReadable } from './SendList'
18
-import produce from 'immer'
18
19
const apis: ApiHandlers = {
20
@@ -192,9 +191,7 @@ function serialize(p: Readonly<Plugin> | AvailablePlugin) {
191
if (typeof o.repo === 'object') // custom repo
192
o.repo = o.repo.web
193
o.log = 'log' in p && p.log?.length > 0
195
- o = produce(o, (o: any) => {
196
- _.each(o.config, x => x.showIf &&= String(x.showIf))
197
- })
194
+ o.config &&= JSON.stringify(o.config, (_k, v) => _.isFunction(v) ? String(v) : v) // allow simple functions
195
return _.defaults(o, { started: null, badApi: null }) // nulls should be used to be sure to overwrite previous values,
196
}
197
src/cross.ts
+1
-1
@@ -38,7 +38,7 @@ export type Falsy = false | null | undefined | '' | 0
38
type Truthy<T> = T extends false | '' | 0 | null | undefined | void ? never : T
39
export type Callback<IN=void, OUT=void> = (x:IN) => OUT
40
export type Promisable<T> = T | Promise<T>
41
-export type Functionable<T> = T | ((...args: any[]) => T)
41
+export type Functionable<T, Args extends any[] = any[]> = T | ((...args: Args) => T)
42
export type Timeout = ReturnType<typeof setTimeout>
43
export interface VfsPerms {
44
can_see?: Who