allow reaction list without channel

uncomment

reacts requireauth, commentReact handles missing channels

enable config, better track pending reacts
This commit is contained in:
jessop 2020-09-30 11:59:05 -04:00 committed by Sean Yesmunt
parent 5f9fda0e7c
commit e954bce821
10 changed files with 62 additions and 55 deletions

View file

@ -29,13 +29,13 @@ const config = {
DEFAULT_LANGUAGE: process.env.DEFAULT_LANGUAGE,
AUTO_FOLLOW_CHANNELS: process.env.AUTO_FOLLOW_CHANNELS,
UNSYNCED_SETTINGS: process.env.UNSYNCED_SETTINGS,
ENABLE_COMMENT_REACTIONS: process.env.ENABLE_COMMENT_REACTIONS === 'true',
SIMPLE_SITE: process.env.SIMPLE_SITE === 'true',
SHOW_ADS: process.env.SHOW_ADS === 'true',
PINNED_URI_1: process.env.PINNED_URI_1,
PINNED_LABEL_1: process.env.PINNED_LABEL_1,
PINNED_URI_2: process.env.PINNED_URI_2,
PINNED_LABEL_2: process.env.PINNED_LABEL_2,
ENABLE_COMMENT_REACTIONS: process.env.ENABLE_COMMENT_REACTIONS === 'true',
};
config.URL_LOCAL = `http://localhost:${config.WEB_SERVER_PORT}`;

View file

@ -25,7 +25,7 @@ declare type CommentsState = {
isFetchingReacts: boolean,
myReactsByCommentId: any,
othersReactsByCommentId: any,
typesReacting: Array<string>,
pendingCommentReactions: Array<string>,
};
declare type CommentReactParams = {

View file

@ -1303,5 +1303,9 @@
"Create a channel": "Create a channel",
"Credit Details": "Credit Details",
"LBRY Credits": "LBRY Credits",
"Sign In": "Sign In",
"Change to tile layout": "Change to tile layout",
"%total_comments% comments": "%total_comments% comments",
"Sync my YouTube channel": "Sync my YouTube channel",
"--end--": "--end--"
}

View file

@ -2,7 +2,7 @@
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
import { SITE_NAME, SIMPLE_SITE } from 'config';
import { SITE_NAME, SIMPLE_SITE, ENABLE_COMMENT_REACTIONS } from 'config';
import React, { useEffect, useState } from 'react';
import { isEmpty } from 'util/object';
import DateTime from 'component/dateTime';
@ -255,7 +255,7 @@ function Comment(props: Props) {
icon={ICONS.REPLY}
/>
)}
<CommentReactions commentId={commentId} />
{ENABLE_COMMENT_REACTIONS && <CommentReactions commentId={commentId} />}
</div>
</>
)}

View file

@ -3,14 +3,14 @@ import Comment from './view';
import {
makeSelectMyReactionsForComment,
makeSelectOthersReactionsForComment,
selectTypesReacting,
selectPendingCommentReacts,
} from 'redux/selectors/comments';
import { doCommentReact } from 'redux/actions/comments';
const select = (state, props) => ({
myReacts: makeSelectMyReactionsForComment(props.commentId)(state),
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
typesReacting: selectTypesReacting(state),
pendingCommentReacts: selectPendingCommentReacts(state),
});
const perform = dispatch => ({

View file

@ -12,10 +12,11 @@ type Props = {
othersReacts: any,
react: (string, string) => void,
commentId: string,
pendingCommentReacts: Array<string>,
};
export default function CommentReactions(props: Props) {
const { myReacts, othersReacts, commentId, react } = props;
const { myReacts, othersReacts, commentId, react, pendingCommentReacts } = props;
const [activeChannel] = usePersistedState('comment-channel');
const getCountForReact = type => {
@ -29,29 +30,29 @@ export default function CommentReactions(props: Props) {
return count;
};
if (!ENABLE_COMMENT_REACTIONS) {
return null;
}
// return null;
return (
<>
<Button
requiresAuth={IS_WEB}
title={__('Upvote')}
icon={ICONS.UPVOTE}
className={classnames('comment__action', {
'comment__action--active': myReacts && myReacts.includes(REACTION_TYPES.LIKE),
})}
disabled={!activeChannel}
disabled={!activeChannel || pendingCommentReacts.includes(commentId + REACTION_TYPES.LIKE)}
onClick={() => react(commentId, REACTION_TYPES.LIKE)}
label={getCountForReact(REACTION_TYPES.LIKE)}
/>
<Button
requiresAuth={IS_WEB}
title={__('Downvote')}
icon={ICONS.DOWNVOTE}
className={classnames('comment__action', {
'comment__action--active': myReacts && myReacts.includes(REACTION_TYPES.DISLIKE),
})}
disabled={!activeChannel}
disabled={!activeChannel || pendingCommentReacts.includes(commentId + REACTION_TYPES.DISLIKE)}
onClick={() => react(commentId, REACTION_TYPES.DISLIKE)}
label={getCountForReact(REACTION_TYPES.DISLIKE)}
/>

View file

@ -1,5 +1,4 @@
// @flow
import { ENABLE_COMMENT_REACTIONS } from 'config';
import * as ICONS from 'constants/icons';
import React, { useEffect } from 'react';
import Comment from 'component/comment';
@ -8,6 +7,7 @@ import Button from 'component/button';
import Card from 'component/common/card';
import CommentCreate from 'component/commentCreate';
import usePersistedState from 'effects/use-persisted-state';
import { ENABLE_COMMENT_REACTIONS } from 'config';
type Props = {
comments: Array<any>,
@ -33,6 +33,7 @@ function CommentList(props: Props) {
linkedComment,
totalComments,
} = props;
const commentRef = React.useRef();
const [activeChannel] = usePersistedState('comment-channel', '');
const [start] = React.useState(0);
@ -67,7 +68,7 @@ function CommentList(props: Props) {
if (totalComments && ENABLE_COMMENT_REACTIONS) {
fetchReacts(uri);
}
}, [fetchReacts, uri, totalComments, activeChannel]);
}, [fetchReacts, uri, totalComments, activeChannel, ENABLE_COMMENT_REACTIONS]);
useEffect(() => {
if (linkedCommentId && commentRef && commentRef.current) {

View file

@ -7,6 +7,7 @@ import {
makeSelectCommentIdsForUri,
makeSelectMyReactionsForComment,
makeSelectOthersReactionsForComment,
selectPendingCommentReacts,
} from 'redux/selectors/comments';
export function doCommentList(uri: string, page: number = 1, pageSize: number = 99999) {
@ -49,31 +50,29 @@ export function doCommentReactList(uri: string | null, commentId?: string) {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const channel = localStorage.getItem('comment-channel');
if (!channel) {
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_FAILED,
data: 'No active channel found',
});
return;
}
const commentIds = uri ? makeSelectCommentIdsForUri(uri)(state) : [commentId];
const myChannels = selectMyChannelClaims(state);
const claimForChannelName = myChannels.find(chan => chan.name === channel);
const channelId = claimForChannelName && claimForChannelName.claim_id;
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_STARTED,
});
Lbry.comment_react_list({
const params: { comment_ids: string, channel_name?: string, channel_id?: string } = {
comment_ids: commentIds.join(','),
channel_name: channel,
channel_id: channelId,
})
};
if (channel && myChannels) {
const claimForChannelName = myChannels && myChannels.find(chan => chan.name === channel);
const channelId = claimForChannelName && claimForChannelName.claim_id;
params['channel_name'] = channel;
params['channel_id'] = channelId;
}
Lbry.comment_react_list(params)
.then((result: CommentReactListResponse) => {
const { my_reactions: myReactions, others_reactions: othersReactions } = result;
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_COMPLETED,
data: {
myReactions,
myReactions: myReactions || {},
othersReactions,
},
});
@ -91,23 +90,27 @@ export function doCommentReact(commentId: string, type: string) {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const channel = localStorage.getItem('comment-channel');
if (!channel) {
const pendingReacts = selectPendingCommentReacts(state);
const myChannels = selectMyChannelClaims(state);
const exclusiveTypes = {
[REACTION_TYPES.LIKE]: REACTION_TYPES.DISLIKE,
[REACTION_TYPES.DISLIKE]: REACTION_TYPES.LIKE,
};
if (!channel || !myChannels) {
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_FAILED,
data: 'No active channel found',
});
return;
}
const myChannels = selectMyChannelClaims(state);
if (pendingReacts.includes(commentId + exclusiveTypes[type])) {
// ignore dislikes during likes, for example
return;
}
let myReacts = makeSelectMyReactionsForComment(commentId)(state);
let reactingTypes = [];
const othersReacts = makeSelectOthersReactionsForComment(commentId)(state);
const claimForChannelName = myChannels.find(chan => chan.name === channel);
const channelId = claimForChannelName && claimForChannelName.claim_id;
const exclusiveTypes = {
[REACTION_TYPES.LIKE]: REACTION_TYPES.DISLIKE,
[REACTION_TYPES.DISLIKE]: REACTION_TYPES.LIKE,
};
const params: CommentReactParams = {
comment_ids: commentId,
@ -118,13 +121,10 @@ export function doCommentReact(commentId: string, type: string) {
if (myReacts.includes(type)) {
params['remove'] = true;
myReacts.splice(myReacts.indexOf(type), 1);
reactingTypes.push(type);
} else {
myReacts.push(type);
reactingTypes.push(type);
if (Object.keys(exclusiveTypes).includes(type)) {
params['clear_types'] = exclusiveTypes[type];
reactingTypes.push(exclusiveTypes[type]);
if (myReacts.indexOf(exclusiveTypes[type]) !== -1) {
myReacts.splice(myReacts.indexOf(exclusiveTypes[type]), 1);
}
@ -132,7 +132,7 @@ export function doCommentReact(commentId: string, type: string) {
}
dispatch({
type: ACTIONS.COMMENT_REACT_STARTED,
data: reactingTypes,
data: commentId + type,
});
// simulate api return shape: ['like'] -> { 'like': 1 }
const myReactsObj = myReacts.reduce((acc, el) => {
@ -144,7 +144,7 @@ export function doCommentReact(commentId: string, type: string) {
.then((result: CommentReactListResponse) => {
dispatch({
type: ACTIONS.COMMENT_REACT_COMPLETED,
data: reactingTypes,
data: commentId + type,
});
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_COMPLETED,
@ -157,7 +157,7 @@ export function doCommentReact(commentId: string, type: string) {
.catch(error => {
dispatch({
type: ACTIONS.COMMENT_REACT_FAILED,
data: reactingTypes,
data: commentId + type,
});
});
};

View file

@ -12,6 +12,7 @@ const defaultState: CommentsState = {
isCommenting: false,
myComments: undefined,
isFetchingReacts: false,
pendingCommentReactions: [],
typesReacting: [],
myReactsByCommentId: {},
othersReactsByCommentId: {},
@ -83,35 +84,35 @@ export default handleActions(
}),
[ACTIONS.COMMENT_REACT_FAILED]: (state: CommentsState, action: any): CommentsState => {
const commentReaction = action.data; // String: reactionHash + type
const newReactingTypes = new Set(state.pendingCommentReactions);
newReactingTypes.delete(commentReaction);
return {
...state,
typesReacting: [],
pendingCommentReactions: Array.from(newReactingTypes),
};
},
[ACTIONS.COMMENT_REACT_STARTED]: (state: CommentsState, action: any): CommentsState => {
const reactingTypes = action.data;
const newReactingTypes = new Set(state.typesReacting);
reactingTypes.forEach(type => {
newReactingTypes.add(type);
});
const commentReaction = action.data;
const newReactingTypes = new Set(state.pendingCommentReactions);
newReactingTypes.add(commentReaction);
return {
...state,
typesReacting: Array.from(newReactingTypes),
pendingCommentReactions: Array.from(newReactingTypes),
};
},
[ACTIONS.COMMENT_REACT_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
const reactingTypes = action.data;
const newReactingTypes = new Set(state.typesReacting);
reactingTypes.forEach(type => {
newReactingTypes.delete(type);
});
const commentReaction = action.data; // String: reactionHash + type
const newReactingTypes = new Set(state.pendingCommentReactions);
newReactingTypes.delete(commentReaction);
return {
...state,
typesReacting: Array.from(newReactingTypes),
pendingCommentReactions: Array.from(newReactingTypes),
};
},

View file

@ -104,7 +104,7 @@ export const makeSelectOthersReactionsForComment = (commentId: string) =>
return state.othersReactsByCommentId[commentId];
});
export const selectTypesReacting = createSelector(selectState, state => state.typesReacting);
export const selectPendingCommentReacts = createSelector(selectState, state => state.pendingCommentReactions);
export const makeSelectCommentsForUri = (uri: string) =>
createSelector(