// @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 { 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 * 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'; 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, }; 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 = 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 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); } /* 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 (
{!channelIsBlocked && !channelIsBlackListed && } {!channelIsMine && (
{!editing && cover && ( )} {editing && } {/* component that offers select/upload */}
{!editing && ( )} {editing && ( )}

{title || '@' + channelName}

{channelIsMine && !editing && (
{__('Content')} {editing ? __('Editing Your Channel') : __('About')} {__('Comments')} {/* 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);