Normalize SoapboxConfig

This commit is contained in:
Alex Gleason 2022-03-28 14:58:50 -05:00
parent 2c0ce084c3
commit 483b28988f
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
8 changed files with 146 additions and 100 deletions

View file

@ -2,6 +2,7 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { getHost } from 'soapbox/actions/instance'; import { getHost } from 'soapbox/actions/instance';
import { normalizeSoapboxConfig } from 'soapbox/normalizers';
import KVStore from 'soapbox/storage/kv_store'; import KVStore from 'soapbox/storage/kv_store';
import { getFeatures } from 'soapbox/utils/features'; import { getFeatures } from 'soapbox/utils/features';
@ -33,79 +34,10 @@ const allowedEmojiRGI = ImmutableList([
'😩', '😩',
]); ]);
const year = new Date().getFullYear();
export const makeDefaultConfig = features => { export const makeDefaultConfig = features => {
return ImmutableMap({ return ImmutableMap({
logo: '',
banner: '',
brandColor: '', // Empty
accentColor: '',
colors: ImmutableMap({
gray: ImmutableMap({
50: '#f9fafb',
100: '#f3f4f6',
200: '#e5e7eb',
300: '#d1d5db',
400: '#9ca3af',
500: '#6b7280',
600: '#4b5563',
700: '#374151',
800: '#1f2937',
900: '#111827',
}),
success: ImmutableMap({
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
300: '#86efac',
400: '#4ade80',
500: '#22c55e',
600: '#16a34a',
700: '#15803d',
800: '#166534',
900: '#14532d',
}),
danger: ImmutableMap({
50: '#fef2f2',
100: '#fee2e2',
200: '#fecaca',
300: '#fca5a5',
400: '#f87171',
500: '#ef4444',
600: '#dc2626',
700: '#b91c1c',
800: '#991b1b',
900: '#7f1d1d',
}),
'gradient-purple': '#b8a3f9',
'gradient-blue': '#9bd5ff',
'sea-blue': '#2feecc',
}),
customCss: ImmutableList(),
promoPanel: ImmutableMap({
items: ImmutableList(),
}),
extensions: ImmutableMap(),
defaultSettings: ImmutableMap(),
copyright: `${year}. Copying is an act of love. Please copy and share.`,
navlinks: ImmutableMap({
homeFooter: ImmutableList(),
}),
allowedEmoji: features.emojiReactsRGI ? allowedEmojiRGI : allowedEmoji, allowedEmoji: features.emojiReactsRGI ? allowedEmojiRGI : allowedEmoji,
verifiedIcon: '',
verifiedCanEditName: false,
displayFqn: Boolean(features.federating), displayFqn: Boolean(features.federating),
cryptoAddresses: ImmutableList(),
cryptoDonatePanel: ImmutableMap({
limit: 1,
}),
aboutPages: ImmutableMap(),
betaPages: ImmutableMap(),
mobilePages: ImmutableMap(),
authenticatedProfile: true,
singleUserMode: false,
singleUserModeProfile: '',
}); });
}; };
@ -114,7 +46,7 @@ export const getSoapboxConfig = createSelector([
state => getFeatures(state.get('instance')), state => getFeatures(state.get('instance')),
], (soapbox, features) => { ], (soapbox, features) => {
const defaultConfig = makeDefaultConfig(features); const defaultConfig = makeDefaultConfig(features);
return soapbox.mergeDeepWith((o, n) => o || n, defaultConfig); return normalizeSoapboxConfig(soapbox).merge(defaultConfig);
}); });
export function rememberSoapboxConfig(host) { export function rememberSoapboxConfig(host) {

View file

@ -24,7 +24,7 @@ interface ISiteWallet {
const SiteWallet: React.FC<ISiteWallet> = ({ limit }): JSX.Element => { const SiteWallet: React.FC<ISiteWallet> = ({ limit }): JSX.Element => {
const addresses: ImmutableList<Address> = const addresses: ImmutableList<Address> =
useSoapboxConfig().get('cryptoAddresses').map(normalizeAddress); useSoapboxConfig().cryptoAddresses.map(normalizeAddress);
const coinList = typeof limit === 'number' ? addresses.take(limit) : addresses; const coinList = typeof limit === 'number' ? addresses.take(limit) : addresses;

View file

@ -12,8 +12,8 @@ import { connect } from 'react-redux';
import { updateConfig } from 'soapbox/actions/admin'; import { updateConfig } from 'soapbox/actions/admin';
import { uploadMedia } from 'soapbox/actions/media'; import { uploadMedia } from 'soapbox/actions/media';
import snackbar from 'soapbox/actions/snackbar'; import snackbar from 'soapbox/actions/snackbar';
import { makeDefaultConfig } from 'soapbox/actions/soapbox';
import Icon from 'soapbox/components/icon'; import Icon from 'soapbox/components/icon';
import { Column } from 'soapbox/components/ui';
import { import {
SimpleForm, SimpleForm,
FieldsGroup, FieldsGroup,
@ -26,10 +26,9 @@ import {
} from 'soapbox/features/forms'; } from 'soapbox/features/forms';
import ThemeToggle from 'soapbox/features/ui/components/theme_toggle'; import ThemeToggle from 'soapbox/features/ui/components/theme_toggle';
import { isMobile } from 'soapbox/is_mobile'; import { isMobile } from 'soapbox/is_mobile';
import { getFeatures } from 'soapbox/utils/features'; import { normalizeSoapboxConfig } from 'soapbox/normalizers';
import Accordion from '../ui/components/accordion'; import Accordion from '../ui/components/accordion';
import Column from '../ui/components/column';
import IconPickerDropdown from './components/icon_picker_dropdown'; import IconPickerDropdown from './components/icon_picker_dropdown';
import SitePreview from './components/site_preview'; import SitePreview from './components/site_preview';
@ -71,11 +70,8 @@ const templates = {
}; };
const mapStateToProps = state => { const mapStateToProps = state => {
const instance = state.get('instance');
return { return {
soapbox: state.get('soapbox'), initialData: state.soapbox,
features: getFeatures(instance),
}; };
}; };
@ -84,37 +80,36 @@ export default @connect(mapStateToProps)
class SoapboxConfig extends ImmutablePureComponent { class SoapboxConfig extends ImmutablePureComponent {
static propTypes = { static propTypes = {
soapbox: ImmutablePropTypes.map.isRequired, initialData: ImmutablePropTypes.map.isRequired,
features: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
state = { state = {
isLoading: false, isLoading: false,
soapbox: this.props.soapbox, data: this.props.initialData,
jsonEditorExpanded: false, jsonEditorExpanded: false,
rawJSON: JSON.stringify(this.props.soapbox, null, 2), rawJSON: JSON.stringify(this.props.soapbox, null, 2),
jsonValid: true, jsonValid: true,
} }
setConfig = (path, value) => { setConfig = (path, value) => {
const { soapbox } = this.state; const { data } = this.state;
const config = soapbox.setIn(path, value); const newData = data.setIn(path, value);
this.setState({ soapbox: config, jsonValid: true }); this.setState({ data: newData, jsonValid: true });
}; };
putConfig = config => { putConfig = data => {
this.setState({ soapbox: config, jsonValid: true }); this.setState({ data, jsonValid: true });
}; };
getParams = () => { getParams = () => {
const { soapbox } = this.state; const { data } = this.state;
return [{ return [{
group: ':pleroma', group: ':pleroma',
key: ':frontend_configurations', key: ':frontend_configurations',
value: [{ value: [{
tuple: [':soapbox_fe', soapbox.toJS()], tuple: [':soapbox_fe', data.toJS()],
}], }],
}]; }];
} }
@ -158,8 +153,8 @@ class SoapboxConfig extends ImmutablePureComponent {
handleDeleteItem = path => { handleDeleteItem = path => {
return e => { return e => {
const soapbox = this.state.soapbox.deleteIn(path); const data = this.state.data.deleteIn(path);
this.setState({ soapbox }); this.setState({ data });
}; };
}; };
@ -195,20 +190,18 @@ class SoapboxConfig extends ImmutablePureComponent {
} }
getSoapboxConfig = () => { getSoapboxConfig = () => {
const { features } = this.props; return normalizeSoapboxConfig(this.state.data);
const { soapbox } = this.state;
return makeDefaultConfig(features).mergeDeep(soapbox);
} }
toggleJSONEditor = (value) => this.setState({ jsonEditorExpanded: value }); toggleJSONEditor = (value) => this.setState({ jsonEditorExpanded: value });
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
if (prevProps.soapbox !== this.props.soapbox) { if (prevProps.initialData !== this.props.initialData) {
this.putConfig(this.props.soapbox); this.putConfig(this.props.initialData);
} }
if (prevState.soapbox !== this.state.soapbox) { if (prevState.data !== this.state.data) {
this.setState({ rawJSON: JSON.stringify(this.state.soapbox, null, 2) }); this.setState({ rawJSON: JSON.stringify(this.state.data, null, 2) });
} }
if (prevState.rawJSON !== this.state.rawJSON) { if (prevState.rawJSON !== this.state.rawJSON) {
@ -226,7 +219,7 @@ class SoapboxConfig extends ImmutablePureComponent {
const soapbox = this.getSoapboxConfig(); const soapbox = this.getSoapboxConfig();
return ( return (
<Column icon='cog' heading={intl.formatMessage(messages.heading)}> <Column label={intl.formatMessage(messages.heading)}>
<SimpleForm onSubmit={this.handleSubmit}> <SimpleForm onSubmit={this.handleSubmit}>
<fieldset disabled={this.state.isLoading}> <fieldset disabled={this.state.isLoading}>
<SitePreview soapbox={soapbox} /> <SitePreview soapbox={soapbox} />

View file

@ -1,9 +1,9 @@
import { getSoapboxConfig } from 'soapbox/actions/soapbox'; import { getSoapboxConfig } from 'soapbox/actions/soapbox';
import { useAppSelector } from 'soapbox/hooks'; import { useAppSelector } from 'soapbox/hooks';
import type { Map as ImmutableMap } from 'immutable'; import type { SoapboxConfig } from 'soapbox/types/soapbox';
/** Get the Soapbox config from the store */ /** Get the Soapbox config from the store */
export const useSoapboxConfig = (): ImmutableMap<string, any> => { export const useSoapboxConfig = (): SoapboxConfig => {
return useAppSelector((state) => getSoapboxConfig(state)); return useAppSelector((state) => getSoapboxConfig(state));
}; };

View file

@ -7,3 +7,5 @@ export { MentionRecord, normalizeMention } from './mention';
export { NotificationRecord, normalizeNotification } from './notification'; export { NotificationRecord, normalizeNotification } from './notification';
export { PollRecord, PollOptionRecord, normalizePoll } from './poll'; export { PollRecord, PollOptionRecord, normalizePoll } from './poll';
export { StatusRecord, normalizeStatus } from './status'; export { StatusRecord, normalizeStatus } from './status';
export { SoapboxConfigRecord, normalizeSoapboxConfig } from './soapbox/soapbox_config';

View file

@ -0,0 +1,11 @@
import { Record as ImmutableRecord } from 'immutable';
import { normalizeSoapboxConfig } from '../soapbox_config';
describe('normalizeSoapboxConfig()', () => {
it('adds base fields', () => {
const result = normalizeSoapboxConfig({});
expect(result.brandColor).toBe('');
expect(ImmutableRecord.isRecord(result)).toBe(true);
});
});

View file

@ -0,0 +1,103 @@
import {
Map as ImmutableMap,
List as ImmutableList,
Record as ImmutableRecord,
fromJS,
} from 'immutable';
const DEFAULT_COLORS = ImmutableMap({
gray: ImmutableMap({
50: '#f9fafb',
100: '#f3f4f6',
200: '#e5e7eb',
300: '#d1d5db',
400: '#9ca3af',
500: '#6b7280',
600: '#4b5563',
700: '#374151',
800: '#1f2937',
900: '#111827',
}),
success: ImmutableMap({
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
300: '#86efac',
400: '#4ade80',
500: '#22c55e',
600: '#16a34a',
700: '#15803d',
800: '#166534',
900: '#14532d',
}),
danger: ImmutableMap({
50: '#fef2f2',
100: '#fee2e2',
200: '#fecaca',
300: '#fca5a5',
400: '#f87171',
500: '#ef4444',
600: '#dc2626',
700: '#b91c1c',
800: '#991b1b',
900: '#7f1d1d',
}),
'gradient-purple': '#b8a3f9',
'gradient-blue': '#9bd5ff',
'sea-blue': '#2feecc',
});
export const SoapboxConfigRecord = ImmutableRecord({
logo: '',
banner: '',
brandColor: '', // Empty
accentColor: '',
colors: ImmutableMap(),
copyright: `${new Date().getFullYear()}. Copying is an act of love. Please copy and share.`,
customCss: ImmutableList<string>(),
defaultSettings: ImmutableMap(),
extensions: ImmutableMap(),
greentext: false,
promoPanel: ImmutableMap({
items: ImmutableList(),
}),
navlinks: ImmutableMap({
homeFooter: ImmutableList(),
}),
allowedEmoji: ImmutableList<string>([
'👍',
'❤️',
'😆',
'😮',
'😢',
'😩',
]),
verifiedIcon: '',
verifiedCanEditName: false,
displayFqn: true,
cryptoAddresses: ImmutableList<ImmutableMap<string, any>>(),
cryptoDonatePanel: ImmutableMap({
limit: 1,
}),
aboutPages: ImmutableMap(),
betaPages: ImmutableMap(),
mobilePages: ImmutableMap(),
authenticatedProfile: true,
singleUserMode: false,
singleUserModeProfile: '',
}, 'SoapboxConfig');
type SoapboxConfigMap = ImmutableMap<string, any>;
const normalizeColors = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMap => {
const colors = DEFAULT_COLORS.mergeDeep(soapboxConfig.get('colors'));
return soapboxConfig.set('colors', colors);
};
export const normalizeSoapboxConfig = (soapboxConfig: Record<string, any>) => {
return SoapboxConfigRecord(
ImmutableMap(fromJS(soapboxConfig)).withMutations(soapboxConfig => {
normalizeColors(soapboxConfig);
}),
);
};

View file

@ -0,0 +1,5 @@
import { SoapboxConfigRecord } from 'soapbox/normalizers';
type SoapboxConfig = ReturnType<typeof SoapboxConfigRecord>;
export { SoapboxConfig };