use comment component for livestream comments

This commit is contained in:
Sean Yesmunt 2021-03-23 22:53:33 -04:00 committed by jessopb
parent d8a5ca082b
commit 64e8c8e095
7 changed files with 100 additions and 76 deletions

View file

@ -50,6 +50,7 @@ type Props = {
activeChannelClaim: ?ChannelClaim, activeChannelClaim: ?ChannelClaim,
playingUri: ?PlayingUri, playingUri: ?PlayingUri,
stakedLevel: number, stakedLevel: number,
livestream?: boolean,
}; };
const LENGTH_TO_COLLAPSE = 300; const LENGTH_TO_COLLAPSE = 300;
@ -77,6 +78,7 @@ function Comment(props: Props) {
othersReacts, othersReacts,
playingUri, playingUri,
stakedLevel, stakedLevel,
livestream,
} = props; } = props;
const { const {
push, push,
@ -162,6 +164,7 @@ function Comment(props: Props) {
className={classnames('comment', { className={classnames('comment', {
'comment--top-level': isTopLevel, 'comment--top-level': isTopLevel,
'comment--reply': !isTopLevel, 'comment--reply': !isTopLevel,
'comment--livestream': livestream,
})} })}
id={commentId} id={commentId}
onMouseOver={() => setMouseHover(true)} onMouseOver={() => setMouseHover(true)}
@ -173,13 +176,20 @@ function Comment(props: Props) {
'comment--slimed': slimedToDeath && !displayDeadComment, 'comment--slimed': slimedToDeath && !displayDeadComment,
})} })}
> >
<div className="comment__thumbnail-wrapper"> {!livestream && (
{authorUri ? ( <div className="comment__thumbnail-wrapper">
<ChannelThumbnail uri={authorUri} obscure={channelIsBlocked} small className="comment__author-thumbnail" /> {authorUri ? (
) : ( <ChannelThumbnail
<ChannelThumbnail small className="comment__author-thumbnail" /> uri={authorUri}
)} obscure={channelIsBlocked}
</div> small
className="comment__author-thumbnail"
/>
) : (
<ChannelThumbnail small className="comment__author-thumbnail" />
)}
</div>
)}
<div className="comment__body_container"> <div className="comment__body_container">
<div className="comment__meta"> <div className="comment__meta">
@ -187,13 +197,15 @@ function Comment(props: Props) {
{!author ? ( {!author ? (
<span className="comment__author">{__('Anonymous')}</span> <span className="comment__author">{__('Anonymous')}</span>
) : ( ) : (
<UriIndicator link uri={authorUri} /> <UriIndicator link external={livestream} uri={authorUri} />
)}
{!livestream && (
<Button
className="comment__time"
onClick={handleTimeClick}
label={<DateTime date={timePosted} timeAgo />}
/>
)} )}
<Button
className="comment__time"
onClick={handleTimeClick}
label={<DateTime date={timePosted} timeAgo />}
/>
{isPinned && ( {isPinned && (
<span className="comment__pin"> <span className="comment__pin">
@ -273,18 +285,20 @@ function Comment(props: Props) {
)} )}
</div> </div>
<div className="comment__actions"> {!livestream && (
{threadDepth !== 0 && ( <div className="comment__actions">
<Button {threadDepth !== 0 && (
requiresAuth={IS_WEB} <Button
label={commentingEnabled ? __('Reply') : __('Log in to reply')} requiresAuth={IS_WEB}
className="comment__action" label={commentingEnabled ? __('Reply') : __('Log in to reply')}
onClick={handleCommentReply} className="comment__action"
icon={ICONS.REPLY} onClick={handleCommentReply}
/> icon={ICONS.REPLY}
)} />
{ENABLE_COMMENT_REACTIONS && <CommentReactions uri={uri} commentId={commentId} />} )}
</div> {ENABLE_COMMENT_REACTIONS && <CommentReactions uri={uri} commentId={commentId} />}
</div>
)}
{isReplying && ( {isReplying && (
<CommentCreate <CommentCreate

View file

@ -4,8 +4,7 @@ import classnames from 'classnames';
import Card from 'component/common/card'; import Card from 'component/common/card';
import Spinner from 'component/spinner'; import Spinner from 'component/spinner';
import CommentCreate from 'component/commentCreate'; import CommentCreate from 'component/commentCreate';
import Button from 'component/button'; import CommentView from 'component/comment';
import MarkdownPreview from 'component/common/markdown-preview';
type Props = { type Props = {
uri: string, uri: string,
@ -114,20 +113,17 @@ export default function LivestreamFeed(props: Props) {
<div className="livestream__comments"> <div className="livestream__comments">
{comments.map((comment) => ( {comments.map((comment) => (
<div key={comment.comment_id} className={classnames('livestream__comment')}> <div key={comment.comment_id} className={classnames('livestream__comment')}>
{comment.channel_url ? ( <CommentView
<Button livestream
target="_blank" isTopLevel
className={classnames('livestream__comment-author', { uri={uri}
'livestream__comment-author--streamer': authorUri={comment.channel_url}
claim.signing_channel && claim.signing_channel.claim_id === comment.channel_id, author={comment.channel_name}
})} claimId={comment.claim_id}
navigate={comment.channel_url} commentId={comment.comment_id}
label={comment.channel_name} message={comment.comment}
/> timePosted={comment.timestamp * 1000}
) : ( />
<div className="livestream__comment-author">{comment.channel_name}</div>
)}
<MarkdownPreview content={comment.comment} simpleLinks />
</div> </div>
))} ))}
</div> </div>

View file

@ -17,6 +17,7 @@ type Props = {
// to allow for other elements to be nested within the UriIndicator // to allow for other elements to be nested within the UriIndicator
children: ?Node, children: ?Node,
inline: boolean, inline: boolean,
external?: boolean,
}; };
class UriIndicator extends React.PureComponent<Props> { class UriIndicator extends React.PureComponent<Props> {
@ -37,7 +38,7 @@ class UriIndicator extends React.PureComponent<Props> {
}; };
render() { render() {
const { link, isResolvingUri, claim, children, inline, hideAnonymous = false } = this.props; const { link, isResolvingUri, claim, children, inline, hideAnonymous = false, external = false } = this.props;
if (!claim) { if (!claim) {
return <span className="empty">{isResolvingUri ? 'Validating...' : 'Unused'}</span>; return <span className="empty">{isResolvingUri ? 'Validating...' : 'Unused'}</span>;
@ -74,10 +75,14 @@ class UriIndicator extends React.PureComponent<Props> {
} }
if (children) { if (children) {
return <Button navigate={channelLink}>{children}</Button>; return (
<Button target={external ? '_blank' : undefined} navigate={channelLink}>
{children}
</Button>
);
} else { } else {
return ( return (
<Button className="button--uri-indicator" navigate={channelLink}> <Button className="button--uri-indicator" navigate={channelLink} target={external ? '_blank' : undefined}>
{inner} {inner}
</Button> </Button>
); );

View file

@ -13,13 +13,13 @@ const NO_WALLET_ERROR = 'no wallet found for this user';
const BAD_PASSWORD_ERROR_NAME = 'InvalidPasswordError'; const BAD_PASSWORD_ERROR_NAME = 'InvalidPasswordError';
export function doSetDefaultAccount(success, failure) { export function doSetDefaultAccount(success, failure) {
return dispatch => { return (dispatch) => {
dispatch({ dispatch({
type: ACTIONS.SET_DEFAULT_ACCOUNT, type: ACTIONS.SET_DEFAULT_ACCOUNT,
}); });
Lbry.account_list() Lbry.account_list()
.then(accountList => { .then((accountList) => {
const { lbc_mainnet: accounts } = accountList; const { lbc_mainnet: accounts } = accountList;
let defaultId; let defaultId;
for (let i = 0; i < accounts.length; ++i) { for (let i = 0; i < accounts.length; ++i) {
@ -43,7 +43,7 @@ export function doSetDefaultAccount(success, failure) {
success(); success();
} }
}) })
.catch(err => { .catch((err) => {
if (failure) { if (failure) {
failure(err); failure(err);
} }
@ -53,7 +53,7 @@ export function doSetDefaultAccount(success, failure) {
failure('Could not set a default account'); // fail failure('Could not set a default account'); // fail
} }
}) })
.catch(err => { .catch((err) => {
if (failure) { if (failure) {
failure(err); failure(err);
} }
@ -62,13 +62,13 @@ export function doSetDefaultAccount(success, failure) {
} }
export function doSetSync(oldHash, newHash, data) { export function doSetSync(oldHash, newHash, data) {
return dispatch => { return (dispatch) => {
dispatch({ dispatch({
type: ACTIONS.SET_SYNC_STARTED, type: ACTIONS.SET_SYNC_STARTED,
}); });
return Lbryio.call('sync', 'set', { old_hash: oldHash, new_hash: newHash, data }, 'post') return Lbryio.call('sync', 'set', { old_hash: oldHash, new_hash: newHash, data }, 'post')
.then(response => { .then((response) => {
if (!response.hash) { if (!response.hash) {
throw Error('No hash returned for sync/set.'); throw Error('No hash returned for sync/set.');
} }
@ -78,7 +78,7 @@ export function doSetSync(oldHash, newHash, data) {
data: { syncHash: response.hash }, data: { syncHash: response.hash },
}); });
}) })
.catch(error => { .catch((error) => {
dispatch({ dispatch({
type: ACTIONS.SET_SYNC_FAILED, type: ACTIONS.SET_SYNC_FAILED,
data: { error }, data: { error },
@ -94,7 +94,7 @@ export const doGetSyncDesktop = (cb?, password) => (dispatch, getState) => {
const setSyncPending = selectSetSyncIsPending(state); const setSyncPending = selectSetSyncIsPending(state);
const syncLocked = selectSyncIsLocked(state); const syncLocked = selectSyncIsLocked(state);
return getSavedPassword().then(savedPassword => { return getSavedPassword().then((savedPassword) => {
const passwordArgument = password || password === '' ? password : savedPassword === null ? '' : savedPassword; const passwordArgument = password || password === '' ? password : savedPassword === null ? '' : savedPassword;
if (syncEnabled && !getSyncPending && !setSyncPending && !syncLocked) { if (syncEnabled && !getSyncPending && !setSyncPending && !syncLocked) {
@ -128,7 +128,7 @@ export function doSyncLoop(noInterval) {
} }
export function doSyncUnsubscribe() { export function doSyncUnsubscribe() {
return dispatch => { return (dispatch) => {
if (syncTimer) { if (syncTimer) {
clearInterval(syncTimer); clearInterval(syncTimer);
} }
@ -148,7 +148,7 @@ export function doGetSync(passedPassword, callback) {
} }
} }
return dispatch => { return (dispatch) => {
dispatch({ dispatch({
type: ACTIONS.GET_SYNC_STARTED, type: ACTIONS.GET_SYNC_STARTED,
}); });
@ -156,7 +156,7 @@ export function doGetSync(passedPassword, callback) {
const data = {}; const data = {};
Lbry.wallet_status() Lbry.wallet_status()
.then(status => { .then((status) => {
if (status.is_locked) { if (status.is_locked) {
return Lbry.wallet_unlock({ password }); return Lbry.wallet_unlock({ password });
} }
@ -164,15 +164,15 @@ export function doGetSync(passedPassword, callback) {
// Wallet is already unlocked // Wallet is already unlocked
return true; return true;
}) })
.then(isUnlocked => { .then((isUnlocked) => {
if (isUnlocked) { if (isUnlocked) {
return Lbry.sync_hash(); return Lbry.sync_hash();
} }
data.unlockFailed = true; data.unlockFailed = true;
throw new Error(); throw new Error();
}) })
.then(hash => Lbryio.call('sync', 'get', { hash }, 'post')) .then((hash) => Lbryio.call('sync', 'get', { hash }, 'post'))
.then(response => { .then((response) => {
const syncHash = response.hash; const syncHash = response.hash;
data.syncHash = syncHash; data.syncHash = syncHash;
data.syncData = response.data; data.syncData = response.data;
@ -183,7 +183,7 @@ export function doGetSync(passedPassword, callback) {
return Lbry.sync_apply({ password, data: response.data, blocking: true }); return Lbry.sync_apply({ password, data: response.data, blocking: true });
} }
}) })
.then(response => { .then((response) => {
if (!response) { if (!response) {
dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data }); dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data });
handleCallback(null, data.changed); handleCallback(null, data.changed);
@ -200,7 +200,7 @@ export function doGetSync(passedPassword, callback) {
dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data }); dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data });
handleCallback(null, data.changed); handleCallback(null, data.changed);
}) })
.catch(syncAttemptError => { .catch((syncAttemptError) => {
const badPasswordError = const badPasswordError =
syncAttemptError && syncAttemptError.data && syncAttemptError.data.name === BAD_PASSWORD_ERROR_NAME; syncAttemptError && syncAttemptError.data && syncAttemptError.data.name === BAD_PASSWORD_ERROR_NAME;
@ -248,7 +248,7 @@ export function doGetSync(passedPassword, callback) {
dispatch(doSetSync('', walletHash, syncApplyData, password)); dispatch(doSetSync('', walletHash, syncApplyData, password));
handleCallback(); handleCallback();
}) })
.catch(syncApplyError => { .catch((syncApplyError) => {
handleCallback(syncApplyError); handleCallback(syncApplyError);
}); });
} }
@ -258,7 +258,7 @@ export function doGetSync(passedPassword, callback) {
} }
export function doSyncApply(syncHash, syncData, password) { export function doSyncApply(syncHash, syncData, password) {
return dispatch => { return (dispatch) => {
dispatch({ dispatch({
type: ACTIONS.SYNC_APPLY_STARTED, type: ACTIONS.SYNC_APPLY_STARTED,
}); });
@ -286,14 +286,14 @@ export function doSyncApply(syncHash, syncData, password) {
} }
export function doCheckSync() { export function doCheckSync() {
return dispatch => { return (dispatch) => {
dispatch({ dispatch({
type: ACTIONS.GET_SYNC_STARTED, type: ACTIONS.GET_SYNC_STARTED,
}); });
Lbry.sync_hash().then(hash => { Lbry.sync_hash().then((hash) => {
Lbryio.call('sync', 'get', { hash }, 'post') Lbryio.call('sync', 'get', { hash }, 'post')
.then(response => { .then((response) => {
const data = { const data = {
hasSyncedWallet: true, hasSyncedWallet: true,
syncHash: response.hash, syncHash: response.hash,
@ -314,19 +314,19 @@ export function doCheckSync() {
} }
export function doResetSync() { export function doResetSync() {
return dispatch => return (dispatch) =>
new Promise(resolve => { new Promise((resolve) => {
dispatch({ type: ACTIONS.SYNC_RESET }); dispatch({ type: ACTIONS.SYNC_RESET });
resolve(); resolve();
}); });
} }
export function doSyncEncryptAndDecrypt(oldPassword, newPassword, encrypt) { export function doSyncEncryptAndDecrypt(oldPassword, newPassword, encrypt) {
return dispatch => { return (dispatch) => {
const data = {}; const data = {};
return Lbry.sync_hash() return Lbry.sync_hash()
.then(hash => Lbryio.call('sync', 'get', { hash }, 'post')) .then((hash) => Lbryio.call('sync', 'get', { hash }, 'post'))
.then(syncGetResponse => { .then((syncGetResponse) => {
data.oldHash = syncGetResponse.hash; data.oldHash = syncGetResponse.hash;
return Lbry.sync_apply({ password: oldPassword, data: syncGetResponse.data }); return Lbry.sync_apply({ password: oldPassword, data: syncGetResponse.data });
@ -339,7 +339,7 @@ export function doSyncEncryptAndDecrypt(oldPassword, newPassword, encrypt) {
} }
}) })
.then(() => Lbry.sync_apply({ password: newPassword })) .then(() => Lbry.sync_apply({ password: newPassword }))
.then(syncApplyResponse => { .then((syncApplyResponse) => {
if (syncApplyResponse.hash !== data.oldHash) { if (syncApplyResponse.hash !== data.oldHash) {
return dispatch(doSetSync(data.oldHash, syncApplyResponse.hash, syncApplyResponse.data)); return dispatch(doSetSync(data.oldHash, syncApplyResponse.hash, syncApplyResponse.data));
} }

View file

@ -105,12 +105,20 @@ export const makeSelectCommentIdsForUri = (uri: string) =>
export const makeSelectMyReactionsForComment = (commentId: string) => export const makeSelectMyReactionsForComment = (commentId: string) =>
createSelector(selectState, (state) => { createSelector(selectState, (state) => {
if (!state.myReactsByCommentId) {
return [];
}
return state.myReactsByCommentId[commentId] || []; return state.myReactsByCommentId[commentId] || [];
}); });
export const makeSelectOthersReactionsForComment = (commentId: string) => export const makeSelectOthersReactionsForComment = (commentId: string) =>
createSelector(selectState, (state) => { createSelector(selectState, (state) => {
return state.othersReactsByCommentId[commentId]; if (!state.othersReactsByCommentId) {
return {};
}
return state.othersReactsByCommentId[commentId] || {};
}); });
export const selectPendingCommentReacts = createSelector(selectState, (state) => state.pendingCommentReactions); export const selectPendingCommentReacts = createSelector(selectState, (state) => state.pendingCommentReactions);

View file

@ -34,6 +34,7 @@ $thumbnailWidthSmall: 1rem;
} }
.comment { .comment {
width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-size: var(--font-small); font-size: var(--font-small);
@ -101,6 +102,10 @@ $thumbnailWidthSmall: 1rem;
} }
} }
.comment--livestream {
margin-right: 0;
}
.comment--slimed { .comment--slimed {
opacity: 0.6; opacity: 0.6;
} }

View file

@ -48,10 +48,6 @@
margin-top: var(--spacing-s); margin-top: var(--spacing-s);
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
> :first-child {
margin-right: var(--spacing-s);
}
} }
.livestream__comment-author { .livestream__comment-author {