Skip to content

Commit

Permalink
Merge pull request #1456 from rszwajko/groupBySection
Browse files Browse the repository at this point in the history
Preserve the order and group options by section
  • Loading branch information
sgratch authored Jun 21, 2021
2 parents d2226b8 + 9f8c823 commit 7655599
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 80 deletions.
43 changes: 43 additions & 0 deletions src/components/Settings/ChangesList.js
Original file line number Diff line number Diff line change
@@ -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 (
<ul className={style['section-list']}>
{
Object.entries(filteredSectionTree)
.map(([sectionTitle, fields]) => (
<li key={sectionTitle}>
{sectionTitle}
<ul className={style['field-list']}>
{ fields.map(fieldTitle => <li key={fieldTitle}>{fieldTitle}</li>)}
</ul>
</li>
))
}
</ul>
)
}

ChangesList.propTypes = {
changes: PropTypes.array,
translatedLabels: PropTypes.array.isRequired,
}

export default ChangesList
7 changes: 4 additions & 3 deletions src/components/Settings/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down Expand Up @@ -94,7 +95,7 @@ const Settings = ({ draftValues, onSave, lastTransactionId, onCancel,
useEffect(() => {
const partialSaveState = {
show: partialSuccess,
fields: pendingChanges.map(e => <p key={translatedLabels[e]}>{translatedLabels[e]}</p>),
fields: pendingChanges,
}
if (partialSaveState.show) { setShowPartialSave(partialSaveState) }
if (completeFailure) { setShowCompleteFailure(completeFailure) }
Expand Down Expand Up @@ -128,7 +129,7 @@ const Settings = ({ draftValues, onSave, lastTransactionId, onCancel,
type='error'
title={<p>{msg.failedToSaveChangesToFields()}</p>}
onDismiss={() => resetNotifications(setShowPartialSave, { show: false, fields: [] })} >
{partialSave.fields}
<ChangesList changes={partialSave.fields} translatedLabels={translatedLabels} />
</CounterAlert>
}
{ showCompleteFailure &&
Expand Down Expand Up @@ -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,
Expand Down
16 changes: 3 additions & 13 deletions src/components/Settings/SettingsToolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -39,23 +39,13 @@ const SettingsToolbar = ({ onSave, onReset, onCancel, enableSave, enableReset, t
setShowResetConfirmModal(false)
}

const buildConfirmationModalSubContent = () => (
<ul className={style['changes-list']}>{
changes.map(name => {
const value = translatedLabels[name] || name
return (<li key={`${idPrefix}_li_${name}`}>{value}</li>)
})
}
</ul>
)

return ReactDOM.createPortal(
<Toolbar className={style['toolbar']}>
<ConfirmationModal
show={showSaveConfirmModal}
title={msg.saveChanges()}
body={msg.saveSettingsChangesConfirmation()}
subContent={buildConfirmationModalSubContent()}
subContent={<ChangesList changes={changes} translatedLabels={translatedLabels} />}
onClose={onSaveClose}
confirm={{
title: msg.yes(),
Expand Down Expand Up @@ -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,
}
Expand Down
9 changes: 7 additions & 2 deletions src/components/Settings/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
124 changes: 62 additions & 62 deletions src/components/UserSettings/GlobalSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,147 +148,147 @@ 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'
return {
[GENERAL_SECTION]: {
title: msg.general(),
fields: [
{
((name) => ({
title: msg.username(),
name: 'username',
body: <span>{config.userName}</span>,
},
{
name,
body: <span>{config[name]}</span>,
}))('userName'),
((name) => ({
title: msg.email(),
name: 'email',
body: <span>{config.email}</span>,
},
{
title: translatedLabels.language,
name: 'language',
name,
body: <span>{config[name]}</span>,
}))('email'),
((name) => ({
title: msg.language(),
name,
tooltip: draftValues.persistLocale ? undefined : msg.optionIsNotSavedOnTheServer({ persistenceReEnableHowTo: msg.persistenceReEnableHowTo({ advancedOptions: msg.advancedOptions() }) }),
body: (
<div className={style['half-width']}>
<SelectBox
id={`${idPrefix}-language`}
id={`${idPrefix}-${name}`}
items={Object.entries(localeWithFullName).map(([id, value]) => ({ id, value, isDefault: id === DEFAULT_LOCALE }))}
selected={draftValues.language}
onChange={onChange('language')}
selected={draftValues[name]}
onChange={onChange(name)}
/>
</div>
),
},
{
title: translatedLabels.sshKey,
}))('language'),
((name) => ({
title: msg.sshKey(),
tooltip: msg.sshKeyTooltip(),
name: 'sshKey',
name,
body: (
<div className={style['half-width']}>
<FormControl
id={`${idPrefix}-ssh-key`}
id={`${idPrefix}-${name}`}
componentClass='textarea'
onChange={e => onChange('sshKey')(e.target.value)}
value={draftValues.sshKey || ''}
onChange={e => onChange(name)(e.target.value)}
value={draftValues[name] || ''}
rows={8}
/>
</div>
),
},
}))('sshKey'),
],
},
refreshInterval: {
title: msg.refreshInterval(),
tooltip: msg.refreshIntervalTooltip(),
fields: [
{
title: translatedLabels.refreshInterval,
name: 'refreshInterval',
((name) => ({
title: msg.uiRefresh(),
name,
body: (
<div className={style['half-width']}>
<SelectBox
id={`${idPrefix}-update-rate`}
id={`${idPrefix}-${name}`}
items={this.refreshIntervalList(msg)
.map(({ id, value }) => ({ id, value, isDefault: id === AppConfiguration.schedulerFixedDelayInSeconds }))}
selected={draftValues.refreshInterval}
onChange={onChange('refreshInterval')}
selected={draftValues[name]}
onChange={onChange(name)}
/>
</div>
),
},
}))('refreshInterval'),
],
},
notifications: {
title: msg.notifications(),
tooltip: msg.notificationSettingsAffectAllNotifications(),
fields: [
{
title: translatedLabels.showNotifications,
name: 'showNotificatons',
((name) => ({
title: msg.dontDisturb(),
name,
body: (
<Switch
id={`${idPrefix}-dont-disturb`}
isChecked={!draftValues.showNotifications}
id={`${idPrefix}-${name}`}
isChecked={!draftValues[name]}
onChange={(dontDisturb) => {
onChange('showNotifications')(!dontDisturb)
onChange(name)(!dontDisturb)
}}
/>
),
},
{
title: translatedLabels.notificationSnoozeDuration,
name: 'notificationSnoozeDuration',
}))('showNotifications'),
((name) => ({
title: msg.dontDisturbFor(),
name,
body: (
<div className={style['half-width']}>
<SelectBox
id={`${idPrefix}-dont-disturb-for`}
id={`${idPrefix}-${name}`}
items={this.dontDisturbList(msg)
.map(({ id, value }) => ({ id, value, isDefault: id === AppConfiguration.notificationSnoozeDurationInMinutes }))}
selected={draftValues.notificationSnoozeDuration}
onChange={onChange('notificationSnoozeDuration')}
selected={draftValues[name]}
onChange={onChange(name)}
disabled={draftValues.showNotifications}
/>
</div>
),
},
}))('notificationSnoozeDuration'),
],
},
advancedOptions: {
title: msg.advancedOptions(),
fields: [
{
((name) => ({
title: msg.persistLanguage(),
name: 'persistLocale',
name,
tooltip: msg.persistLanguageTooltip(),
body: (<Switch
id={`${idPrefix}-persist-locale`}
isChecked={draftValues.persistLocale}
onChange={(persist) => 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({
Expand Down

0 comments on commit 7655599

Please sign in to comment.