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_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
|
YRBL_SAD_IMG_URL=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-sad/c2d9649633d974e5ffb503925e1f17d951f1bd0f/f262dd
|
||||||
ENABLE_COMMENT_REACTIONS=false
|
ENABLE_COMMENT_REACTIONS=false
|
||||||
|
ENABLE_FILE_REACTIONS=false
|
||||||
|
|
||||||
# OG
|
# OG
|
||||||
OG_TITLE_SUFFIX=| lbry.tv
|
OG_TITLE_SUFFIX=| lbry.tv
|
||||||
|
|
|
@ -30,6 +30,7 @@ const config = {
|
||||||
AUTO_FOLLOW_CHANNELS: process.env.AUTO_FOLLOW_CHANNELS,
|
AUTO_FOLLOW_CHANNELS: process.env.AUTO_FOLLOW_CHANNELS,
|
||||||
UNSYNCED_SETTINGS: process.env.UNSYNCED_SETTINGS,
|
UNSYNCED_SETTINGS: process.env.UNSYNCED_SETTINGS,
|
||||||
ENABLE_COMMENT_REACTIONS: process.env.ENABLE_COMMENT_REACTIONS === 'true',
|
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',
|
SIMPLE_SITE: process.env.SIMPLE_SITE === 'true',
|
||||||
SHOW_ADS: process.env.SHOW_ADS === 'true',
|
SHOW_ADS: process.env.SHOW_ADS === 'true',
|
||||||
PINNED_URI_1: process.env.PINNED_URI_1,
|
PINNED_URI_1: process.env.PINNED_URI_1,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @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 PAGES from 'constants/pages';
|
||||||
import * as MODALS from 'constants/modal_types';
|
import * as MODALS from 'constants/modal_types';
|
||||||
import * as ICONS from 'constants/icons';
|
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 { useIsMobile } from 'effects/use-screensize';
|
||||||
import ClaimSupportButton from 'component/claimSupportButton';
|
import ClaimSupportButton from 'component/claimSupportButton';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
|
import FileReactions from 'component/fileReactions';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
|
@ -79,6 +80,7 @@ function FileActions(props: Props) {
|
||||||
|
|
||||||
const lhsSection = (
|
const lhsSection = (
|
||||||
<>
|
<>
|
||||||
|
{ENABLE_FILE_REACTIONS && <FileReactions uri={uri} />}
|
||||||
<ClaimSupportButton uri={uri} fileAction />
|
<ClaimSupportButton uri={uri} fileAction />
|
||||||
<Button
|
<Button
|
||||||
button="alt"
|
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
|
// Notifications
|
||||||
export const WS_CONNECT = 'WS_CONNECT';
|
export const WS_CONNECT = 'WS_CONNECT';
|
||||||
export const WS_DISCONNECT = 'WS_DISCONNECT';
|
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 commentsReducer from 'redux/reducers/comments';
|
||||||
import blockedReducer from 'redux/reducers/blocked';
|
import blockedReducer from 'redux/reducers/blocked';
|
||||||
import searchReducer from 'redux/reducers/search';
|
import searchReducer from 'redux/reducers/search';
|
||||||
|
import reactionsReducer from 'redux/reducers/reactions';
|
||||||
|
|
||||||
export default history =>
|
export default history =>
|
||||||
combineReducers({
|
combineReducers({
|
||||||
|
@ -35,6 +36,7 @@ export default history =>
|
||||||
homepage: homepageReducer,
|
homepage: homepageReducer,
|
||||||
notifications: notificationsReducer,
|
notifications: notificationsReducer,
|
||||||
publish: publishReducer,
|
publish: publishReducer,
|
||||||
|
reactions: reactionsReducer,
|
||||||
rewards: rewardsReducer,
|
rewards: rewardsReducer,
|
||||||
search: searchReducer,
|
search: searchReducer,
|
||||||
settings: settingsReducer,
|
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