// @flow import * as ICONS from 'constants/icons'; import * as MODALS from 'constants/modal_types'; import React, { useState, useEffect } from 'react'; import { parseURI } from 'lbry-redux'; import { Lbryio } from 'lbryinc'; import Page from 'component/page'; import SubscribeButton from 'component/subscribeButton'; import BlockButton from 'component/blockButton'; import ShareButton from 'component/shareButton'; import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs'; import { withRouter } from 'react-router'; import Button from 'component/button'; import { formatLbryUrlForWeb } from 'util/url'; import ChannelContent from 'component/channelContent'; import ChannelAbout from 'component/channelAbout'; import ChannelDiscussion from 'component/channelDiscussion'; import ChannelThumbnail from 'component/channelThumbnail'; import ChannelEdit from 'component/channelEdit'; import ClaimUri from 'component/claimUri'; import classnames from 'classnames'; import { Form, FormField } from 'component/common/form'; import Icon from 'component/common/icon'; import HelpLink from 'component/common/help-link'; import { DEBOUNCE_WAIT_DURATION_MS } from 'constants/search'; import ClaimList from 'component/claimList'; import DateTime from 'component/dateTime'; const PAGE_VIEW_QUERY = `view`; const ABOUT_PAGE = `about`; const DISCUSSION_PAGE = `discussion`; const LIGHTHOUSE_URL = 'https://lighthouse.lbry.com/search'; const ARROW_LEFT_KEYCODE = 37; const ARROW_RIGHT_KEYCODE = 39; type Props = { uri: string, claim: ChannelClaim, title: ?string, cover: ?string, thumbnail: ?string, page: number, location: { search: string }, history: { push: string => void }, match: { params: { attribute: ?string } }, channelIsMine: boolean, isSubscribed: boolean, channelIsBlocked: boolean, blackListedOutpoints: Array<{ txid: string, nout: number, }>, openModal: (id: string, { uri: string, claimIsMine?: boolean, isSupport?: boolean }) => void, supportOption: boolean, fetchSubCount: string => void, subCount: number, showMature: boolean, }; function ChannelPage(props: Props) { const { uri, title, cover, history, location, page, channelIsMine, thumbnail, claim, isSubscribed, channelIsBlocked, blackListedOutpoints, openModal, supportOption, showMature, fetchSubCount, subCount, } = props; const { channelName } = parseURI(uri); const { search } = location; const urlParams = new URLSearchParams(search); const currentView = urlParams.get(PAGE_VIEW_QUERY) || undefined; const { permanent_url: permanentUrl } = claim; const [editing, setEditing] = useState(false); const [thumbPreview, setThumbPreview] = useState(thumbnail); const [coverPreview, setCoverPreview] = useState(cover); const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState(undefined); const [lastYtSyncDate, setLastYtSyncDate] = useState(); const claimId = claim.claim_id; const formattedSubCount = Number(subCount).toLocaleString(); // If a user changes tabs, update the url so it stays on the same page if they refresh. // We don't want to use links here because we can't animate the tab change and using links // would alter the Tab label's role attribute, which should stay role="tab" to work with keyboards/screen readers. const tabIndex = currentView === ABOUT_PAGE || editing ? 1 : currentView === DISCUSSION_PAGE ? 2 : 0; function onTabChange(newTabIndex) { let url = formatLbryUrlForWeb(uri); let search = '?'; if (newTabIndex === 0) { setSearchResults(null); search += `page=${page}`; } else if (newTabIndex === 1) { search += `${PAGE_VIEW_QUERY}=${ABOUT_PAGE}`; } else { search += `${PAGE_VIEW_QUERY}=${DISCUSSION_PAGE}`; } history.push(`${url}${search}`); } function getResults(fetchUrl) { fetch(fetchUrl) .then(res => res.json()) .then(results => { const urls = results.map(({ name, claimId }) => { return `lbry://${name}#${claimId}`; }); setSearchResults(urls); }) .catch(() => { setSearchResults(null); }); } useEffect(() => { const timer = setTimeout(() => { if (searchQuery === '') { // In order to display original search results, search results must be set to null. A query of '' should display original results. return setSearchResults(null); } else { getResults( `${LIGHTHOUSE_URL}?s=${encodeURIComponent(searchQuery)}&channel_id=${encodeURIComponent(claimId)}${ !showMature ? '&nsfw=false' : '' }` ); } }, DEBOUNCE_WAIT_DURATION_MS); return () => clearTimeout(timer); }, [claimId, searchQuery]); useEffect(() => { Lbryio.call('yt', 'get_youtuber', { channel_claim_id: claimId }).then(response => { if (response.is_verified_youtuber) { setLastYtSyncDate(response.last_synced); } }); }, [claimId]); function handleInputChange(e) { const { value } = e.target; setSearchQuery(value); } /* Since the search is inside of TabList, the left and right arrow keys change the tabIndex. This results in the user not being able to navigate the search string by using arrow keys. This function allows the event to change cursor position and then stops propagation to prevent tab changing. */ function handleSearchArrowKeys(e) { if (e.keyCode === ARROW_LEFT_KEYCODE || e.keyCode === ARROW_RIGHT_KEYCODE) { e.stopPropagation(); } } let channelIsBlackListed = false; if (claim && blackListedOutpoints) { channelIsBlackListed = blackListedOutpoints.some( outpoint => outpoint.txid === claim.txid && outpoint.nout === claim.nout ); } React.useEffect(() => { setSearchResults(null); setSearchQuery(''); fetchSubCount(claimId); }, [uri, fetchSubCount, claimId]); React.useEffect(() => { if (!channelIsMine && editing) { setEditing(false); } }, [channelIsMine, editing]); return ( {lastYtSyncDate && (
{__('Official YouTube Creator - Last updated %time_ago%', { time_ago: DateTime.getTimeAgoStr(lastYtSyncDate), })}
)}
{!channelIsBlocked && !channelIsBlackListed && } {!channelIsMine && (
{!editing && cover && ( )} {editing && } {/* component that offers select/upload */}
{!editing && ( )} {editing && ( )}

{title || '@' + channelName}

{formattedSubCount} {subCount !== 1 ? __('Followers') : __('Follower')} {channelIsMine && !editing && (
{__('Content')} {editing ? __('Editing Your Channel') : __('About')} {__('Comments')} {/* only render searchbar on content page (tab index 0 === content page) */} {tabIndex === 0 ? (
{}} className="wunderbar--inline"> ) : (
)} {searchResults ? ( ) : ( )} {editing ? ( setThumbPreview(v)} updateCover={v => setCoverPreview(v)} /> ) : ( )} ); } export default withRouter(ChannelPage);