diff --git a/client/coral-admin/src/routes/Configure/containers/Configure.js b/client/coral-admin/src/routes/Configure/containers/Configure.js
index 8f80a3e24..dfa8df876 100644
--- a/client/coral-admin/src/routes/Configure/containers/Configure.js
+++ b/client/coral-admin/src/routes/Configure/containers/Configure.js
@@ -2,38 +2,20 @@ import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose, gql} from 'react-apollo';
-import withQuery from 'coral-framework/hocs/withQuery';
+import {withQuery, withMergedSettings} from 'coral-framework/hocs';
import {Spinner} from 'coral-ui';
import {notify} from 'coral-framework/actions/notification';
import PropTypes from 'prop-types';
-import assignWith from 'lodash/assignWith';
import {withUpdateSettings} from 'coral-framework/graphql/mutations';
import {getErrorMessages, getDefinitionName} from 'coral-framework/utils';
import StreamSettings from './StreamSettings';
import TechSettings from './TechSettings';
import ModerationSettings from './ModerationSettings';
import {clearPending, setActiveSection} from '../../../actions/configure';
-
import Configure from '../components/Configure';
-// Like lodash merge but does not recurse into arrays.
-const mergeExcludingArrays = (objValue, srcValue) => {
- if (typeof srcValue === 'object' && !Array.isArray(srcValue)) {
- return assignWith({}, objValue, srcValue, mergeExcludingArrays);
- }
- return srcValue;
-};
-
class ConfigureContainer extends Component {
- // Merge current settings with pending settings.
- getMergedSettings = (props = this.props) => {
- return assignWith({}, props.root.settings, props.pending, mergeExcludingArrays);
- }
-
- // Cached merged settings.
- mergedSettings = this.getMergedSettings();
-
savePending = async () => {
try {
await this.props.updateSettings(this.props.pending);
@@ -44,14 +26,6 @@ class ConfigureContainer extends Component {
}
};
- componentWillReceiveProps(nextProps) {
-
- // Recalculate merged settings when necessary.
- if (this.props.root.settings !== nextProps.root.settings || this.props.pending !== nextProps.pending) {
- this.mergedSettings = this.getMergedSettings(nextProps);
- }
- }
-
render () {
if(this.props.data.loading) {
return ;
@@ -62,7 +36,7 @@ class ConfigureContainer extends Component {
auth={this.props.auth}
data={this.props.data}
root={this.props.root}
- settings={this.mergedSettings}
+ settings={this.props.mergedSettings}
canSave={this.props.canSave}
savePending={this.savePending}
setActiveSection={this.props.setActiveSection}
@@ -112,6 +86,7 @@ export default compose(
withUpdateSettings,
withConfigureQuery,
connect(mapStateToProps, mapDispatchToProps),
+ withMergedSettings('root.settings', 'pending', 'mergedSettings'),
)(ConfigureContainer);
ConfigureContainer.propTypes = {
@@ -124,5 +99,6 @@ ConfigureContainer.propTypes = {
root: PropTypes.object.isRequired,
canSave: PropTypes.bool.isRequired,
pending: PropTypes.object.isRequired,
+ mergedSettings: PropTypes.object.isRequired,
activeSection: PropTypes.string.isRequired,
};
diff --git a/client/coral-embed-stream/src/actions/configure.js b/client/coral-embed-stream/src/actions/configure.js
index e69de29bb..bfc738fe0 100644
--- a/client/coral-embed-stream/src/actions/configure.js
+++ b/client/coral-embed-stream/src/actions/configure.js
@@ -0,0 +1,9 @@
+import * as actions from '../constants/configure';
+
+export const updatePending = ({updater, errorUpdater}) => {
+ return {type: actions.UPDATE_PENDING, updater, errorUpdater};
+};
+
+export const clearPending = () => {
+ return {type: actions.CLEAR_PENDING};
+};
diff --git a/client/coral-embed-stream/src/constants/configure.js b/client/coral-embed-stream/src/constants/configure.js
new file mode 100644
index 000000000..b29cf6280
--- /dev/null
+++ b/client/coral-embed-stream/src/constants/configure.js
@@ -0,0 +1,4 @@
+const prefix = 'TALK_EMBED_STREAM_CONFIGURE';
+
+export const UPDATE_PENDING = `${prefix}_UPDATE_PENDING`;
+export const CLEAR_PENDING = `${prefix}_CLEAR_PENDING`;
diff --git a/client/coral-embed-stream/src/reducers/configure.js b/client/coral-embed-stream/src/reducers/configure.js
new file mode 100644
index 000000000..0a94d5a54
--- /dev/null
+++ b/client/coral-embed-stream/src/reducers/configure.js
@@ -0,0 +1,42 @@
+import * as actions from '../constants/configure';
+import isEmpty from 'lodash/isEmpty';
+import update from 'immutability-helper';
+
+const initialState = {
+ canSave: false,
+ pending: {},
+ errors: {},
+};
+
+export default function config(state = initialState, action) {
+ switch (action.type) {
+ case actions.UPDATE_PENDING: {
+ let next = state;
+ if (action.updater) {
+ next = update(next, {
+ pending: action.updater,
+ });
+ }
+ if (action.errorUpdater) {
+ next = update(next, {
+ errors: action.errorUpdater,
+ });
+ }
+ const noErrors = Object.keys(next.errors).reduce((res, error) => res && !next.errors[error], true);
+ const canSave = !isEmpty(next.pending) && noErrors;
+ next = update(next, {
+ canSave: {$set: canSave},
+ });
+
+ return next;
+ }
+ case actions.CLEAR_PENDING:
+ return {
+ ...state,
+ pending: {},
+ canSave: false,
+ };
+ default:
+ return state;
+ }
+}
diff --git a/client/coral-embed-stream/src/reducers/index.js b/client/coral-embed-stream/src/reducers/index.js
index 5c553b04f..d6d0c7693 100644
--- a/client/coral-embed-stream/src/reducers/index.js
+++ b/client/coral-embed-stream/src/reducers/index.js
@@ -2,6 +2,7 @@ import auth from './auth';
import asset from './asset';
import embed from './embed';
import config from './config';
+import configure from './configure';
import stream from './stream';
import {reducer as commentBox} from '../../../talk-plugin-commentbox';
@@ -11,5 +12,6 @@ export default {
commentBox,
embed,
config,
+ configure,
stream,
};
diff --git a/client/coral-embed-stream/src/tabs/configure/components/Checkbox.css b/client/coral-embed-stream/src/tabs/configure/components/Checkbox.css
new file mode 100644
index 000000000..a25ea150f
--- /dev/null
+++ b/client/coral-embed-stream/src/tabs/configure/components/Checkbox.css
@@ -0,0 +1,59 @@
+.root {
+ position: relative;
+ display: inline-block;
+}
+
+.input {
+ position: absolute;
+ left: 7px;
+ bottom: 7px;
+ margin: 0;
+ padding: 0;
+ outline: none;
+ cursor: pointer;
+ pointer-events: none;
+ opacity: 0;
+}
+
+.checkbox {
+ cursor: pointer;
+}
+
+.checkbox:before {
+ content: "\e835";
+ color: #717171;
+ left: 4px;
+ top: 0px;
+ width: 18px;
+ height: 18px;
+ font-family: 'Material Icons';
+ font-weight: normal;
+ font-style: normal;
+ font-size: 24px;
+ line-height: 1;
+ text-transform: none;
+ letter-spacing: normal;
+ word-wrap: normal;
+ white-space: nowrap;
+ direction: ltr;
+ vertical-align: -6px;
+ text-rendering: optimizeLegibility;
+ font-feature-settings: 'liga';
+ transition: all .2s ease;
+ z-index: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.checkboxChecked:before {
+ content: "\e834";
+ color: #00a291;
+}
+
+.input:focus + .checkbox:before {
+ color: #00a291;
+}
+
+.input:focus + .checkboxChecked:before {
+ color: #00e291;
+}
diff --git a/client/coral-embed-stream/src/tabs/configure/components/Checkbox.js b/client/coral-embed-stream/src/tabs/configure/components/Checkbox.js
new file mode 100644
index 000000000..98804fe60
--- /dev/null
+++ b/client/coral-embed-stream/src/tabs/configure/components/Checkbox.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import styles from './Checkbox.css';
+import cn from 'classnames';
+import PropTypes from 'prop-types';
+
+const Checkbox = ({onChange, checked, className, ...rest}) => (
+
+);
+
+Checkbox.propTypes = {
+ className: PropTypes.string,
+ onChange: PropTypes.func,
+ checked: PropTypes.bool,
+};
+
+export default Checkbox;
diff --git a/client/coral-embed-stream/src/tabs/configure/components/Configuration.css b/client/coral-embed-stream/src/tabs/configure/components/Configuration.css
new file mode 100644
index 000000000..b18328a94
--- /dev/null
+++ b/client/coral-embed-stream/src/tabs/configure/components/Configuration.css
@@ -0,0 +1,27 @@
+.root {
+ position: relative;
+ margin: 12px 12px 12px 0;
+}
+
+.action {
+ display: inline-block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ padding-left: 4px;
+}
+
+.title {
+ font-size: 14px;
+ margin-bottom: 5px;
+ font-weight: bold;
+ cursor: pointer;
+}
+
+.content {
+ display: inline-block;
+ padding: 0px 50px;
+ box-sizing: border-box;
+}
+
+
diff --git a/client/coral-embed-stream/src/tabs/configure/components/Configuration.js b/client/coral-embed-stream/src/tabs/configure/components/Configuration.js
new file mode 100644
index 000000000..025517be6
--- /dev/null
+++ b/client/coral-embed-stream/src/tabs/configure/components/Configuration.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import Checkbox from './Checkbox';
+import PropTypes from 'prop-types';
+import cn from 'classnames';
+import styles from './Configuration.css';
+import uuid from 'uuid/v4';
+
+class Configuration extends React.Component {
+
+ id = uuid();
+
+ render() {
+ const {title, children, className, onCheckbox, checked, ...rest} = this.props;
+ return (
+
+ {checked !== undefined &&
+
+
+
+ }
+
+
+
+ {children}
+
+
+
+ );
+ }
+}
+
+Configuration.propTypes = {
+ title: PropTypes.string.isRequired,
+ className: PropTypes.string,
+ onCheckbox: PropTypes.func,
+ checked: PropTypes.bool,
+ children: PropTypes.node,
+};
+
+export default Configuration;
diff --git a/client/coral-embed-stream/src/tabs/configure/components/Settings.css b/client/coral-embed-stream/src/tabs/configure/components/Settings.css
new file mode 100644
index 000000000..914b7332b
--- /dev/null
+++ b/client/coral-embed-stream/src/tabs/configure/components/Settings.css
@@ -0,0 +1,26 @@
+.container {
+ position: relative;
+}
+
+.apply {
+ float: right;
+ margin: 0 10px;
+}
+
+.description {
+ max-width: 380px;
+}
+
+.checkbox {
+ vertical-align: top;
+ margin: 12px 12px 12px 0;
+}
+
+.list {
+ margin-top: 26px;
+}
+
+.wrapper {
+ margin-bottom: 20px;
+}
+
diff --git a/client/coral-embed-stream/src/tabs/configure/components/Settings.js b/client/coral-embed-stream/src/tabs/configure/components/Settings.js
new file mode 100644
index 000000000..2235369fe
--- /dev/null
+++ b/client/coral-embed-stream/src/tabs/configure/components/Settings.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import {Button} from 'coral-ui';
+import PropTypes from 'prop-types';
+import t from 'coral-framework/services/i18n';
+import cn from 'classnames';
+import styles from './Settings.css';
+import Configuration from './Configuration';
+
+class Settings extends React.Component {
+ render() {
+ const {settings: {moderation}, toggleModeration} = this.props;
+ const changed = false;
+ return (
+
+
+
{t('configure.title')}
+
+
{t('configure.description')}
+
+
+
+ {t('configure.enable_premod_description')}
+
+
+
+ );
+ }
+}
+
+Settings.propTypes = {
+ settings: PropTypes.object.isRequired,
+ toggleModeration: PropTypes.func.isRequired,
+};
+
+export default Settings;
diff --git a/client/coral-embed-stream/src/tabs/configure/containers/Settings.js b/client/coral-embed-stream/src/tabs/configure/containers/Settings.js
new file mode 100644
index 000000000..66625715d
--- /dev/null
+++ b/client/coral-embed-stream/src/tabs/configure/containers/Settings.js
@@ -0,0 +1,79 @@
+import React from 'react';
+import {gql, compose} from 'react-apollo';
+import {withFragments, withMergedSettings} from 'coral-framework/hocs';
+import {getErrorMessages} from 'coral-framework/utils';
+import Settings from '../components/Settings.js';
+import PropTypes from 'prop-types';
+import {withUpdateAssetSettings} from 'coral-framework/graphql/mutations';
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+import {notify} from 'coral-framework/actions/notification';
+import {clearPending, updatePending} from '../../../actions/configure';
+
+class SettingsContainer extends React.Component {
+
+ toggleModeration = () => {
+ const updater = {moderation: {$set: this.props.mergedSettings.moderation === 'PRE' ? 'POST' : 'PRE'}};
+ this.props.updatePending({updater});
+ };
+
+ savePending = async () => {
+ try {
+ await this.props.updateAssetSettings(this.props.asset.id, this.props.pending);
+ this.props.clearPending();
+ }
+ catch(err) {
+ this.props.notify('error', getErrorMessages(err));
+ }
+ };
+
+ render() {
+ return ;
+ }
+}
+
+SettingsContainer.propTypes = {
+ asset: PropTypes.object.isRequired,
+ pending: PropTypes.object.isRequired,
+ mergedSettings: PropTypes.object.isRequired,
+ updateAssetSettings: PropTypes.func.isRequired,
+ clearPending: PropTypes.func.isRequired,
+ notify: PropTypes.func.isRequired,
+ updatePending: PropTypes.func.isRequired,
+};
+
+const withSettingsFragments = withFragments({
+ asset: gql`
+ fragment CoralEmbedStream_Settings_asset on Asset {
+ id
+ settings {
+ moderation
+ }
+ }
+ `,
+});
+
+const mapStateToProps = (state) => ({
+ pending: state.configure.pending,
+ canSave: state.configure.canSave,
+});
+
+const mapDispatchToProps = (dispatch) =>
+ bindActionCreators({
+ notify,
+ clearPending,
+ updatePending,
+ }, dispatch);
+
+const enhance = compose(
+ withSettingsFragments,
+ withUpdateAssetSettings,
+ connect(mapStateToProps, mapDispatchToProps),
+ withMergedSettings('asset.settings', 'pending', 'mergedSettings'),
+);
+
+export default enhance(SettingsContainer);
diff --git a/client/coral-framework/hocs/index.js b/client/coral-framework/hocs/index.js
index 67b47482e..b3c0219e3 100644
--- a/client/coral-framework/hocs/index.js
+++ b/client/coral-framework/hocs/index.js
@@ -5,3 +5,4 @@ export {default as withCopyToClipboard} from './withCopyToClipboard';
export {default as withEmit} from './withEmit';
export {default as excludeIf} from './excludeIf';
export {default as connect} from './connect';
+export {default as withMergedSettings} from './withMergedSettings';
diff --git a/client/coral-framework/hocs/withMergedSettings.js b/client/coral-framework/hocs/withMergedSettings.js
new file mode 100644
index 000000000..8368475e6
--- /dev/null
+++ b/client/coral-framework/hocs/withMergedSettings.js
@@ -0,0 +1,16 @@
+import {mergeExcludingArrays} from 'coral-framework/utils';
+import assignWith from 'lodash/assignWith';
+import get from 'lodash/get';
+import {withPropsOnChange} from 'recompose';
+
+const withMergedSettings = (settings, pending, result) =>
+ withPropsOnChange(
+ (props, nextProps) =>
+ get(props, settings) !== get(nextProps, settings) ||
+ get(props, pending) !== get(nextProps, pending),
+ (props) => ({
+ [result]: assignWith({}, get(props, settings), get(props, pending), mergeExcludingArrays)
+ })
+ );
+
+export default withMergedSettings;
diff --git a/client/coral-framework/utils/index.js b/client/coral-framework/utils/index.js
index 037c1b99f..9f71b0913 100644
--- a/client/coral-framework/utils/index.js
+++ b/client/coral-framework/utils/index.js
@@ -2,6 +2,7 @@ import {gql} from 'react-apollo';
import t from 'coral-framework/services/i18n';
import union from 'lodash/union';
import {capitalize} from 'coral-framework/helpers/strings';
+import assignWith from 'lodash/assignWith';
export * from 'coral-framework/helpers/strings';
export const getTotalActionCount = (type, comment) => {
@@ -197,3 +198,12 @@ export function getTotalReactionsCount(actionSummaries) {
.filter(({__typename}) => !NOT_REACTION_TYPES.includes(__typename))
.reduce((total, {count}) => total + count, 0);
}
+
+// Like lodash merge but does not recurse into arrays.
+export function mergeExcludingArrays(objValue, srcValue) {
+ if (typeof srcValue === 'object' && !Array.isArray(srcValue)) {
+ return assignWith({}, objValue, srcValue, mergeExcludingArrays);
+ }
+ return srcValue;
+}
+