From 7dc44194f95247fda4ddd6e39fda4fabe2d6923a Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Wed, 10 Mar 2021 13:34:21 -0500 Subject: [PATCH] bring in livestream changes from odysee --- package.json | 1 + ui/component/commentCreate/index.js | 12 +- ui/component/commentCreate/view.jsx | 47 +++++- ui/component/livestreamComments/index.js | 14 ++ ui/component/livestreamComments/view.jsx | 129 +++++++++++++++ ui/component/livestreamLayout/index.js | 9 ++ ui/component/livestreamLayout/view.jsx | 34 ++++ ui/component/livestreamLink/index.js | 9 ++ ui/component/livestreamLink/view.jsx | 68 ++++++++ ui/component/page/view.jsx | 5 +- ui/constants/action_types.js | 1 + ui/constants/livestream.js | 4 + ui/page/livestream/index.js | 2 +- ui/page/livestreamStream/index.js | 18 +++ ui/page/livestreamStream/view.jsx | 114 +++++++++++++ ui/page/show/index.js | 8 +- ui/page/show/view.jsx | 7 + ui/redux/actions/app.js | 44 ++--- ui/redux/actions/websocket.js | 61 ++++--- ui/scss/all.scss | 1 + ui/scss/component/_button.scss | 5 + ui/scss/component/_livestream.scss | 198 +++++++++++++++++++++++ ui/scss/component/_main.scss | 4 + yarn.lock | 59 +++++++ 24 files changed, 804 insertions(+), 50 deletions(-) create mode 100644 ui/component/livestreamComments/index.js create mode 100644 ui/component/livestreamComments/view.jsx create mode 100644 ui/component/livestreamLayout/index.js create mode 100644 ui/component/livestreamLayout/view.jsx create mode 100644 ui/component/livestreamLink/index.js create mode 100644 ui/component/livestreamLink/view.jsx create mode 100644 ui/constants/livestream.js create mode 100644 ui/page/livestreamStream/index.js create mode 100644 ui/page/livestreamStream/view.jsx create mode 100644 ui/scss/component/_livestream.scss diff --git a/package.json b/package.json index 0b5d72490..7af6adf43 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ "electron-is-dev": "^0.3.0", "electron-webpack": "^2.8.2", "electron-window-state": "^4.1.1", + "emoji-dictionary": "^1.0.11", "eslint": "^5.15.2", "eslint-config-prettier": "^2.9.0", "eslint-config-standard": "^12.0.0", diff --git a/ui/component/commentCreate/index.js b/ui/component/commentCreate/index.js index 6644cae2a..2f69ff259 100644 --- a/ui/component/commentCreate/index.js +++ b/ui/component/commentCreate/index.js @@ -1,10 +1,16 @@ import { connect } from 'react-redux'; -import { makeSelectClaimForUri, selectMyChannelClaims, selectFetchingMyChannels } from 'lbry-redux'; +import { + makeSelectClaimForUri, + makeSelectClaimIsMine, + selectMyChannelClaims, + selectFetchingMyChannels, +} from 'lbry-redux'; import { selectIsPostingComment } from 'redux/selectors/comments'; import { doOpenModal, doSetActiveChannel } from 'redux/actions/app'; import { doCommentCreate } from 'redux/actions/comments'; import { selectUserVerifiedEmail } from 'redux/selectors/user'; import { selectActiveChannelClaim } from 'redux/selectors/app'; +import { doToast } from 'redux/actions/notifications'; import { CommentCreate } from './view'; const select = (state, props) => ({ @@ -14,12 +20,14 @@ const select = (state, props) => ({ isFetchingChannels: selectFetchingMyChannels(state), isPostingComment: selectIsPostingComment(state), activeChannelClaim: selectActiveChannelClaim(state), + claimIsMine: makeSelectClaimIsMine(props.uri)(state), }); const perform = (dispatch, ownProps) => ({ createComment: (comment, claimId, parentId) => dispatch(doCommentCreate(comment, claimId, parentId, ownProps.uri)), openModal: (modal, props) => dispatch(doOpenModal(modal, props)), - setActiveChannel: claimId => dispatch(doSetActiveChannel(claimId)), + setActiveChannel: (claimId) => dispatch(doSetActiveChannel(claimId)), + toast: (message) => dispatch(doToast({ message, isError: true })), }); export default connect(select, perform)(CommentCreate); diff --git a/ui/component/commentCreate/view.jsx b/ui/component/commentCreate/view.jsx index 9b68e2d8e..814b7f71e 100644 --- a/ui/component/commentCreate/view.jsx +++ b/ui/component/commentCreate/view.jsx @@ -10,6 +10,16 @@ import usePersistedState from 'effects/use-persisted-state'; import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field'; import { useHistory } from 'react-router'; import type { ElementRef } from 'react'; +import emoji from 'emoji-dictionary'; + +const COMMENT_SLOW_MODE_SECONDS = 5; +const LIVESTREAM_EMOJIS = [ + emoji.getUnicode('rocket'), + emoji.getUnicode('jeans'), + emoji.getUnicode('fire'), + emoji.getUnicode('heart'), + emoji.getUnicode('open_mouth'), +]; type Props = { uri: string, @@ -25,6 +35,9 @@ type Props = { isPostingComment: boolean, activeChannel: string, activeChannelClaim: ?ChannelClaim, + livestream?: boolean, + toast: (string) => void, + claimIsMine: boolean, }; export function CommentCreate(props: Props) { @@ -40,11 +53,15 @@ export function CommentCreate(props: Props) { parentId, isPostingComment, activeChannelClaim, + livestream, + toast, + claimIsMine, } = props; const buttonref: ElementRef = React.useRef(); const { push } = useHistory(); const { claim_id: claimId } = claim; const [commentValue, setCommentValue] = React.useState(''); + const [lastCommentTime, setLastCommentTime] = React.useState(); const [charCount, setCharCount] = useState(commentValue.length); const [advancedEditor, setAdvancedEditor] = usePersistedState('comment-editor-mode', false); const hasChannels = channels && channels.length; @@ -79,7 +96,18 @@ export function CommentCreate(props: Props) { function handleSubmit() { if (activeChannelClaim && commentValue.length) { - createComment(commentValue, claimId, parentId).then(res => { + const timeUntilCanComment = !lastCommentTime + ? 0 + : lastCommentTime / 1000 - Date.now() / 1000 + COMMENT_SLOW_MODE_SECONDS; + + if (livestream && !claimIsMine && timeUntilCanComment > 0) { + toast( + __('Slowmode is on. You can comment again in %time% seconds.', { time: Math.floor(timeUntilCanComment) }) + ); + return; + } + + createComment(commentValue, claimId, parentId).then((res) => { if (res && res.signature) { setCommentValue(''); @@ -144,6 +172,23 @@ export function CommentCreate(props: Props) { autoFocus={isReply} textAreaMaxLength={FF_MAX_CHARS_IN_COMMENT} /> + {livestream && hasChannels && ( +
+ {LIVESTREAM_EMOJIS.map((emoji) => ( +
+ )}
+ ))} + + ) : ( +
+ )} +
+ +
+ +
+ + } + /> + ); +} diff --git a/ui/component/livestreamLayout/index.js b/ui/component/livestreamLayout/index.js new file mode 100644 index 000000000..eee30cee8 --- /dev/null +++ b/ui/component/livestreamLayout/index.js @@ -0,0 +1,9 @@ +import { connect } from 'react-redux'; +import { makeSelectClaimForUri } from 'lbry-redux'; +import LivestreamLayout from './view'; + +const select = (state, props) => ({ + claim: makeSelectClaimForUri(props.uri)(state), +}); + +export default connect(select)(LivestreamLayout); diff --git a/ui/component/livestreamLayout/view.jsx b/ui/component/livestreamLayout/view.jsx new file mode 100644 index 000000000..8ac8e6dbf --- /dev/null +++ b/ui/component/livestreamLayout/view.jsx @@ -0,0 +1,34 @@ +// @flow +import { BITWAVE_EMBED_URL } from 'constants/livestream'; +import React from 'react'; +import FileTitle from 'component/fileTitle'; +import LivestreamComments from 'component/livestreamComments'; + +type Props = { + uri: string, + claim: ?StreamClaim, + activeViewers: number, +}; + +export default function LivestreamLayout(props: Props) { + const { claim, uri, activeViewers } = props; + + if (!claim) { + return null; + } + + return ( + <> +
+
+
+