add file reactions code from odysee
This commit is contained in:
parent
dc42df3bf2
commit
eb84a366d2
10 changed files with 294 additions and 1 deletions
|
@ -25,6 +25,7 @@ SHOW_ADS=true
|
|||
YRBL_HAPPY_IMG_URL=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-happy/7aa50a7e5adaf48691935d55e45d697547392929/839d9a
|
||||
YRBL_SAD_IMG_URL=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-sad/c2d9649633d974e5ffb503925e1f17d951f1bd0f/f262dd
|
||||
ENABLE_COMMENT_REACTIONS=false
|
||||
ENABLE_FILE_REACTIONS=false
|
||||
|
||||
# OG
|
||||
OG_TITLE_SUFFIX=| lbry.tv
|
||||
|
|
|
@ -30,6 +30,7 @@ const config = {
|
|||
AUTO_FOLLOW_CHANNELS: process.env.AUTO_FOLLOW_CHANNELS,
|
||||
UNSYNCED_SETTINGS: process.env.UNSYNCED_SETTINGS,
|
||||
ENABLE_COMMENT_REACTIONS: process.env.ENABLE_COMMENT_REACTIONS === 'true',
|
||||
ENABLE_FILE_REACTIONS: process.env.ENABLE_FILE_REACTIONS === 'true',
|
||||
SIMPLE_SITE: process.env.SIMPLE_SITE === 'true',
|
||||
SHOW_ADS: process.env.SHOW_ADS === 'true',
|
||||
PINNED_URI_1: process.env.PINNED_URI_1,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @flow
|
||||
import { SIMPLE_SITE, SITE_NAME } from 'config';
|
||||
import { SIMPLE_SITE, SITE_NAME, ENABLE_FILE_REACTIONS } from 'config';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import * as ICONS from 'constants/icons';
|
||||
|
@ -11,6 +11,7 @@ import * as RENDER_MODES from 'constants/file_render_modes';
|
|||
import { useIsMobile } from 'effects/use-screensize';
|
||||
import ClaimSupportButton from 'component/claimSupportButton';
|
||||
import { useHistory } from 'react-router';
|
||||
import FileReactions from 'component/fileReactions';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
|
@ -79,6 +80,7 @@ function FileActions(props: Props) {
|
|||
|
||||
const lhsSection = (
|
||||
<>
|
||||
{ENABLE_FILE_REACTIONS && <FileReactions uri={uri} />}
|
||||
<ClaimSupportButton uri={uri} fileAction />
|
||||
<Button
|
||||
button="alt"
|
||||
|
|
26
ui/component/fileReactions/index.js
Normal file
26
ui/component/fileReactions/index.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
makeSelectReactionsForUri,
|
||||
makeSelectMyReactionForUri,
|
||||
makeSelectLikeCountForUri,
|
||||
makeSelectDislikeCountForUri,
|
||||
} from 'redux/selectors/reactions';
|
||||
import { doFetchReactions, doReactionLike, doReactionDislike } from 'redux/actions/reactions';
|
||||
import { selectThemePath } from 'redux/selectors/settings';
|
||||
import FileViewCount from './view';
|
||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
reactions: makeSelectReactionsForUri(props.uri)(state),
|
||||
myReaction: makeSelectMyReactionForUri(props.uri)(state),
|
||||
likeCount: makeSelectLikeCountForUri(props.uri)(state),
|
||||
dislikeCount: makeSelectDislikeCountForUri(props.uri)(state),
|
||||
theme: selectThemePath(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
doFetchReactions,
|
||||
doReactionLike,
|
||||
doReactionDislike,
|
||||
})(FileViewCount);
|
52
ui/component/fileReactions/view.jsx
Normal file
52
ui/component/fileReactions/view.jsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import Button from 'component/button';
|
||||
|
||||
type Props = {
|
||||
claim: StreamClaim,
|
||||
doFetchReactions: string => void,
|
||||
doReactionLike: string => void,
|
||||
doReactionDislike: string => void,
|
||||
uri: string,
|
||||
likeCount: number,
|
||||
dislikeCount: number,
|
||||
myReaction: ?string,
|
||||
};
|
||||
|
||||
function FileReactions(props: Props) {
|
||||
const { claim, uri, doFetchReactions, doReactionLike, doReactionDislike, likeCount, dislikeCount } = props;
|
||||
const claimId = claim && claim.claim_id;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (claimId) {
|
||||
doFetchReactions(claimId);
|
||||
}
|
||||
}, [claimId, doFetchReactions]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
title={__('I like this')}
|
||||
requiresAuth
|
||||
className={classnames('button--file-action')}
|
||||
label={String(likeCount)}
|
||||
iconSize={18}
|
||||
icon={ICONS.UPVOTE}
|
||||
onClick={() => doReactionLike(uri)}
|
||||
/>
|
||||
<Button
|
||||
requiresAuth
|
||||
title={__('I dislike this')}
|
||||
className={classnames('button--file-action')}
|
||||
label={String(dislikeCount)}
|
||||
iconSize={18}
|
||||
icon={ICONS.DOWNVOTE}
|
||||
onClick={() => doReactionDislike(uri)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default FileReactions;
|
|
@ -278,3 +278,11 @@ export const TOGGLE_BLOCK_CHANNEL = 'TOGGLE_BLOCK_CHANNEL';
|
|||
// Notifications
|
||||
export const WS_CONNECT = 'WS_CONNECT';
|
||||
export const WS_DISCONNECT = 'WS_DISCONNECT';
|
||||
|
||||
export const REACTIONS_LIST_STARTED = 'REACTIONS_LIST_STARTED';
|
||||
export const REACTIONS_LIST_FAILED = 'REACTIONS_LIST_FAILED';
|
||||
export const REACTIONS_LIST_COMPLETED = 'REACTIONS_LIST_COMPLETED';
|
||||
export const REACTIONS_NEW_STARTED = 'REACTIONS_NEW_STARTED';
|
||||
export const REACTIONS_NEW_FAILED = 'REACTIONS_NEW_FAILED';
|
||||
export const REACTIONS_LIKE_COMPLETED = 'REACTIONS_LIKE_COMPLETED';
|
||||
export const REACTIONS_DISLIKE_COMPLETED = 'REACTIONS_DISLIKE_COMPLETED';
|
||||
|
|
|
@ -20,6 +20,7 @@ import userReducer from 'redux/reducers/user';
|
|||
import commentsReducer from 'redux/reducers/comments';
|
||||
import blockedReducer from 'redux/reducers/blocked';
|
||||
import searchReducer from 'redux/reducers/search';
|
||||
import reactionsReducer from 'redux/reducers/reactions';
|
||||
|
||||
export default history =>
|
||||
combineReducers({
|
||||
|
@ -35,6 +36,7 @@ export default history =>
|
|||
homepage: homepageReducer,
|
||||
notifications: notificationsReducer,
|
||||
publish: publishReducer,
|
||||
reactions: reactionsReducer,
|
||||
rewards: rewardsReducer,
|
||||
search: searchReducer,
|
||||
settings: settingsReducer,
|
||||
|
|
70
ui/redux/actions/reactions.js
Normal file
70
ui/redux/actions/reactions.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
// @flow
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import * as REACTION_TYPES from 'constants/reactions';
|
||||
import { makeSelectMyReactionForUri } from '../selectors/reactions';
|
||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||
|
||||
export const doFetchReactions = (claimId: string) => (dispatch: Dispatch) => {
|
||||
dispatch({ type: ACTIONS.REACTIONS_LIST_STARTED });
|
||||
|
||||
return Lbryio.call('reaction', 'list', { claim_ids: claimId }, 'post')
|
||||
.then((reactions: Array<number>) => {
|
||||
dispatch({ type: ACTIONS.REACTIONS_LIST_COMPLETED, data: { claimId, reactions } });
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch({ type: ACTIONS.REACTIONS_LIST_FAILED, data: error });
|
||||
});
|
||||
};
|
||||
|
||||
export const doReactionLike = (uri: string) => (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const myReaction = makeSelectMyReactionForUri(uri)(state);
|
||||
const claim = makeSelectClaimForUri(uri)(state);
|
||||
const claimId = claim.claim_id;
|
||||
const shouldRemove = myReaction === REACTION_TYPES.LIKE;
|
||||
|
||||
return Lbryio.call(
|
||||
'reaction',
|
||||
'react',
|
||||
{
|
||||
claim_ids: claimId,
|
||||
type: REACTION_TYPES.LIKE,
|
||||
clear_types: REACTION_TYPES.DISLIKE,
|
||||
...(shouldRemove ? { remove: true } : {}),
|
||||
},
|
||||
'post'
|
||||
)
|
||||
.then(() => {
|
||||
dispatch({ type: ACTIONS.REACTIONS_LIKE_COMPLETED, data: { claimId, shouldRemove } });
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch({ type: ACTIONS.REACTIONS_NEW_FAILED, data: error });
|
||||
});
|
||||
};
|
||||
|
||||
export const doReactionDislike = (uri: string) => (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const myReaction = makeSelectMyReactionForUri(uri)(state);
|
||||
const claim = makeSelectClaimForUri(uri)(state);
|
||||
const claimId = claim.claim_id;
|
||||
const shouldRemove = myReaction === REACTION_TYPES.DISLIKE;
|
||||
|
||||
return Lbryio.call(
|
||||
'reaction',
|
||||
'react',
|
||||
{
|
||||
claim_ids: claimId,
|
||||
type: REACTION_TYPES.DISLIKE,
|
||||
clear_types: REACTION_TYPES.LIKE,
|
||||
...(shouldRemove ? { remove: true } : {}),
|
||||
},
|
||||
'post'
|
||||
)
|
||||
.then(() => {
|
||||
dispatch({ type: ACTIONS.REACTIONS_DISLIKE_COMPLETED, data: { claimId, shouldRemove } });
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch({ type: ACTIONS.REACTIONS_NEW_FAILED, data: error });
|
||||
});
|
||||
};
|
55
ui/redux/reducers/reactions.js
Normal file
55
ui/redux/reducers/reactions.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
// @flow
|
||||
import { handleActions } from 'util/redux-utils';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import * as REACTION_TYPES from 'constants/reactions';
|
||||
|
||||
const defaultState = {
|
||||
fetchingReactions: false,
|
||||
reactionsError: undefined,
|
||||
reactionsById: {},
|
||||
};
|
||||
|
||||
export default handleActions(
|
||||
{
|
||||
[ACTIONS.REACTIONS_LIST_STARTED]: state => ({ ...state, fetchingReactions: true }),
|
||||
[ACTIONS.REACTIONS_LIST_FAILED]: (state, action) => ({
|
||||
...state,
|
||||
reactionsError: action.data,
|
||||
}),
|
||||
[ACTIONS.REACTIONS_LIST_COMPLETED]: (state, action) => {
|
||||
const { claimId, reactions } = action.data;
|
||||
|
||||
const reactionsById = { ...state.reactionsById, [claimId]: reactions };
|
||||
return {
|
||||
...state,
|
||||
fetchingreactions: false,
|
||||
reactionsById,
|
||||
};
|
||||
},
|
||||
[ACTIONS.REACTIONS_LIKE_COMPLETED]: (state, action) => {
|
||||
const { claimId, shouldRemove } = action.data;
|
||||
const reactionsById = { ...state.reactionsById };
|
||||
reactionsById[claimId].my_reactions[claimId][REACTION_TYPES.LIKE] = shouldRemove ? 0 : 1;
|
||||
reactionsById[claimId].my_reactions[claimId][REACTION_TYPES.DISLIKE] = 0;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchingreactions: false,
|
||||
reactionsById,
|
||||
};
|
||||
},
|
||||
[ACTIONS.REACTIONS_DISLIKE_COMPLETED]: (state, action) => {
|
||||
const { claimId, shouldRemove } = action.data;
|
||||
const reactionsById = { ...state.reactionsById };
|
||||
reactionsById[claimId].my_reactions[claimId][REACTION_TYPES.DISLIKE] = shouldRemove ? 0 : 1;
|
||||
reactionsById[claimId].my_reactions[claimId][REACTION_TYPES.LIKE] = 0;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchingreactions: false,
|
||||
reactionsById,
|
||||
};
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
76
ui/redux/selectors/reactions.js
Normal file
76
ui/redux/selectors/reactions.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
import * as REACTION_TYPES from 'constants/reactions';
|
||||
import { createSelector } from 'reselect';
|
||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||
|
||||
const selectState = state => state.reactions || {};
|
||||
|
||||
export const selectReactionsById = createSelector(selectState, state => state.reactionsById);
|
||||
|
||||
export const selectFetchingReactions = createSelector(selectState, state => state.fetchingReactions);
|
||||
|
||||
export const makeSelectReactionsForUri = uri =>
|
||||
createSelector(makeSelectClaimForUri(uri), selectReactionsById, (claim, reactionsById) => {
|
||||
return claim ? reactionsById[claim.claim_id] : {};
|
||||
});
|
||||
|
||||
export const makeSelectMyReactionForUri = uri =>
|
||||
createSelector(makeSelectClaimForUri(uri), makeSelectReactionsForUri(uri), (claim, reactions) => {
|
||||
const claimId = claim.claim_id;
|
||||
|
||||
if (!reactions || reactions.my_reactions === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const myReactions = reactions.my_reactions[claimId];
|
||||
if (myReactions[REACTION_TYPES.LIKE]) {
|
||||
return REACTION_TYPES.LIKE;
|
||||
} else if (myReactions[REACTION_TYPES.DISLIKE]) {
|
||||
return REACTION_TYPES.DISLIKE;
|
||||
} else {
|
||||
// Ignore other types of reactions for now
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
export const makeSelectLikeCountForUri = uri =>
|
||||
createSelector(makeSelectClaimForUri(uri), makeSelectReactionsForUri(uri), (claim, reactions) => {
|
||||
const claimId = claim.claim_id;
|
||||
|
||||
if (!reactions || reactions.my_reactions === null || reactions.others_reactions === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
if (reactions.others_reactions) {
|
||||
const likeCount = reactions.others_reactions[claimId][REACTION_TYPES.LIKE] || 0;
|
||||
|
||||
count += likeCount;
|
||||
}
|
||||
if (reactions.my_reactions) {
|
||||
const likeCount = reactions.my_reactions[claimId][REACTION_TYPES.LIKE] || 0;
|
||||
count += likeCount;
|
||||
}
|
||||
|
||||
return count;
|
||||
});
|
||||
|
||||
export const makeSelectDislikeCountForUri = uri =>
|
||||
createSelector(makeSelectClaimForUri(uri), makeSelectReactionsForUri(uri), (claim, reactions) => {
|
||||
const claimId = claim.claim_id;
|
||||
|
||||
if (!reactions || reactions.my_reactions === null || reactions.others_reactions === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
if (reactions.others_reactions) {
|
||||
const dislikeCount = reactions.others_reactions[claimId][REACTION_TYPES.DISLIKE] || 0;
|
||||
count += dislikeCount;
|
||||
}
|
||||
if (reactions.my_reactions) {
|
||||
const dislikeCount = reactions.my_reactions[claimId][REACTION_TYPES.DISLIKE] || 0;
|
||||
count += dislikeCount;
|
||||
}
|
||||
|
||||
return count;
|
||||
});
|
Loading…
Reference in a new issue