diff --git a/client/coral-framework/helpers/plugins.js b/client/coral-framework/helpers/plugins.js index 20aee7abb..d701b933f 100644 --- a/client/coral-framework/helpers/plugins.js +++ b/client/coral-framework/helpers/plugins.js @@ -21,7 +21,7 @@ export function getSlotElements(slot, props = {}) { .filter(o => o.module.slots[slot]) .map(o => o.module.slots[slot])); return components - .map((component, i) => React.createElement(component, {...props, key: i})); + .map((component, i) => React.createElement(component, {key: i, ...props})); } function getComponentFragments(components) { diff --git a/client/coral-reaction/helpers.js b/client/coral-framework/helpers/strings.js similarity index 100% rename from client/coral-reaction/helpers.js rename to client/coral-framework/helpers/strings.js diff --git a/client/coral-framework/hocs/withReaction.js b/client/coral-framework/hocs/withReaction.js new file mode 100644 index 000000000..b838941ad --- /dev/null +++ b/client/coral-framework/hocs/withReaction.js @@ -0,0 +1,220 @@ +import React from 'react'; +import get from 'lodash/get'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {compose, gql, graphql} from 'react-apollo'; +import withFragments from 'coral-framework/hocs/withFragments'; +import {showSignInDialog} from 'coral-framework/actions/auth'; +import {capitalize} from 'coral-framework/helpers/strings'; +import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils'; + +export default reaction => WrappedComponent => { + if (typeof reaction !== 'string') { + console.error('Reaction must be a valid string'); + return null; + } + + reaction = reaction.toLowerCase(); + + class WithReactions extends React.Component { + render() { + const {comment} = this.props; + + const reactionSummary = getMyActionSummary( + `${capitalize(reaction)}ActionSummary`, + comment + ); + const count = getTotalActionCount( + `${capitalize(reaction)}ActionSummary`, + comment + ); + + const withReactionProps = {reactionSummary, count}; + + return ; + } + } + + const isReaction = a => + a.__typereaction === `${capitalize(reaction)}ActionSummary`; + + const COMMENT_FRAGMENT = gql` + fragment ${capitalize(reaction)}Button_updateFragment on Comment { + action_summaries { + ... on ${capitalize(reaction)}ActionSummary { + count + current_user { + id + } + } + } + } + `; + + const withDeleteReaction = graphql( + gql` + mutation deleteReaction($id: ID!) { + deleteAction(id:$id) { + errors { + translation_key + } + } + } + `, + { + props: ({mutate}) => ({ + deleteReaction: (id, commentId) => { + return mutate({ + variables: {id}, + optimisticResponse: { + deleteAction: { + __typereaction: 'DeleteActionResponse', + errors: null + } + }, + update: proxy => { + const fragmentId = `Comment_${commentId}`; + + // Read the data from our cache for this query. + const data = proxy.readFragment({ + fragment: COMMENT_FRAGMENT, + id: fragmentId + }); + + // Check whether we liked this comment. + const idx = data.action_summaries.findIndex(isReaction); + if ( + idx < 0 || + get(data.action_summaries[idx], 'current_user.id') !== id + ) { + return; + } + + data.action_summaries[idx] = { + ...data.action_summaries[idx], + count: data.action_summaries[idx].count - 1, + current_user: null + }; + + // Write our data back to the cache. + proxy.writeFragment({ + fragment: COMMENT_FRAGMENT, + id: fragmentId, + data + }); + } + }); + } + }) + } + ); + + const withPostReaction = graphql( + gql` + mutation create${capitalize(reaction)}($${reaction}: Create${capitalize(reaction)}Input!) { + create${capitalize(reaction)}(${reaction}: $${reaction}) { + ${reaction} { + id + } + errors { + translation_key + } + } + } + `, + { + props: ({mutate}) => ({ + postReaction: reactionData => { + return mutate({ + variables: {[reaction]: reactionData}, + optimisticResponse: { + [`create${capitalize(reaction)}`]: { + __typereaction: `Create${capitalize(reaction)}Response`, + errors: null, + [reaction]: { + __typereaction: `${capitalize(reaction)}Action`, + id: 'pending' + } + } + }, + update: (proxy, mutationResult) => { + console.log(isReaction); + const fragmentId = `Comment_${reactionData.item_id}`; + + // Read the data from our cache for this query. + const data = proxy.readFragment({ + fragment: COMMENT_FRAGMENT, + id: fragmentId + }); + + // Add our comment from the mutation to the end. + let idx = data.action_summaries.findIndex(isReaction); + console.log(data.action_summaries); + + // Check whether we already reactioned this comment. + if (idx >= 0 && data.action_summaries[idx].current_user) { + return; + } + + if (idx < 0) { + // Add initial action when it doesn't exist. + data.action_summaries.push({ + __typereaction: `${capitalize(reaction)}ActionSummary`, + count: 0, + current_user: null + }); + idx = data.action_summaries.length - 1; + } + + data.action_summaries[idx] = { + ...data.action_summaries[idx], + count: data.action_summaries[idx].count + 1, + current_user: mutationResult.data[ + `create${capitalize(reaction)}` + ][reaction] + }; + + // Write our data back to the cache. + proxy.writeFragment({ + fragment: COMMENT_FRAGMENT, + id: fragmentId, + data + }); + } + }); + } + }) + } + ); + + const mapDispatchToProps = dispatch => + bindActionCreators({showSignInDialog}, dispatch); + + const enhance = compose( + withFragments({ + root: gql` + fragment ${capitalize(reaction)}Button_root on RootQuery { + me { + status + } + } + `, + comment: gql` + fragment ${capitalize(reaction)}Button_comment on Comment { + action_summaries { + ... on ${capitalize(reaction)}ActionSummary { + count + current_user { + id + } + } + } + }` + }), + connect(null, mapDispatchToProps), + withDeleteReaction, + withPostReaction + ); + + return enhance(WithReactions); +}; diff --git a/client/coral-reaction/actions.js b/client/coral-reaction/actions.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/coral-reaction/components/CoralReaction.js b/client/coral-reaction/components/CoralReaction.js deleted file mode 100644 index f691e2bb7..000000000 --- a/client/coral-reaction/components/CoralReaction.js +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cn from 'classnames'; -import styles from './style.css'; - -import {capitalize} from '../helpers'; -import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils'; - -class CoralReaction extends React.Component { - handleClick = () => { - const name = this.props.children; - const {postReaction, deleteReaction, showSignInDialog} = this.props; - - const {root: {me}, comment} = this.props; - - const myActionSummary = getMyActionSummary( - `${capitalize(name)}ActionSummary`, - comment - ); - - // If the current user does not exist, trigger sign in dialog. - if (!me) { - showSignInDialog(); - return; - } - - // If the current user is banned, do nothing. - if (me.status === 'BANNED') { - return; - } - - if (myActionSummary) { - deleteReaction(myActionSummary.current_user.id, comment.id); - } else { - postReaction({ - item_id: comment.id, - item_type: 'COMMENTS' - }); - } - }; - - render() { - const name = this.props.children; - const {comment} = this.props; - - console.log(this.props) - - if (!comment) { - return null; - } - - const myReaction = getMyActionSummary(`${capitalize(name)}ActionSummary`, comment); - let count = getTotalActionCount(`${capitalize(name)}ActionSummary`, comment); - - return ( -
- -
- ); - } -} - -CoralReaction.propTypes = { - children: PropTypes.string.isRequired, - icon: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - tag: PropTypes.string, - translations: PropTypes.object -}; - -export default CoralReaction; - -/** - * - * icon: Could be a string or a component https://material.io/icons/ - **/ diff --git a/client/coral-reaction/components/style.css b/client/coral-reaction/components/style.css deleted file mode 100644 index a82f20ff6..000000000 --- a/client/coral-reaction/components/style.css +++ /dev/null @@ -1,30 +0,0 @@ -.like { - display: inline-block; -} - -.button { - color: #2a2a2a; - margin: 5px 10px 5px 0px; - background: none; - padding: 0px; - border: none; - font-size: inherit; - - &:hover { - color: #767676; - cursor: pointer; - } - - &.liked { - color: rgb(0,134,227); - - &:hover { - color: rgb(0,134,227); - cursor: pointer; - } - } -} - -.icon { - padding: 0 5px; -} diff --git a/client/coral-reaction/constants.js b/client/coral-reaction/constants.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/coral-reaction/containers/CoralReaction.js b/client/coral-reaction/containers/CoralReaction.js deleted file mode 100644 index f8880a935..000000000 --- a/client/coral-reaction/containers/CoralReaction.js +++ /dev/null @@ -1,188 +0,0 @@ -import get from 'lodash/get'; -import {connect} from 'react-redux'; -import {bindActionCreators} from 'redux'; -import {compose, gql, graphql} from 'react-apollo'; -import CoralReaction from '../components/CoralReaction'; -import withFragments from 'coral-framework/hocs/withFragments'; -import {showSignInDialog} from 'coral-framework/actions/auth'; -import {capitalize} from '../helpers'; - -const name = 'love'; - -const isReaction = a => a.__typename === `${capitalize(name)}ActionSummary`; - -const COMMENT_FRAGMENT = gql` - fragment ${capitalize(name)}Button_updateFragment on Comment { - action_summaries { - ... on ${capitalize(name)}ActionSummary { - count - current_user { - id - } - } - } - } -`; - -const withDeleteReaction = graphql( - gql` - mutation deleteReaction($id: ID!) { - deleteAction(id:$id) { - errors { - translation_key - } - } - } - `, - { - props: ({mutate}) => ({ - deleteReaction: (id, commentId) => { - return mutate({ - variables: {id}, - optimisticResponse: { - deleteAction: { - __typename: 'DeleteActionResponse', - errors: null - } - }, - update: proxy => { - const fragmentId = `Comment_${commentId}`; - - // Read the data from our cache for this query. - const data = proxy.readFragment({ - fragment: COMMENT_FRAGMENT, - id: fragmentId - }); - - // Check whether we liked this comment. - const idx = data.action_summaries.findIndex(isReaction); - if ( - idx < 0 || - get(data.action_summaries[idx], 'current_user.id') !== id - ) { - return; - } - - data.action_summaries[idx] = { - ...data.action_summaries[idx], - count: data.action_summaries[idx].count - 1, - current_user: null - }; - - // Write our data back to the cache. - proxy.writeFragment({ - fragment: COMMENT_FRAGMENT, - id: fragmentId, - data - }); - } - }); - } - }) - } -); - -const withPostReaction = graphql( - gql` - mutation create${capitalize(name)}($${name}: Create${capitalize(name)}Input!) { - create${capitalize(name)}(${name}: $${capitalize(name)}) { - ${name} { - id - } - errors { - translation_key - } - } - } - `, - { - props: ({mutate}) => ({ - postReaction: reaction => { - return mutate({ - variables: {reaction}, - optimisticResponse: { - [`create${capitalize(name)}`]: { - __typename: `Create${capitalize(name)}Response`, - errors: null, - [name]: { - __typename: `${capitalize(name)}Action`, - id: 'pending' - } - } - }, - update: (proxy, mutationResult) => { - const fragmentId = `Comment_${reaction.item_id}`; - - // Read the data from our cache for this query. - const data = proxy.readFragment({ - fragment: COMMENT_FRAGMENT, - id: fragmentId - }); - - // Add our comment from the mutation to the end. - let idx = data.action_summaries.findIndex(isReaction); - - // Check whether we already reactioned this comment. - if (idx >= 0 && data.action_summaries[idx].current_user) { - return; - } - - if (idx < 0) { - // Add initial action when it doesn't exist. - data.action_summaries.push({ - __typename: `${capitalize(name)}ActionSummary`, - count: 0, - current_user: null - }); - idx = data.action_summaries.length - 1; - } - - data.action_summaries[idx] = { - ...data.action_summaries[idx], - count: data.action_summaries[idx].count + 1, - current_user: mutationResult.data[`create${capitalize(name)}`][name] - }; - - // Write our data back to the cache. - proxy.writeFragment({ - fragment: COMMENT_FRAGMENT, - id: fragmentId, - data - }); - } - }); - } - }) - } -); - -const mapDispatchToProps = dispatch => - bindActionCreators({showSignInDialog}, dispatch); - -const enhance = compose( - withFragments({ - root: gql` - fragment ${capitalize(name)}Button_root on RootQuery { - me { - status - } - } - `, - comment: gql` - fragment ${capitalize(name)}Button_comment on Comment { - action_summaries { - ... on ${capitalize(name)}ActionSummary { - count - current_user { - id - } - } - } - }` - }), - connect(null, mapDispatchToProps), - withDeleteReaction, - withPostReaction -); - -export default enhance(CoralReaction); diff --git a/client/coral-reaction/index.js b/client/coral-reaction/index.js deleted file mode 100644 index d8527b9fd..000000000 --- a/client/coral-reaction/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import CoralReaction from './containers/CoralReaction'; - -export default CoralReaction; \ No newline at end of file diff --git a/client/coral-reaction/reducer.js b/client/coral-reaction/reducer.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/coral-reaction/typeDefGenerator.js b/client/coral-reaction/typeDefGenerator.js deleted file mode 100644 index 63c20684e..000000000 --- a/client/coral-reaction/typeDefGenerator.js +++ /dev/null @@ -1,80 +0,0 @@ -export function typeDefGenerator(type = '') { - function capitalize(str) { - const newString = String.new(str); - return newString.charAt(0).toUpperCase() + newString.slice(1); - } - - return ` -enum ACTION_TYPE { - - # Represents a Like. - LIKE -} - -enum ASSET_METRICS_SORT { - - # Represents a LikeAction. - LIKE -} - -input CreateLikeInput { - - # The item's id for which we are to create a like. - item_id: ID! - - # The type of the item for which we are to create the like. - item_type: ACTION_ITEM_TYPE! -} - -# LikeAction is used by users who "like" a specific entity. -type LikeAction implements Action { - - # The ID of the action. - id: ID! - - # The author of the action. - user: User - - # The time when the Action was updated. - updated_at: Date - - # The time when the Action was created. - created_at: Date -} - -type LikeActionSummary implements ActionSummary { - - # The count of actions with this group. - count: Int - - # The current user's action. - current_user: LikeAction -} - -# A summary of counts related to all the Likes on an Asset. -type LikeAssetActionSummary implements AssetActionSummary { - - # Number of likes associated with actionable types on this this Asset. - actionCount: Int - - # Number of unique actionable types that are referenced by the likes. - actionableItemCount: Int -} - -type CreateLikeResponse implements Response { - - # The like that was created. - like: LikeAction - - # An array of errors relating to the mutation that occurred. - errors: [UserError] -} - -type RootMutation { - - # Creates a like on an entity. - createLike(like: CreateLikeInput!): CreateLikeResponse -} - -`; -} \ No newline at end of file