rename SAVE to APPLY, show number of updates - #911

This commit is contained in:
proddy
2023-01-19 09:11:59 +01:00
parent 9ba0c6df37
commit 0ed3bfff4a
19 changed files with 287 additions and 112 deletions

View File

@@ -4,6 +4,7 @@ import { range } from 'lodash';
import { Button, Checkbox, MenuItem } from '@mui/material'; import { Button, Checkbox, MenuItem } from '@mui/material';
import SaveIcon from '@mui/icons-material/Save'; import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Cancel';
import { createAPSettingsValidator, validate } from '../../validators'; import { createAPSettingsValidator, validate } from '../../validators';
import { import {
@@ -16,7 +17,7 @@ import {
} from '../../components'; } from '../../components';
import { APProvisionMode, APSettings } from '../../types'; import { APProvisionMode, APSettings } from '../../types';
import { numberValue, updateValue, useRest } from '../../utils'; import { numberValue, updateValueDirty, useRest } from '../../utils';
import * as APApi from '../../api/ap'; import * as APApi from '../../api/ap';
import { useI18nContext } from '../../i18n/i18n-react'; import { useI18nContext } from '../../i18n/i18n-react';
@@ -26,16 +27,17 @@ export const isAPEnabled = ({ provision_mode }: APSettings) => {
}; };
const APSettingsForm: FC = () => { const APSettingsForm: FC = () => {
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<APSettings>({ const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, saveData, errorMessage } =
read: APApi.readAPSettings, useRest<APSettings>({
update: APApi.updateAPSettings read: APApi.readAPSettings,
}); update: APApi.updateAPSettings
});
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const updateFormValue = updateValue(setData); const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
const content = () => { const content = () => {
if (!data) { if (!data) {
@@ -163,18 +165,30 @@ const APSettingsForm: FC = () => {
/> />
</> </>
)} )}
<ButtonRow> {dirtyFlags && dirtyFlags.length !== 0 && (
<Button <ButtonRow>
startIcon={<SaveIcon />} <Button
disabled={saving} startIcon={<CancelIcon />}
variant="outlined" disabled={saving}
color="primary" variant="outlined"
type="submit" color="primary"
onClick={validateAndSubmit} type="submit"
> onClick={() => loadData()}
{LL.SAVE()} >
</Button> {LL.CANCEL()}
</ButtonRow> </Button>
<Button
startIcon={<SaveIcon />}
disabled={saving}
variant="outlined"
color="primary"
type="submit"
onClick={validateAndSubmit}
>
{LL.APPLY_CHANGES(dirtyFlags.length)}
</Button>
</ButtonRow>
)}
</> </>
); );
}; };

View File

@@ -3,6 +3,7 @@ import { ValidateFieldsError } from 'async-validator';
import { Button, Checkbox, MenuItem, Grid, Typography, InputAdornment } from '@mui/material'; import { Button, Checkbox, MenuItem, Grid, Typography, InputAdornment } from '@mui/material';
import SaveIcon from '@mui/icons-material/Save'; import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Cancel';
import { createMqttSettingsValidator, validate } from '../../validators'; import { createMqttSettingsValidator, validate } from '../../validators';
import { import {
@@ -14,22 +15,23 @@ import {
ValidatedTextField ValidatedTextField
} from '../../components'; } from '../../components';
import { MqttSettings } from '../../types'; import { MqttSettings } from '../../types';
import { numberValue, updateValue, useRest } from '../../utils'; import { numberValue, updateValueDirty, useRest } from '../../utils';
import * as MqttApi from '../../api/mqtt'; import * as MqttApi from '../../api/mqtt';
import { useI18nContext } from '../../i18n/i18n-react'; import { useI18nContext } from '../../i18n/i18n-react';
const MqttSettingsForm: FC = () => { const MqttSettingsForm: FC = () => {
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<MqttSettings>({ const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, saveData, errorMessage } =
read: MqttApi.readMqttSettings, useRest<MqttSettings>({
update: MqttApi.updateMqttSettings read: MqttApi.readMqttSettings,
}); update: MqttApi.updateMqttSettings
});
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const updateFormValue = updateValue(setData); const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
const content = () => { const content = () => {
if (!data) { if (!data) {
@@ -372,18 +374,31 @@ const MqttSettingsForm: FC = () => {
/> />
</Grid> </Grid>
</Grid> </Grid>
<ButtonRow>
<Button {dirtyFlags && dirtyFlags.length !== 0 && (
startIcon={<SaveIcon />} <ButtonRow>
disabled={saving} <Button
variant="outlined" startIcon={<CancelIcon />}
color="primary" disabled={saving}
type="submit" variant="outlined"
onClick={validateAndSubmit} color="primary"
> type="submit"
{LL.SAVE()} onClick={() => loadData()}
</Button> >
</ButtonRow> {LL.CANCEL()}
</Button>
<Button
startIcon={<SaveIcon />}
disabled={saving}
variant="outlined"
color="primary"
type="submit"
onClick={validateAndSubmit}
>
{LL.APPLY_CHANGES(dirtyFlags.length)}
</Button>
</ButtonRow>
)}
</> </>
); );
}; };

View File

@@ -20,6 +20,7 @@ import DeleteIcon from '@mui/icons-material/Delete';
import SaveIcon from '@mui/icons-material/Save'; import SaveIcon from '@mui/icons-material/Save';
import LockIcon from '@mui/icons-material/Lock'; import LockIcon from '@mui/icons-material/Lock';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import CancelIcon from '@mui/icons-material/Cancel';
import { import {
BlockFormControlLabel, BlockFormControlLabel,
@@ -32,7 +33,7 @@ import {
} from '../../components'; } from '../../components';
import { NetworkSettings } from '../../types'; import { NetworkSettings } from '../../types';
import * as NetworkApi from '../../api/network'; import * as NetworkApi from '../../api/network';
import { numberValue, updateValue, useRest } from '../../utils'; import { numberValue, updateValueDirty, useRest } from '../../utils';
import * as EMSESP from '../../project/api'; import * as EMSESP from '../../project/api';
import { WiFiConnectionContext } from './WiFiConnectionContext'; import { WiFiConnectionContext } from './WiFiConnectionContext';
@@ -52,7 +53,18 @@ const WiFiSettingsForm: FC = () => {
const [initialized, setInitialized] = useState(false); const [initialized, setInitialized] = useState(false);
const [restarting, setRestarting] = useState(false); const [restarting, setRestarting] = useState(false);
const { loadData, saving, data, setData, saveData, errorMessage, restartNeeded } = useRest<NetworkSettings>({ const {
loadData,
saving,
data,
setData,
origData,
dirtyFlags,
setDirtyFlags,
saveData,
errorMessage,
restartNeeded
} = useRest<NetworkSettings>({
read: NetworkApi.readNetworkSettings, read: NetworkApi.readNetworkSettings,
update: NetworkApi.updateNetworkSettings update: NetworkApi.updateNetworkSettings
}); });
@@ -78,7 +90,7 @@ const WiFiSettingsForm: FC = () => {
} }
}, [initialized, setInitialized, data, setData, selectedNetwork]); }, [initialized, setInitialized, data, setData, selectedNetwork]);
const updateFormValue = updateValue(setData); const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -287,8 +299,19 @@ const WiFiSettingsForm: FC = () => {
</Button> </Button>
</MessageBox> </MessageBox>
)} )}
{!restartNeeded && (
{!restartNeeded && dirtyFlags && dirtyFlags.length !== 0 && (
<ButtonRow> <ButtonRow>
<Button
startIcon={<CancelIcon />}
disabled={saving}
variant="outlined"
color="primary"
type="submit"
onClick={() => loadData()}
>
{LL.CANCEL()}
</Button>
<Button <Button
startIcon={<SaveIcon />} startIcon={<SaveIcon />}
disabled={saving} disabled={saving}
@@ -297,7 +320,7 @@ const WiFiSettingsForm: FC = () => {
type="submit" type="submit"
onClick={validateAndSubmit} onClick={validateAndSubmit}
> >
{LL.SAVE()} {LL.APPLY_CHANGES(dirtyFlags.length)}
</Button> </Button>
</ButtonRow> </ButtonRow>
)} )}

View File

@@ -3,11 +3,12 @@ import { ValidateFieldsError } from 'async-validator';
import { Button, Checkbox, MenuItem } from '@mui/material'; import { Button, Checkbox, MenuItem } from '@mui/material';
import SaveIcon from '@mui/icons-material/Save'; import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Cancel';
import { validate } from '../../validators'; import { validate } from '../../validators';
import { BlockFormControlLabel, ButtonRow, FormLoader, SectionContent, ValidatedTextField } from '../../components'; import { BlockFormControlLabel, ButtonRow, FormLoader, SectionContent, ValidatedTextField } from '../../components';
import { NTPSettings } from '../../types'; import { NTPSettings } from '../../types';
import { updateValue, useRest } from '../../utils'; import { updateValueDirty, useRest } from '../../utils';
import * as NTPApi from '../../api/ntp'; import * as NTPApi from '../../api/ntp';
import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ'; import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ';
import { NTP_SETTINGS_VALIDATOR } from '../../validators/ntp'; import { NTP_SETTINGS_VALIDATOR } from '../../validators/ntp';
@@ -15,14 +16,15 @@ import { NTP_SETTINGS_VALIDATOR } from '../../validators/ntp';
import { useI18nContext } from '../../i18n/i18n-react'; import { useI18nContext } from '../../i18n/i18n-react';
const NTPSettingsForm: FC = () => { const NTPSettingsForm: FC = () => {
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<NTPSettings>({ const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, saveData, errorMessage } =
read: NTPApi.readNTPSettings, useRest<NTPSettings>({
update: NTPApi.updateNTPSettings read: NTPApi.readNTPSettings,
}); update: NTPApi.updateNTPSettings
});
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const updateFormValue = updateValue(setData); const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -79,18 +81,30 @@ const NTPSettingsForm: FC = () => {
<MenuItem disabled>{LL.TIME_ZONE()}...</MenuItem> <MenuItem disabled>{LL.TIME_ZONE()}...</MenuItem>
{timeZoneSelectItems()} {timeZoneSelectItems()}
</ValidatedTextField> </ValidatedTextField>
<ButtonRow> {dirtyFlags && dirtyFlags.length !== 0 && (
<Button <ButtonRow>
startIcon={<SaveIcon />} <Button
disabled={saving} startIcon={<CancelIcon />}
variant="outlined" disabled={saving}
color="primary" variant="outlined"
type="submit" color="primary"
onClick={validateAndSubmit} type="submit"
> onClick={() => loadData()}
{LL.SAVE()} >
</Button> {LL.CANCEL()}
</ButtonRow> </Button>
<Button
startIcon={<SaveIcon />}
disabled={saving}
variant="outlined"
color="primary"
type="submit"
onClick={validateAndSubmit}
>
{LL.APPLY_CHANGES(dirtyFlags.length)}
</Button>
</ButtonRow>
)}
</> </>
); );
}; };

View File

@@ -129,7 +129,7 @@ const NTPStatusForm: FC = () => {
color="primary" color="primary"
autoFocus autoFocus
> >
{LL.SAVE()} {LL.UPDATE()}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@@ -185,7 +185,7 @@ const ManageUsersForm: FC = () => {
type="submit" type="submit"
onClick={onSubmit} onClick={onSubmit}
> >
{LL.SAVE()} {LL.UPDATE()}
</Button> </Button>
</Box> </Box>

View File

@@ -3,12 +3,13 @@ import { ValidateFieldsError } from 'async-validator';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
import SaveIcon from '@mui/icons-material/Save'; import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Cancel';
import * as SecurityApi from '../../api/security'; import * as SecurityApi from '../../api/security';
import { SecuritySettings } from '../../types'; import { SecuritySettings } from '../../types';
import { ButtonRow, FormLoader, MessageBox, SectionContent, ValidatedPasswordField } from '../../components'; import { ButtonRow, FormLoader, MessageBox, SectionContent, ValidatedPasswordField } from '../../components';
import { SECURITY_SETTINGS_VALIDATOR, validate } from '../../validators'; import { SECURITY_SETTINGS_VALIDATOR, validate } from '../../validators';
import { updateValue, useRest } from '../../utils'; import { updateValueDirty, useRest } from '../../utils';
import { AuthenticatedContext } from '../../contexts/authentication'; import { AuthenticatedContext } from '../../contexts/authentication';
import { useI18nContext } from '../../i18n/i18n-react'; import { useI18nContext } from '../../i18n/i18n-react';
@@ -17,13 +18,15 @@ const SecuritySettingsForm: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<SecuritySettings>({ const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, saveData, errorMessage } =
read: SecurityApi.readSecuritySettings, useRest<SecuritySettings>({
update: SecurityApi.updateSecuritySettings read: SecurityApi.readSecuritySettings,
}); update: SecurityApi.updateSecuritySettings
});
const authenticatedContext = useContext(AuthenticatedContext); const authenticatedContext = useContext(AuthenticatedContext);
const updateFormValue = updateValue(setData);
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
const content = () => { const content = () => {
if (!data) { if (!data) {
@@ -54,18 +57,30 @@ const SecuritySettingsForm: FC = () => {
margin="normal" margin="normal"
/> />
<MessageBox level="info" message={LL.SU_TEXT()} mt={1} /> <MessageBox level="info" message={LL.SU_TEXT()} mt={1} />
<ButtonRow> {dirtyFlags && dirtyFlags.length !== 0 && (
<Button <ButtonRow>
startIcon={<SaveIcon />} <Button
disabled={saving} startIcon={<CancelIcon />}
variant="outlined" disabled={saving}
color="primary" variant="outlined"
type="submit" color="primary"
onClick={validateAndSubmit} type="submit"
> onClick={() => loadData()}
{LL.SAVE()} >
</Button> {LL.CANCEL()}
</ButtonRow> </Button>
<Button
startIcon={<SaveIcon />}
disabled={saving}
variant="outlined"
color="primary"
type="submit"
onClick={validateAndSubmit}
>
{LL.APPLY_CHANGES(dirtyFlags.length)}
</Button>
</ButtonRow>
)}
</> </>
); );
}; };

View File

@@ -2,6 +2,7 @@ import { FC, useState } from 'react';
import { Button, Checkbox } from '@mui/material'; import { Button, Checkbox } from '@mui/material';
import SaveIcon from '@mui/icons-material/Save'; import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Cancel';
import * as SystemApi from '../../api/system'; import * as SystemApi from '../../api/system';
import { import {
@@ -14,7 +15,7 @@ import {
} from '../../components'; } from '../../components';
import { OTASettings } from '../../types'; import { OTASettings } from '../../types';
import { numberValue, updateValue, useRest } from '../../utils'; import { numberValue, updateValueDirty, useRest } from '../../utils';
import { ValidateFieldsError } from 'async-validator'; import { ValidateFieldsError } from 'async-validator';
import { validate } from '../../validators'; import { validate } from '../../validators';
@@ -23,14 +24,15 @@ import { OTA_SETTINGS_VALIDATOR } from '../../validators/system';
import { useI18nContext } from '../../i18n/i18n-react'; import { useI18nContext } from '../../i18n/i18n-react';
const OTASettingsForm: FC = () => { const OTASettingsForm: FC = () => {
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<OTASettings>({ const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, saveData, errorMessage } =
read: SystemApi.readOTASettings, useRest<OTASettings>({
update: SystemApi.updateOTASettings read: SystemApi.readOTASettings,
}); update: SystemApi.updateOTASettings
});
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const updateFormValue = updateValue(setData); const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -76,18 +78,30 @@ const OTASettingsForm: FC = () => {
onChange={updateFormValue} onChange={updateFormValue}
margin="normal" margin="normal"
/> />
<ButtonRow> {dirtyFlags && dirtyFlags.length !== 0 && (
<Button <ButtonRow>
startIcon={<SaveIcon />} <Button
disabled={saving} startIcon={<CancelIcon />}
variant="outlined" disabled={saving}
color="primary" variant="outlined"
type="submit" color="primary"
onClick={validateAndSubmit} type="submit"
> onClick={() => loadData()}
{LL.SAVE()} >
</Button> {LL.CANCEL()}
</ButtonRow> </Button>
<Button
startIcon={<SaveIcon />}
disabled={saving}
variant="outlined"
color="primary"
type="submit"
onClick={validateAndSubmit}
>
{LL.APPLY_CHANGES(dirtyFlags.length)}
</Button>
</ButtonRow>
)}
</> </>
); );
}; };

View File

@@ -15,6 +15,8 @@ const de: Translation = {
DASHBOARD: 'Kontrollzentrum', DASHBOARD: 'Kontrollzentrum',
SETTINGS_OF: '{0} Einstellungen', SETTINGS_OF: '{0} Einstellungen',
SAVED: 'gespeichert', SAVED: 'gespeichert',
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
UPDATE: 'Update', // TODO translate
HELP_OF: '{0} Hilfe', HELP_OF: '{0} Hilfe',
LOGGED_IN: 'Eingeloggt als {name}', LOGGED_IN: 'Eingeloggt als {name}',
PLEASE_SIGNIN: 'Bitte einloggen, um fortzufahren', PLEASE_SIGNIN: 'Bitte einloggen, um fortzufahren',

View File

@@ -48,6 +48,8 @@ const en: Translation = {
RESET: 'Reset', RESET: 'Reset',
SEND: 'Send', SEND: 'Send',
SAVE: 'Save', SAVE: 'Save',
APPLY_CHANGES: 'Apply Changes ({0})',
UPDATE: 'Update',
REMOVE: 'Remove', REMOVE: 'Remove',
PROBLEM_UPDATING: 'Problem updating', PROBLEM_UPDATING: 'Problem updating',
PROBLEM_LOADING: 'Problem loading', PROBLEM_LOADING: 'Problem loading',

View File

@@ -48,6 +48,8 @@ const fr: Translation = {
RESET: 'Réinitialiser', RESET: 'Réinitialiser',
SEND: 'Envoyer', SEND: 'Envoyer',
SAVE: 'Sauvegarder', SAVE: 'Sauvegarder',
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
UPDATE: 'Update', // TODO translate
REMOVE: 'Enlever', REMOVE: 'Enlever',
PROBLEM_UPDATING: 'Problème lors de la mise à jour', PROBLEM_UPDATING: 'Problème lors de la mise à jour',
PROBLEM_LOADING: 'Problème lors du chargement', PROBLEM_LOADING: 'Problème lors du chargement',

View File

@@ -48,6 +48,8 @@ const nl: Translation = {
RESET: 'Reset', RESET: 'Reset',
SEND: 'Verzenden', SEND: 'Verzenden',
SAVE: 'Opslaan', SAVE: 'Opslaan',
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
UPDATE: 'Update', // TODO translate
REMOVE: 'Verwijderen', REMOVE: 'Verwijderen',
PROBLEM_UPDATING: 'Probleem met updaten', PROBLEM_UPDATING: 'Probleem met updaten',
PROBLEM_LOADING: 'Probleem met laden', PROBLEM_LOADING: 'Probleem met laden',

View File

@@ -48,6 +48,8 @@ const no: Translation = {
RESET: 'Nullstill', RESET: 'Nullstill',
SEND: 'Send', SEND: 'Send',
SAVE: 'Lagre', SAVE: 'Lagre',
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
UPDATE: 'Update', // TODO translate
REMOVE: 'Fjern', REMOVE: 'Fjern',
PROBLEM_UPDATING: 'Problem med oppdatering', PROBLEM_UPDATING: 'Problem med oppdatering',
PROBLEM_LOADING: 'Problem med opplasting', PROBLEM_LOADING: 'Problem med opplasting',

View File

@@ -48,6 +48,8 @@ const pl: BaseTranslation = {
RESET: 'Reset{{uj|owanie|}}', RESET: 'Reset{{uj|owanie|}}',
SEND: 'Wyślij', SEND: 'Wyślij',
SAVE: 'Zapisz', SAVE: 'Zapisz',
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
UPDATE: 'Update', // TODO translate
REMOVE: 'Usuń', REMOVE: 'Usuń',
PROBLEM_UPDATING: 'Problem z aktualizacją!', PROBLEM_UPDATING: 'Problem z aktualizacją!',
PROBLEM_LOADING: 'Problem z załadowaniem!', PROBLEM_LOADING: 'Problem z załadowaniem!',

View File

@@ -48,6 +48,8 @@ const sv: Translation = {
RESET: 'Nollsäll', RESET: 'Nollsäll',
SEND: 'Skicka', SEND: 'Skicka',
SAVE: 'Spara', SAVE: 'Spara',
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
UPDATE: 'Update', // TODO translate
REMOVE: 'Ta bort', REMOVE: 'Ta bort',
PROBLEM_UPDATING: 'Problem vid uppdatering', PROBLEM_UPDATING: 'Problem vid uppdatering',
PROBLEM_LOADING: 'Problem vid hämtning', PROBLEM_LOADING: 'Problem vid hämtning',

View File

@@ -634,7 +634,7 @@ const DashboardData: FC = () => {
onClick={() => sendSensor()} onClick={() => sendSensor()}
color="warning" color="warning"
> >
{LL.SAVE()} {LL.UPDATE()}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@@ -1056,7 +1056,7 @@ const DashboardData: FC = () => {
<ValidatedTextField <ValidatedTextField
name="g" name="g"
label="GPIO" label="GPIO"
value={analog.g} value={numberValue(analog.g)}
fullWidth fullWidth
type="number" type="number"
variant="outlined" variant="outlined"
@@ -1235,7 +1235,7 @@ const DashboardData: FC = () => {
onClick={() => sendAnalog()} onClick={() => sendAnalog()}
color="warning" color="warning"
> >
{LL.SAVE()} {LL.UPDATE()}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@@ -6,6 +6,7 @@ import { useSnackbar } from 'notistack';
import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment } from '@mui/material'; import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment } from '@mui/material';
import SaveIcon from '@mui/icons-material/Save'; import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Cancel';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import { validate } from '../validators'; import { validate } from '../validators';
@@ -19,7 +20,7 @@ import {
ButtonRow, ButtonRow,
MessageBox MessageBox
} from '../components'; } from '../components';
import { numberValue, extractErrorMessage, updateValue, useRest } from '../utils'; import { numberValue, extractErrorMessage, updateValueDirty, useRest } from '../utils';
import * as EMSESP from './api'; import * as EMSESP from './api';
import { Settings, BOARD_PROFILES } from './types'; import { Settings, BOARD_PROFILES } from './types';
@@ -36,7 +37,18 @@ export function boardProfileSelectItems() {
} }
const SettingsApplication: FC = () => { const SettingsApplication: FC = () => {
const { loadData, saveData, saving, setData, data, errorMessage, restartNeeded } = useRest<Settings>({ const {
loadData,
saveData,
saving,
setData,
data,
origData,
dirtyFlags,
setDirtyFlags,
errorMessage,
restartNeeded
} = useRest<Settings>({
read: EMSESP.readSettings, read: EMSESP.readSettings,
update: EMSESP.writeSettings update: EMSESP.writeSettings
}); });
@@ -46,7 +58,7 @@ const SettingsApplication: FC = () => {
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const updateFormValue = updateValue(setData); const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const [processingBoard, setProcessingBoard] = useState<boolean>(false); const [processingBoard, setProcessingBoard] = useState<boolean>(false);
@@ -431,7 +443,7 @@ const SettingsApplication: FC = () => {
endAdornment: <InputAdornment position="end">{LL.MINUTES()}</InputAdornment> endAdornment: <InputAdornment position="end">{LL.MINUTES()}</InputAdornment>
}} }}
variant="outlined" variant="outlined"
value={data.shower_alert_trigger} value={numberValue(data.shower_alert_trigger)}
type="number" type="number"
onChange={updateFormValue} onChange={updateFormValue}
size="small" size="small"
@@ -447,7 +459,7 @@ const SettingsApplication: FC = () => {
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment> endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
}} }}
variant="outlined" variant="outlined"
value={data.shower_alert_coldshot} value={numberValue(data.shower_alert_coldshot)}
type="number" type="number"
onChange={updateFormValue} onChange={updateFormValue}
size="small" size="small"
@@ -566,7 +578,7 @@ const SettingsApplication: FC = () => {
label="Port" label="Port"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.syslog_port} value={numberValue(data.syslog_port)}
type="number" type="number"
onChange={updateFormValue} onChange={updateFormValue}
margin="normal" margin="normal"
@@ -603,7 +615,7 @@ const SettingsApplication: FC = () => {
}} }}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.syslog_mark_interval} value={numberValue(data.syslog_mark_interval)}
type="number" type="number"
onChange={updateFormValue} onChange={updateFormValue}
margin="normal" margin="normal"
@@ -619,8 +631,19 @@ const SettingsApplication: FC = () => {
</Button> </Button>
</MessageBox> </MessageBox>
)} )}
{!restartNeeded && (
{!restartNeeded && dirtyFlags && dirtyFlags.length !== 0 && (
<ButtonRow> <ButtonRow>
<Button
startIcon={<CancelIcon />}
disabled={saving}
variant="outlined"
color="primary"
type="submit"
onClick={() => loadData()}
>
{LL.CANCEL()}
</Button>
<Button <Button
startIcon={<SaveIcon />} startIcon={<SaveIcon />}
disabled={saving} disabled={saving}
@@ -629,7 +652,7 @@ const SettingsApplication: FC = () => {
type="submit" type="submit"
onClick={validateAndSubmit} onClick={validateAndSubmit}
> >
{LL.SAVE()} {LL.APPLY_CHANGES(dirtyFlags.length)}
</Button> </Button>
</ButtonRow> </ButtonRow>
)} )}

View File

@@ -21,3 +21,29 @@ export const updateValue =
[event.target.name]: extractEventValue(event) [event.target.name]: extractEventValue(event)
})); }));
}; };
export const updateValueDirty =
<S>(origData: any, dirtyFlags: any, setDirtyFlags: any, updateEntity: UpdateEntity<S>) =>
(event: React.ChangeEvent<HTMLInputElement>) => {
const updated_value = extractEventValue(event);
const name = event.target.name;
updateEntity((prevState) => ({
...prevState,
[name]: updated_value
}));
const arr: string[] = dirtyFlags;
if (origData[name] !== updated_value) {
if (!arr.includes(name)) {
arr.push(name);
}
} else {
const startIndex = arr.indexOf(name);
if (startIndex !== -1) {
arr.splice(startIndex, 1);
}
}
setDirtyFlags(arr);
};

View File

@@ -16,16 +16,22 @@ export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const [saving, setSaving] = useState<boolean>(false);
const [data, setData] = useState<D>(); const [data, setData] = useState<D>();
const [saving, setSaving] = useState<boolean>(false);
const [errorMessage, setErrorMessage] = useState<string>(); const [errorMessage, setErrorMessage] = useState<string>();
const [restartNeeded, setRestartNeeded] = useState<boolean>(false); const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
const [origData, setOrigData] = useState<D>();
const [dirtyFlags, setDirtyFlags] = useState<string[]>();
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
setData(undefined); setData(undefined);
setDirtyFlags([]);
setErrorMessage(undefined); setErrorMessage(undefined);
try { try {
setData((await read()).data); const fetch_data = (await read()).data;
setData(fetch_data);
setOrigData(fetch_data);
} catch (error) { } catch (error) {
const message = extractErrorMessage(error, LL.PROBLEM_LOADING()); const message = extractErrorMessage(error, LL.PROBLEM_LOADING());
enqueueSnackbar(message, { variant: 'error' }); enqueueSnackbar(message, { variant: 'error' });
@@ -66,5 +72,16 @@ export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
loadData(); loadData();
}, [loadData]); }, [loadData]);
return { loadData, saveData, saving, setData, data, errorMessage, restartNeeded } as const; return {
loadData,
saveData,
saving,
setData,
data,
origData,
dirtyFlags,
setDirtyFlags,
errorMessage,
restartNeeded
} as const;
}; };