diff --git a/app/soapbox/actions/settings.js b/app/soapbox/actions/settings.js index 18b0636f5..e10f2f430 100644 --- a/app/soapbox/actions/settings.js +++ b/app/soapbox/actions/settings.js @@ -8,6 +8,7 @@ import { createSelector } from 'reselect'; export const SETTING_CHANGE = 'SETTING_CHANGE'; export const SETTING_SAVE = 'SETTING_SAVE'; +export const SETTINGS_UPDATE = 'SETTINGS_UPDATE'; export const FE_NAME = 'soapbox_fe'; diff --git a/app/soapbox/features/developers/index.js b/app/soapbox/features/developers/index.js index 19b7c7b61..3c5734857 100644 --- a/app/soapbox/features/developers/index.js +++ b/app/soapbox/features/developers/index.js @@ -32,6 +32,16 @@ class Developers extends React.Component { +
+ +
+ +
+
+ +
+ +
diff --git a/app/soapbox/features/developers/settings_store.js b/app/soapbox/features/developers/settings_store.js new file mode 100644 index 000000000..1ac9c7d42 --- /dev/null +++ b/app/soapbox/features/developers/settings_store.js @@ -0,0 +1,111 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { connect } from 'react-redux'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { injectIntl, FormattedMessage, defineMessages } from 'react-intl'; +import Column from 'soapbox/features/ui/components/column'; +import { SimpleForm, SimpleTextarea } from 'soapbox/features/forms'; +import { showAlertForError } from 'soapbox/actions/alerts'; +import { patchMe } from 'soapbox/actions/me'; +import { FE_NAME, SETTINGS_UPDATE } from 'soapbox/actions/settings'; + +const isJSONValid = text => { + try { + JSON.parse(text); + return true; + } catch { + return false; + } +}; + +const messages = defineMessages({ + heading: { id: 'column.settings_store', defaultMessage: 'Settings store' }, + hint: { id: 'developers.settings_store.hint', defaultMessage: 'It is possible to directly edit your user settings here. BE CAREFUL! Editing this section can break your account, and you will only be able to recover through the API.' }, +}); + +const mapStateToProps = state => { + return { + settingsStore: state.get('settings'), + }; +}; + +export default @connect(mapStateToProps) +@injectIntl +class SettingsStore extends ImmutablePureComponent { + + static propTypes = { + intl: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + settingsStore: ImmutablePropTypes.map.isRequired, + } + + state = { + rawJSON: JSON.stringify(this.props.settingsStore, null, 2), + jsonValid: true, + isLoading: false, + } + + componentDidUpdate(prevProps) { + const { settingsStore } = this.props; + + if (settingsStore !== prevProps.settingsStore) { + this.setState({ + rawJSON: JSON.stringify(settingsStore, null, 2), + jsonValid: true, + }); + } + } + + handleEditJSON = ({ target }) => { + const rawJSON = target.value; + this.setState({ rawJSON, jsonValid: isJSONValid(rawJSON) }); + } + + handleSubmit = e => { + const { dispatch } = this.props; + const { rawJSON } = this.state; + + const settings = JSON.parse(rawJSON); + + this.setState({ isLoading: true }); + dispatch(patchMe({ + pleroma_settings_store: { + [FE_NAME]: settings, + }, + })).then(response => { + dispatch({ type: SETTINGS_UPDATE, settings }); + this.setState({ isLoading: false }); + }).catch(error => { + dispatch(showAlertForError(error)); + this.setState({ isLoading: false }); + }); + } + + render() { + const { intl } = this.props; + const { rawJSON, jsonValid, isLoading } = this.state; + + return ( + + +
+ +
+
+ +
+
+
+ ); + } + +} diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index 9f13aedf4..2f69499a9 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -116,6 +116,7 @@ import { IntentionalError, Developers, CreateApp, + SettingsStore, } from './util/async-components'; // Dummy import, to make sure that ends up in the application bundle. @@ -321,8 +322,9 @@ class SwitchingColumnsArea extends React.PureComponent { - - + + + diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index c80b53ee9..d651dc656 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -445,3 +445,7 @@ export function Developers() { export function CreateApp() { return import(/* webpackChunkName: "features/developers" */'../../developers/apps/create'); } + +export function SettingsStore() { + return import(/* webpackChunkName: "features/developers" */'../../developers/settings_store'); +} diff --git a/app/soapbox/features/ui/util/react_router_helpers.js b/app/soapbox/features/ui/util/react_router_helpers.js index af0b40800..337f62e4e 100644 --- a/app/soapbox/features/ui/util/react_router_helpers.js +++ b/app/soapbox/features/ui/util/react_router_helpers.js @@ -8,6 +8,7 @@ import ColumnLoading from '../components/column_loading'; import ColumnForbidden from '../components/column_forbidden'; import BundleColumnError from '../components/bundle_column_error'; import BundleContainer from '../containers/bundle_container'; +import { getSettings } from 'soapbox/actions/settings'; import { isStaff, isAdmin } from 'soapbox/utils/accounts'; const mapStateToProps = state => { @@ -15,6 +16,7 @@ const mapStateToProps = state => { return { account: state.getIn(['accounts', me]), + settings: getSettings(state), }; }; @@ -27,9 +29,11 @@ class WrappedRoute extends React.Component { componentParams: PropTypes.object, layout: PropTypes.object, account: ImmutablePropTypes.map, + settings: ImmutablePropTypes.map.isRequired, publicRoute: PropTypes.bool, staffOnly: PropTypes.bool, adminOnly: PropTypes.bool, + developerOnly: PropTypes.bool, }; static defaultProps = { @@ -100,10 +104,11 @@ class WrappedRoute extends React.Component { } render() { - const { component: Component, content, account, publicRoute, staffOnly, adminOnly, ...rest } = this.props; + const { component: Component, content, account, settings, publicRoute, developerOnly, staffOnly, adminOnly, ...rest } = this.props; const authorized = [ account || publicRoute, + developerOnly ? settings.get('isDeveloper') : true, staffOnly ? account && isStaff(account) : true, adminOnly ? account && isAdmin(account) : true, ].every(c => c); diff --git a/app/soapbox/reducers/settings.js b/app/soapbox/reducers/settings.js index 9bfa3f356..b7d4d44b7 100644 --- a/app/soapbox/reducers/settings.js +++ b/app/soapbox/reducers/settings.js @@ -1,4 +1,9 @@ -import { SETTING_CHANGE, SETTING_SAVE, FE_NAME } from '../actions/settings'; +import { + SETTING_CHANGE, + SETTING_SAVE, + SETTINGS_UPDATE, + FE_NAME, +} from '../actions/settings'; import { NOTIFICATIONS_FILTER_SET } from '../actions/notifications'; import { SEARCH_FILTER_SET } from '../actions/search'; import { EMOJI_USE } from '../actions/emojis'; @@ -35,6 +40,8 @@ export default function settings(state = initialState, action) { return updateFrequentEmojis(state, action.emoji); case SETTING_SAVE: return state.set('saved', true); + case SETTINGS_UPDATE: + return fromJS(action.settings); default: return state; }