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 (
+
+ {
+ Object.entries(filteredSectionTree)
+ .map(([sectionTitle, fields]) => (
+ -
+ {sectionTitle}
+
+ { fields.map(fieldTitle => - {fieldTitle}
)}
+
+
+ ))
+ }
+
+ )
+}
+
+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 = () => (
- {
- changes.map(name => {
- const value = translatedLabels[name] || name
- return (- {value}
)
- })
- }
-
- )
-
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({