diff --git a/src/components/Settings/ChangesList.js b/src/components/Settings/ChangesList.js new file mode 100644 index 000000000..7dd82ec03 --- /dev/null +++ b/src/components/Settings/ChangesList.js @@ -0,0 +1,43 @@ +import React from 'react' +import PropTypes from 'prop-types' + +import style from './style.css' + +const ChangesList = ({ changes = [], translatedLabels = [] }) => { + // start with translated labels to preserve the order of sections/fields + const filteredSectionTree = translatedLabels + .filter(({ fieldName }) => changes.find(changeName => changeName === fieldName)) + // group fields by section (keep the order of sections) + // output { "some section": ["field A", "field B", ...], ...} + .reduce((acc, { fieldTitle, sectionTitle }) => { + if (acc[sectionTitle]) { + acc[sectionTitle].push(fieldTitle) + } else { + acc[sectionTitle] = [fieldTitle] + } + return acc + }, {}) + + return ( + + ) +} + +ChangesList.propTypes = { + changes: PropTypes.array, + translatedLabels: PropTypes.array.isRequired, +} + +export default ChangesList diff --git a/src/components/Settings/Settings.js b/src/components/Settings/Settings.js index 139c0ee50..4bf292351 100644 --- a/src/components/Settings/Settings.js +++ b/src/components/Settings/Settings.js @@ -8,6 +8,7 @@ import CounterAlert from '_/components/CounterAlert' import { generateUnique } from '_/helpers' import { MsgContext } from '_/intl' import style from './style.css' +import ChangesList from './ChangesList' const changedInTheMeantime = ({ currentValues = {}, baseValues = {}, draftValues = {}, sentValues = {} }) => { return Object.keys(currentValues).filter(name => @@ -94,7 +95,7 @@ const Settings = ({ draftValues, onSave, lastTransactionId, onCancel, useEffect(() => { const partialSaveState = { show: partialSuccess, - fields: pendingChanges.map(e =>

{translatedLabels[e]}

), + fields: pendingChanges, } if (partialSaveState.show) { setShowPartialSave(partialSaveState) } if (completeFailure) { setShowCompleteFailure(completeFailure) } @@ -128,7 +129,7 @@ const Settings = ({ draftValues, onSave, lastTransactionId, onCancel, type='error' title={

{msg.failedToSaveChangesToFields()}

} onDismiss={() => resetNotifications(setShowPartialSave, { show: false, fields: [] })} > - {partialSave.fields} + } { showCompleteFailure && @@ -157,7 +158,7 @@ Settings.propTypes = { currentValues: PropTypes.object.isRequired, baseValues: PropTypes.object.isRequired, sentValues: PropTypes.object.isRequired, - translatedLabels: PropTypes.object.isRequired, + translatedLabels: PropTypes.array.isRequired, lastTransactionId: PropTypes.string, onSave: PropTypes.func.isRequired, onReset: PropTypes.func.isRequired, diff --git a/src/components/Settings/SettingsToolbar.js b/src/components/Settings/SettingsToolbar.js index e1b2b31ba..3164c63c9 100644 --- a/src/components/Settings/SettingsToolbar.js +++ b/src/components/Settings/SettingsToolbar.js @@ -6,13 +6,13 @@ import { MsgContext } from '_/intl' import style from './style.css' import ConfirmationModal from '_/components/VmActions/ConfirmationModal' +import ChangesList from './ChangesList' const SettingsToolbar = ({ onSave, onReset, onCancel, enableSave, enableReset, translatedLabels, changes = [] }) => { const { msg } = useContext(MsgContext) const [container] = useState(document.createElement('div')) const [showSaveConfirmModal, setShowSaveConfirmModal] = useState(false) const [showResetConfirmModal, setShowResetConfirmModal] = useState(false) - const idPrefix = 'settings_toolbar' useEffect(() => { const root = document.getElementById('settings-toolbar') @@ -39,23 +39,13 @@ const SettingsToolbar = ({ onSave, onReset, onCancel, enableSave, enableReset, t setShowResetConfirmModal(false) } - const buildConfirmationModalSubContent = () => ( - - ) - return ReactDOM.createPortal( } onClose={onSaveClose} confirm={{ title: msg.yes(), @@ -113,7 +103,7 @@ SettingsToolbar.propTypes = { onSave: PropTypes.func.isRequired, onReset: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired, - translatedLabels: PropTypes.object.isRequired, + translatedLabels: PropTypes.array.isRequired, enableSave: PropTypes.bool, changes: PropTypes.array, } diff --git a/src/components/Settings/style.css b/src/components/Settings/style.css index 6afca8517..918407300 100644 --- a/src/components/Settings/style.css +++ b/src/components/Settings/style.css @@ -61,7 +61,12 @@ :global(#settings-toolbar) { margin-left: -20px; } -.changes-list{ + +.section-list{ list-style-type: disc; - list-style: inside; +} + +.field-list{ + list-style-type: circle; + margin-inline-start: 1em; } diff --git a/src/components/UserSettings/GlobalSettings.js b/src/components/UserSettings/GlobalSettings.js index b57f773c8..90cb1f610 100644 --- a/src/components/UserSettings/GlobalSettings.js +++ b/src/components/UserSettings/GlobalSettings.js @@ -148,7 +148,7 @@ class GlobalSettings extends Component { this.saveOptions(saveFields, id) } - buildSections (onChange, translatedLabels) { + buildSections (onChange) { const { draftValues } = this.state const { config, msg } = this.props const idPrefix = 'global-user-settings' @@ -156,139 +156,139 @@ class GlobalSettings extends Component { [GENERAL_SECTION]: { title: msg.general(), fields: [ - { + ((name) => ({ title: msg.username(), - name: 'username', - body: {config.userName}, - }, - { + name, + body: {config[name]}, + }))('userName'), + ((name) => ({ title: msg.email(), - name: 'email', - body: {config.email}, - }, - { - title: translatedLabels.language, - name: 'language', + name, + body: {config[name]}, + }))('email'), + ((name) => ({ + title: msg.language(), + name, tooltip: draftValues.persistLocale ? undefined : msg.optionIsNotSavedOnTheServer({ persistenceReEnableHowTo: msg.persistenceReEnableHowTo({ advancedOptions: msg.advancedOptions() }) }), body: (
({ id, value, isDefault: id === DEFAULT_LOCALE }))} - selected={draftValues.language} - onChange={onChange('language')} + selected={draftValues[name]} + onChange={onChange(name)} />
), - }, - { - title: translatedLabels.sshKey, + }))('language'), + ((name) => ({ + title: msg.sshKey(), tooltip: msg.sshKeyTooltip(), - name: 'sshKey', + name, body: (
onChange('sshKey')(e.target.value)} - value={draftValues.sshKey || ''} + onChange={e => onChange(name)(e.target.value)} + value={draftValues[name] || ''} rows={8} />
), - }, + }))('sshKey'), ], }, refreshInterval: { title: msg.refreshInterval(), tooltip: msg.refreshIntervalTooltip(), fields: [ - { - title: translatedLabels.refreshInterval, - name: 'refreshInterval', + ((name) => ({ + title: msg.uiRefresh(), + name, body: (
({ id, value, isDefault: id === AppConfiguration.schedulerFixedDelayInSeconds }))} - selected={draftValues.refreshInterval} - onChange={onChange('refreshInterval')} + selected={draftValues[name]} + onChange={onChange(name)} />
), - }, + }))('refreshInterval'), ], }, notifications: { title: msg.notifications(), tooltip: msg.notificationSettingsAffectAllNotifications(), fields: [ - { - title: translatedLabels.showNotifications, - name: 'showNotificatons', + ((name) => ({ + title: msg.dontDisturb(), + name, body: ( { - onChange('showNotifications')(!dontDisturb) + onChange(name)(!dontDisturb) }} /> ), - }, - { - title: translatedLabels.notificationSnoozeDuration, - name: 'notificationSnoozeDuration', + }))('showNotifications'), + ((name) => ({ + title: msg.dontDisturbFor(), + name, body: (
({ id, value, isDefault: id === AppConfiguration.notificationSnoozeDurationInMinutes }))} - selected={draftValues.notificationSnoozeDuration} - onChange={onChange('notificationSnoozeDuration')} + selected={draftValues[name]} + onChange={onChange(name)} disabled={draftValues.showNotifications} />
), - }, + }))('notificationSnoozeDuration'), ], }, advancedOptions: { title: msg.advancedOptions(), fields: [ - { + ((name) => ({ title: msg.persistLanguage(), - name: 'persistLocale', + name, tooltip: msg.persistLanguageTooltip(), body: ( onChange('persistLocale')(persist)} + id={`${idPrefix}-${name}`} + isChecked={draftValues[name]} + onChange={(persist) => onChange(name)(persist)} />), - }, + }))('persistLocale'), ], }, } } render () { - const { lastTransactionId, currentValues, msg } = this.props + const { lastTransactionId, currentValues } = this.props const { draftValues, baseValues, sentValues, defaultValues, activeSectionKey } = this.state - // required also in Settings for error handling: the case of partial success(only some fields saved) - // the alert shows the names of the fields that were NOT saved - const translatedLabels = { - sshKey: msg.sshKey(), - language: msg.language(), - showNotifications: msg.dontDisturb(), - notificationSnoozeDuration: msg.dontDisturbFor(), - refreshInterval: msg.uiRefresh(), - persistLocale: msg.persistLanguage(), - } - const sections = this.buildSections(this.onChange, translatedLabels) + const sections = this.buildSections(this.onChange) const { [activeSectionKey]: activeSection } = sections + // required in Settings for error handling and confirmation dialog + // the alert/dialog need to show the translated field labels (together with section labels) + // output: [ {sectionTitle: "globally unique + translated", fieldTitle: "translated", fieldName: "globally unique"}, ... ]} + // NOTE that the order of section/fields is preserved here + const translatedLabels = Object.values(sections) + .flatMap(section => section.sections ? Object.values(section.sections) : section) + .flatMap(({ title: sectionTitle, fields }) => + // assume global uniqueness of: fieldName, sectionTitle + fields.map(({ name: fieldName, title: fieldTitle }) => ({ sectionTitle, fieldTitle, fieldName })) + ) const onSelect = result => { this.setState({