// @flow import React, { useState } from 'react'; import { parseURI } from 'lbry-redux'; 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 { formatLbryUriForWeb } from 'util/uri'; 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 * as ICONS from 'constants/icons'; import classnames from 'classnames'; import * as MODALS from 'constants/modal_types'; import { Form, FormField } from 'component/common/form'; import ClaimPreview from 'component/claimPreview'; import Icon from 'component/common/icon'; import HelpLink from 'component/common/help-link'; import debounce from 'util/debounce'; import { DEBOUNCE_WAIT_DURATION_MS } from 'constants/search'; const PAGE_VIEW_QUERY = `view`; const ABOUT_PAGE = `about`; const DISCUSSION_PAGE = `discussion`; const LIGHTHOUSE_URL = 'https://lighthouse.lbry.com/search'; 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, }; function ChannelPage(props: Props) { const { uri, title, cover, history, location, page, channelIsMine, thumbnail, claim, isSubscribed, channelIsBlocked, blackListedOutpoints, openModal, supportOption, 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); // passing callback to useState ensures it's only set on initial render. Without this, the debouncing wont work. const [performSearch] = useState(() => debounce(updateResults, DEBOUNCE_WAIT_DURATION_MS)); const claimId = claim.claim_id; // 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 = formatLbryUriForWeb(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 handleSearch() { performSearch(searchQuery); } function updateResults(query) { // In order to display original search results, search results must be set to null. A query of '' should display original results. if (query === '') return setSearchResults(null); const url = generateFetchUrl(query); getResults(url); } function generateFetchUrl(query) { return `${LIGHTHOUSE_URL}?s=${encodeURIComponent(query)}&channel_id=${encodeURIComponent(claim.claim_id)}`; } 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); }); } React.useEffect(() => { handleSearch(); }, [searchQuery]); function handleInputChange(e) { const { value } = e.target; setSearchQuery(value); } 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]); return (
{!channelIsBlocked && !channelIsBlackListed && } {!channelIsMine && (
{!editing && cover && ( )} {editing && } {/* component that offers select/upload */}
{!editing && ( )} {editing && ( )}

{title || '@' + channelName} {channelIsMine && !editing && (

{subCount} {subCount !== 1 ? __('Subscribers') : __('Subscriber')}
{__('Content')} {editing ? __('Editing Your Channel') : __('About')} {__('Discussion')} {/* only render searchbar on content page (tab index 0 === content page) */} {tabIndex === 0 ? (
) : (
)} {searchResults ? ( searchResults.map(url => ) ) : ( )} {editing ? ( setThumbPreview(v)} updateCover={v => setCoverPreview(v)} /> ) : ( )}
); } export default withRouter(ChannelPage);