import React from 'react'; import { connect } from 'react-redux'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Column from '../ui/components/column'; import { SimpleForm, FieldsGroup, TextInput, Checkbox, FileChooser, SimpleTextarea, FileChooserLogo, FormPropTypes, } from 'soapbox/features/forms'; import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; import { updateAdminConfig } from 'soapbox/actions/admin'; import Icon from 'soapbox/components/icon'; import { defaultConfig } from 'soapbox/actions/soapbox'; import { uploadMedia } from 'soapbox/actions/media'; import { SketchPicker } from 'react-color'; import Overlay from 'react-overlays/lib/Overlay'; import { isMobile } from 'soapbox/is_mobile'; import detectPassiveEvents from 'detect-passive-events'; import Accordion from '../ui/components/accordion'; const messages = defineMessages({ heading: { id: 'column.soapbox_config', defaultMessage: 'Soapbox config' }, copyrightFooterLabel: { id: 'soapbox_config.copyright_footer.meta_fields.label_placeholder', defaultMessage: 'Copyright footer' }, promoItemIcon: { id: 'soapbox_config.promo_panel.meta_fields.icon_placeholder', defaultMessage: 'Icon' }, promoItemLabel: { id: 'soapbox_config.promo_panel.meta_fields.label_placeholder', defaultMessage: 'Label' }, promoItemURL: { id: 'soapbox_config.promo_panel.meta_fields.url_placeholder', defaultMessage: 'URL' }, homeFooterItemLabel: { id: 'soapbox_config.home_footer.meta_fields.label_placeholder', defaultMessage: 'Label' }, homeFooterItemURL: { id: 'soapbox_config.home_footer.meta_fields.url_placeholder', defaultMessage: 'URL' }, customCssLabel: { id: 'soapbox_config.custom_css.meta_fields.url_placeholder', defaultMessage: 'URL' }, rawJSONLabel: { id: 'soapbox_config.raw_json_label', defaultMessage: 'Advanced: Edit raw JSON data' }, rawJSONHint: { id: 'soapbox_config.raw_json_hint', defaultMessage: 'Edit the settings data directly. Changes made directly to the JSON file will override the form fields above. Click "Save" to apply your changes.' }, }); const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false; const templates = { promoPanelItem: ImmutableMap({ icon: '', text: '', url: '' }), footerItem: ImmutableMap({ title: '', url: '' }), }; const mapStateToProps = state => ({ soapbox: state.get('soapbox'), }); export default @connect(mapStateToProps) @injectIntl class SoapboxConfig extends ImmutablePureComponent { static propTypes = { soapbox: ImmutablePropTypes.map.isRequired, dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; state = { isLoading: false, soapbox: this.props.soapbox, rawJSON: JSON.stringify(this.props.soapbox, null, 2), jsonValid: true, } setConfig = (path, value) => { const { soapbox } = this.state; const config = soapbox.setIn(path, value); this.setState({ soapbox: config, jsonValid: true }); }; putConfig = config => { this.setState({ soapbox: config, jsonValid: true }); }; getParams = () => { const { soapbox } = this.state; return { configs: [{ group: ':pleroma', key: ':frontend_configurations', value: [{ tuple: [':soapbox_fe', soapbox.toJS()], }], }], }; } handleSubmit = (event) => { const { dispatch } = this.props; dispatch(updateAdminConfig(this.getParams())).then(() => { this.setState({ isLoading: false }); }).catch((error) => { this.setState({ isLoading: false }); }); this.setState({ isLoading: true }); event.preventDefault(); } handleChange = (path, getValue) => { return e => { this.setConfig(path, getValue(e)); }; }; handleFileChange = path => { return e => { const data = new FormData(); data.append('file', e.target.files[0]); this.props.dispatch(uploadMedia(data)).then(({ data }) => { this.handleChange(path, e => data.url)(e); }).catch(() => {}); }; }; handleAddItem = (path, template) => { return e => { this.setConfig( path, this.getSoapboxConfig().getIn(path, ImmutableList()).push(template), ); }; }; handleDeleteItem = path => { return e => { const soapbox = this.state.soapbox.deleteIn(path); this.setState({ soapbox }); }; }; handleItemChange = (path, key, field, template) => { return this.handleChange( path, (e) => template .merge(field) .set(key, e.target.value) ); }; handlePromoItemChange = (index, key, field) => { return this.handleItemChange( ['promoPanel', 'items', index], key, field, templates.promoPanelItem ); }; handleHomeFooterItemChange = (index, key, field) => { return this.handleItemChange( ['navlinks', 'homeFooter', index], key, field, templates.footerItem ); }; handleEditJSON = e => { this.setState({ rawJSON: e.target.value }); } getSoapboxConfig = () => { return defaultConfig.mergeDeep(this.state.soapbox); } componentDidUpdate(prevProps, prevState) { if (prevProps.soapbox !== this.props.soapbox) { this.putConfig(this.props.soapbox); } if (prevState.soapbox !== this.state.soapbox) { this.setState({ rawJSON: JSON.stringify(this.state.soapbox, null, 2) }); } if (prevState.rawJSON !== this.state.rawJSON) { try { const data = fromJS(JSON.parse(this.state.rawJSON)); this.putConfig(data); } catch { this.setState({ jsonValid: false }); } } } render() { const { intl } = this.props; const soapbox = this.getSoapboxConfig(); return (
} name='logo' hint={
{/*
} name='banner' hint={} onChange={this.handleFileChange(['banner'])} />
*/}
} value={soapbox.get('brandColor')} onChange={this.handleChange(['brandColor'], (e) => e.hex)} />
{/* } hint={} name='patron' checked={soapbox.getIn(['extensions', 'patron', 'enabled'])} onChange={this.handleChange( ['extensions', 'patron', 'enabled'], (e) => e.checked, )} /> */} e.target.value)} />
Soapbox Icons List }} /> { soapbox.getIn(['promoPanel', 'items']).map((field, i) => (
)) }
{ soapbox.getIn(['navlinks', 'homeFooter']).map((field, i) => (
)) }
{/*
{ soapbox.get('customCss').map((field, i) => (
e.target.value)} />
)) }
*/}
} />
); } } class ColorPicker extends React.PureComponent { static propTypes = { style: PropTypes.object, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, onClose: PropTypes.func, } handleDocumentClick = e => { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); } } componentDidMount() { document.addEventListener('click', this.handleDocumentClick, false); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); } componentWillUnmount() { document.removeEventListener('click', this.handleDocumentClick, false); document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); } setRef = c => { this.node = c; } render() { const { style, value, onChange } = this.props; let margin_left_picker = isMobile(window.innerWidth) ? '20px' : '12px'; return (
); } } class ColorWithPicker extends ImmutablePureComponent { static propTypes = { buttonId: PropTypes.string.isRequired, label: FormPropTypes.label, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, } onToggle = (e) => { if (!e.key || e.key === 'Enter') { if (this.state.active) { this.onHidePicker(); } else { this.onShowPicker(e); } } } state = { active: false, placement: null, } onHidePicker = () => { this.setState({ active: false }); } onShowPicker = ({ target }) => { this.setState({ active: true }); this.setState({ placement: isMobile(window.innerWidth) ? 'bottom' : 'right' }); } render() { const { buttonId, label, value, onChange } = this.props; const { active, placement } = this.state; return (
); } }