better code: better abstraction for save-buttons
Massimo Melina committed
Feb 21, 2024 at 10:15 UTC
ffb602db32607192a8634e6fa7fe8ef5f77b2a00
7 files changed
+19
-17
admin/src/AccountForm.ts
+2
-2
@@ -6,7 +6,7 @@ import { Alert } from '@mui/material'
6
import { apiCall } from './api'
7
import { alertDialog, toast, useDialogBarColors } from './dialog'
8
import { isEqualLax, wantArray } from './misc'
9
-import { IconBtn, modifiedSx } from './mui'
9
+import { IconBtn, modifiedProps } from './mui'
10
import { Account } from './AccountsPage'
11
import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
12
import { AutoDelete, Delete } from '@mui/icons-material'
@@ -89,7 +89,7 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
89
],
90
onError: alertDialog,
91
save: {
92
- sx: modifiedSx( !isEqualLax(values, account)),
92
+ ...modifiedProps( !isEqualLax(values, account)),
93
async onClick() {
94
const { password='', password2, adminActualAccess, hasPassword, invalidated, ...withoutPassword } = values
95
if (add) {
admin/src/ConfigFilePage.ts
+2
-2
@@ -4,7 +4,7 @@ import { createElement as h, Fragment, useEffect, useState } from 'react';
4
import { apiCall, useApiEx } from './api'
5
import { Alert, Box } from '@mui/material'
6
import { focusSelector, isCtrlKey, KeepInScreen } from './misc';
7
-import { Btn, Flex, IconBtn, modifiedSx, reloadBtn } from './mui';
7
+import { Btn, Flex, IconBtn, reloadBtn } from './mui';
8
import { Save, ContentCopy, EditNote } from '@mui/icons-material'
9
import { TextEditor } from './TextEditor';
10
import { state } from './state';
@@ -27,7 +27,7 @@ export default function ConfigFilePage() {
27
h(IconBtn, {
28
icon: Save,
29
title: "Save\n(ctrl+enter)",
30
- sx: modifiedSx(text !== saved),
30
+ modified: text !== saved,
31
onClick: save,
32
}),
33
h(Alert, { severity: 'warning', sx: { flex: 1, minWidth: '10em' } }, "Be careful, you can easily break things here"),
admin/src/ConfigForm.ts
+2
-2
@@ -2,7 +2,7 @@ import { Form, FormProps } from '@hfs/mui-grid-form'
2
import { apiCall, useApiEx } from './api'
3
import { createElement as h, useEffect, useState, Dispatch } from 'react'
4
import _ from 'lodash'
5
-import { IconBtn, modifiedSx } from './mui'
5
+import { IconBtn, modifiedProps } from './mui'
6
import { RestartAlt } from '@mui/icons-material'
7
import { Callback } from '../../src/cross'
8
@@ -30,7 +30,7 @@ export function ConfigForm<T=any>({ keys, form, saveOnChange, onSave, ...rest }:
30
},
31
save: saveOnChange ? false : {
32
onClick: save,
33
- sx: modifiedSx(modified),
33
+ ...modifiedProps(modified),
34
},
35
...Array.isArray(formProps) ? { fields: formProps } : formProps,
36
...rest,
admin/src/CustomHtmlPage.ts
+2
-2
@@ -5,7 +5,7 @@ import { Field, SelectField } from '@hfs/mui-grid-form'
5
import { apiCall, useApiEx } from './api'
6
import { Alert, Box } from '@mui/material'
7
import { Dict, HTTP_MESSAGES, isCtrlKey, prefix } from './misc';
8
-import { IconBtn, modifiedSx, reloadBtn, wikiLink } from './mui';
8
+import { IconBtn, reloadBtn, wikiLink } from './mui';
9
import { Save } from '@mui/icons-material'
10
import _ from 'lodash'
11
import { useDebounce } from 'usehooks-ts'
@@ -46,7 +46,7 @@ export default function CustomHtmlPage() {
46
h(IconBtn, {
47
icon: Save,
48
title: "Save\n(ctrl+enter)",
49
- sx: modifiedSx(anyChange),
49
+ modified: anyChange,
50
onClick: save,
51
}),
52
),
admin/src/FileForm.ts
+2
-2
@@ -8,7 +8,7 @@ import { BoolField, DisplayField, Field, FieldProps, Form, MultiSelectField, Sel
8
import { apiCall, UseApi, useApiEx } from './api'
9
import { basename, defaultPerms, formatBytes, formatTimestamp, isEqualLax, isWhoObject, newDialog, objSameKeys,
10
onlyTruthy, prefix, VfsPerms, wantArray, Who, WhoObject, matches, HTTP_MESSAGES, xlate } from './misc'
11
-import { Btn, IconBtn, LinkBtn, modifiedSx, useBreakpoint, wikiLink } from './mui'
11
+import { Btn, IconBtn, LinkBtn, modifiedProps, useBreakpoint, wikiLink } from './mui'
12
import { reloadVfs, VfsNode } from './VfsPage'
13
import md from './md'
14
import _ from 'lodash'
@@ -132,7 +132,7 @@ export default function FileForm({ file, addToBar, statusApi }: FileFormProps) {
132
],
133
onError: alertDialog,
134
save: {
135
- sx: modifiedSx(!isEqualLax(values, rest)),
135
+ ...modifiedProps(!isEqualLax(values, rest)),
136
async onClick() {
137
const props = _.omit(values, ['ctime','mtime','size','id'])
138
;(props as any).masks ||= null // undefined cannot be serialized
admin/src/OptionsPage.ts
+3
-3
@@ -5,10 +5,10 @@ import { createElement as h, Fragment, useEffect, useRef } from 'react';
5
import { apiCall, useApiEx } from './api'
6
import { state, useSnapState } from './state'
7
import { Link as RouterLink } from 'react-router-dom'
8
-import { CardMembership, Check, EditNote, Refresh, Warning } from '@mui/icons-material'
8
+import { CardMembership, EditNote, Refresh, Warning } from '@mui/icons-material'
9
import { Dict, MAX_TILE_SIZE, REPO_URL, isIpLocalHost, wait, with_, try_, ipForUrl, SORT_BY_OPTIONS, THEME_OPTIONS,
10
CFG } from './misc'
11
-import { iconTooltip, InLink, LinkBtn, modifiedSx, wikiLink, useBreakpoint, NetmaskField, WildcardsSupported } from './mui'
11
+import { iconTooltip, InLink, LinkBtn, modifiedProps, wikiLink, useBreakpoint, NetmaskField, WildcardsSupported } from './mui'
12
import { Form, BoolField, NumberField, SelectField, FieldProps, Field, StringField } from '@hfs/mui-grid-form';
13
import { ArrayField } from './ArrayField'
14
import FileField from './FileField'
@@ -80,7 +80,7 @@ export default function OptionsPage() {
80
onError: alertDialog,
81
save: {
82
onClick: save,
83
- sx: modifiedSx( Object.keys(changes).length>0),
83
+ ...modifiedProps( Object.keys(changes).length>0),
84
},
85
barSx: { gap: 2 },
86
addToBar: [
admin/src/mui.ts
+6
-4
@@ -104,9 +104,10 @@ export function reloadBtn(onClick: any, props?: any) {
104
return h(IconBtn, { icon: Refresh, title: "Reload", onClick, ...props })
105
}
106
107
-export function modifiedSx(is: boolean) {
108
- return is ? { outline: '2px solid' } : undefined
107
+export function modifiedProps(modified: boolean) {
108
+ return modified ? { sx: { outline: '2px solid' } } : undefined
109
}
110
+
111
interface IconBtnProps extends Omit<IconButtonProps, 'disabled'|'title'|'onClick'> {
112
title?: ReactNode
113
icon: SvgIconComponent
@@ -116,10 +117,11 @@ interface IconBtnProps extends Omit<IconButtonProps, 'disabled'|'title'|'onClick
117
confirm?: string
118
doneMessage?: boolean | string // displayed only if the result of onClick !== false
119
tooltipProps?: Partial<TooltipProps>
120
+ modified?: boolean
121
onClick?: (...args: Parameters<NonNullable<IconButtonProps['onClick']>>) => Promisable<any>
122
}
123
122
-export const IconBtn = forwardRef(({ title, icon, onClick, disabled, progress, link, tooltipProps, confirm, doneMessage, sx, ...rest }: IconBtnProps, ref: any) => {
124
+export const IconBtn = forwardRef(({ title, icon, onClick, disabled, progress, link, tooltipProps, confirm, doneMessage, sx, modified, ...rest }: IconBtnProps, ref: any) => {
125
const [loading, setLoading] = useStateMounted(false)
126
if (typeof disabled === 'string')
127
title = disabled
@@ -130,7 +132,7 @@ export const IconBtn = forwardRef(({ title, icon, onClick, disabled, progress, l
132
ref,
133
disabled,
134
...rest,
133
- sx: { height: 'fit-content', ...sx },
135
+ sx: { height: 'fit-content', ...modifiedProps(modified || false)?.sx, ...sx },
136
async onClick(...args) {
137
if (confirm && !await confirmDialog(confirm)) return
138
const ret = onClick?.apply(this,args)