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;
}