ux: admin: avoid disabling the save button when changes are yet to be detected, so that the user doesn't falsely perceive that it's not possible to save yet

Massimo Melina committed Mar 16, 2022 at 16:14 UTC 2f9f807e1cef06fff45e3df35ea8f88d586ff89f
4 files changed +10 -6
admin/src/AccountsPage.ts
+2 -2
@@ -6,7 +6,7 @@ import { Box, Button, Card, CardContent, Grid, List, ListItem, ListItemText, Typ
6 import { Delete, Group, MilitaryTech, Person, PersonAdd, Refresh } from '@mui/icons-material'
7 import { BoolField, Form, MultiSelectField, StringField } from './Form'
8 import { alertDialog, confirmDialog } from './dialog'
9 -import { iconTooltip, isEqualLax, onlyTruthy } from './misc'
9 +import { iconTooltip, isEqualLax, modifiedSx, onlyTruthy } from './misc'
10 import { TreeItem, TreeView } from '@mui/lab'
11 import { makeStyles } from '@mui/styles'
12 import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
@@ -189,7 +189,7 @@ function AccountForm({ account, done, groups }: { account: Account, groups: stri
189 ],
190 onError: alertDialog,
191 save: {
192 - disabled: isEqualLax(values, account),
192 + sx: modifiedSx( !isEqualLax(values, account)),
193 async onClick() {
194 const { password='', password2, ...withoutPassword } = values
195 const { username } = values
admin/src/ConfigPage.ts
+2 -2
@@ -5,7 +5,7 @@ import { createElement as h, isValidElement, useEffect, useRef } from 'react';
5 import { apiCall, useApi, useApiComp } from './api'
6 import { state, useSnapState } from './state'
7 import { Refresh } from '@mui/icons-material'
8 -import { Dict } from './misc'
8 +import { Dict, modifiedSx } from './misc'
9 import { subscribeKey } from 'valtio/utils'
10 import { Form, BoolField, NumberField, StringField, SelectField, FieldProps, Field } from './Form';
11 import StringStringField from './StringStringField'
@@ -41,7 +41,7 @@ export default function ConfigPage() {
41 onError: alertDialog,
42 save: {
43 onClick: save,
44 - disabled: !Object.keys(changes).length,
44 + sx: modifiedSx( Object.keys(changes).length>0),
45 },
46 barSx: { gap: 2 },
47 addToBar: [h(Button, {
admin/src/FileCard.ts
+2 -2
@@ -5,7 +5,7 @@ import { createElement as h, useEffect, useMemo, useState } from 'react'
5 import { Card, CardContent, List, ListItem, ListItemText } from '@mui/material'
6 import { BoolField, DisplayField, Field, FieldProps, Form, MultiSelectField, SelectField } from './Form'
7 import { apiCall, useApi } from './api'
8 -import { formatBytes, isEqualLax, onlyTruthy } from './misc'
8 +import { formatBytes, isEqualLax, modifiedSx, onlyTruthy } from './misc'
9 import { reloadVfs, Who } from './VfsPage'
10 import md from './md'
11 import _ from 'lodash'
@@ -62,7 +62,7 @@ function FileForm({ file }: { file: ReturnType<typeof useSnapState>['selectedFil
62 setValues({ ...values, [k]: v })
63 },
64 save: {
65 - disabled: isEqualLax(values, file),
65 + sx: modifiedSx(!isEqualLax(values, file)),
66 async onClick() {
67 const props = _.pickBy(values, (v,k) =>
68 v !== file[k as keyof typeof values])
admin/src/misc.ts
+4
@@ -24,6 +24,10 @@ export function isEqualLax(a: any,b: any): boolean {
24 && Object.entries(a).every(([k,v]) => isEqualLax(v, b[k])) )
25 }
26
27 +export function modifiedSx(is: boolean) {
28 + return is ? { outline: '2px solid' } : undefined
29 +}
30 +
31 export function IconBtn({ title, icon, onClick, ...rest }: { title?: string, icon: SvgIconComponent, [rest:string]:any }) {
32 const [loading, setLoading] = useStateMounted(false)
33 const ret = h(IconButton, {