lbry-desktop/ui/page/channel/view.jsx

230 lines
7.4 KiB
React
Raw Normal View History

2018-03-26 23:32:43 +02:00
// @flow
2020-02-12 20:10:35 +01:00
import * as ICONS from 'constants/icons';
2020-09-03 22:05:38 +02:00
import * as PAGES from 'constants/pages';
import React from 'react';
2020-12-15 19:54:35 +01:00
import { getThumbnailCdnUrl } from 'util/thumbnail';
2019-05-07 04:35:04 +02:00
import { parseURI } from 'lbry-redux';
2020-09-03 22:05:38 +02:00
import { YOUTUBE_STATUSES } from 'lbryinc';
2018-03-26 23:32:43 +02:00
import Page from 'component/page';
2019-05-07 04:35:04 +02:00
import SubscribeButton from 'component/subscribeButton';
import BlockButton from 'component/blockButton';
2019-05-07 04:35:04 +02:00
import ShareButton from 'component/shareButton';
import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs';
import { useHistory } from 'react-router';
import Button from 'component/button';
import { formatLbryUrlForWeb } from 'util/url';
2019-05-07 04:35:04 +02:00
import ChannelContent from 'component/channelContent';
import ChannelAbout from 'component/channelAbout';
import ChannelDiscussion from 'component/channelDiscussion';
2019-05-07 04:35:04 +02:00
import ChannelThumbnail from 'component/channelThumbnail';
2019-07-02 19:54:42 +02:00
import ChannelEdit from 'component/channelEdit';
import classnames from 'classnames';
2019-10-03 23:40:54 +02:00
import HelpLink from 'component/common/help-link';
2020-06-08 20:42:29 +02:00
import ClaimSupportButton from 'component/claimSupportButton';
import ChannelStakedIndicator from 'component/channelStakedIndicator';
2019-05-07 04:35:04 +02:00
export const PAGE_VIEW_QUERY = `view`;
2019-05-07 04:35:04 +02:00
const ABOUT_PAGE = `about`;
export const DISCUSSION_PAGE = `discussion`;
const EDIT_PAGE = 'edit';
2017-05-04 05:44:08 +02:00
2018-03-26 23:32:43 +02:00
type Props = {
uri: string,
2019-07-17 22:49:06 +02:00
claim: ChannelClaim,
2019-05-07 04:35:04 +02:00
title: ?string,
cover: ?string,
thumbnail: ?string,
page: number,
2019-05-07 04:35:04 +02:00
match: { params: { attribute: ?string } },
channelIsMine: boolean,
isSubscribed: boolean,
channelIsBlocked: boolean,
blackListedOutpoints: Array<{
txid: string,
nout: number,
}>,
fetchSubCount: (string) => void,
subCount: number,
2020-06-21 18:51:06 +02:00
pending: boolean,
2020-09-03 22:05:38 +02:00
youtubeChannels: ?Array<{ channel_claim_id: string, sync_status: string, transfer_state: string }>,
2018-03-26 23:32:43 +02:00
};
2019-03-28 17:53:13 +01:00
function ChannelPage(props: Props) {
const {
uri,
2020-06-29 21:54:07 +02:00
claim,
title,
cover,
page,
channelIsMine,
isSubscribed,
channelIsBlocked,
blackListedOutpoints,
fetchSubCount,
subCount,
2020-06-21 18:51:06 +02:00
pending,
2020-09-03 22:05:38 +02:00
youtubeChannels,
} = props;
const {
push,
goBack,
location: { search },
} = useHistory();
2019-03-28 17:53:13 +01:00
const urlParams = new URLSearchParams(search);
2019-05-07 04:35:04 +02:00
const currentView = urlParams.get(PAGE_VIEW_QUERY) || undefined;
const [discussionWasMounted, setDiscussionWasMounted] = React.useState(false);
const editing = urlParams.get(PAGE_VIEW_QUERY) === EDIT_PAGE;
const { channelName } = parseURI(uri);
2019-07-17 22:49:06 +02:00
const { permanent_url: permanentUrl } = claim;
2019-09-27 05:52:04 +02:00
const claimId = claim.claim_id;
2020-05-25 18:19:15 +02:00
const formattedSubCount = Number(subCount).toLocaleString();
2020-09-03 22:05:38 +02:00
const isMyYouTubeChannel =
claim &&
youtubeChannels &&
youtubeChannels.some(({ channel_claim_id, sync_status, transfer_state }) => {
if (
channel_claim_id === claim.claim_id &&
sync_status !== YOUTUBE_STATUSES.YOUTUBE_SYNC_ABANDONDED &&
transfer_state !== YOUTUBE_STATUSES.YOUTUBE_SYNC_COMPLETED_TRANSFER
) {
return true;
}
});
let channelIsBlackListed = false;
if (claim && blackListedOutpoints) {
channelIsBlackListed = blackListedOutpoints.some(
(outpoint) => outpoint.txid === claim.txid && outpoint.nout === claim.nout
);
}
2019-05-07 04:35:04 +02:00
// 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;
2019-09-16 07:32:02 +02:00
function onTabChange(newTabIndex) {
let url = formatLbryUrlForWeb(uri);
let search = '?';
if (newTabIndex === 0) {
search += `page=${page}`;
} else if (newTabIndex === 1) {
search += `${PAGE_VIEW_QUERY}=${ABOUT_PAGE}`;
} else {
search += `${PAGE_VIEW_QUERY}=${DISCUSSION_PAGE}`;
2017-08-24 23:12:23 +02:00
}
push(`${url}${search}`);
2019-09-16 07:32:02 +02:00
}
React.useEffect(() => {
if (currentView === DISCUSSION_PAGE) {
setDiscussionWasMounted(true);
}
}, [currentView]);
React.useEffect(() => {
2019-09-27 05:52:04 +02:00
fetchSubCount(claimId);
}, [uri, fetchSubCount, claimId]);
2020-06-29 21:54:07 +02:00
if (editing) {
return (
<Page
noFooter
noSideNavigation={editing}
backout={{
title: __('Editing @%channel%', { channel: channelName }),
simpleTitle: __('Editing'),
}}
2020-06-29 21:54:07 +02:00
>
<ChannelEdit uri={uri} onDone={() => goBack()} />
2020-06-29 21:54:07 +02:00
</Page>
);
}
2019-03-28 17:53:13 +01:00
return (
2020-05-15 16:19:37 +02:00
<Page noFooter>
2020-01-03 17:36:15 +01:00
<header className="channel-cover">
<div className="channel__quick-actions">
2020-09-03 22:05:38 +02:00
{isMyYouTubeChannel && (
<Button
button="alt"
label={__('Claim Your Channel')}
icon={ICONS.YOUTUBE}
navigate={`/$/${PAGES.CHANNELS}`}
/>
)}
2020-03-27 19:57:03 +01:00
{!channelIsBlocked && !channelIsBlackListed && <ShareButton uri={uri} />}
2020-06-08 20:42:29 +02:00
{!channelIsBlocked && <ClaimSupportButton uri={uri} />}
2020-01-03 17:36:15 +01:00
{!channelIsBlocked && (!channelIsBlackListed || isSubscribed) && <SubscribeButton uri={permanentUrl} />}
{!isSubscribed && <BlockButton uri={permanentUrl} />}
</div>
2020-06-29 21:54:07 +02:00
{cover && (
2020-01-03 17:36:15 +01:00
<img
className={classnames('channel-cover__custom', { 'channel__image--blurred': channelIsBlocked })}
2020-12-15 19:54:35 +01:00
src={IS_WEB ? getThumbnailCdnUrl({ thumbnail: cover, height: 200, width: 1000, quality: 100 }) : cover}
2020-01-03 17:36:15 +01:00
/>
)}
<div className="channel__primary-info">
2020-06-29 21:54:07 +02:00
<ChannelThumbnail
className="channel__thumbnail--channel-page"
uri={uri}
obscure={channelIsBlocked}
allowGifs
hideStakedIndicator
2020-06-29 21:54:07 +02:00
/>
<h1 className="channel__title">
{title || '@' + channelName}
<ChannelStakedIndicator uri={uri} large />
</h1>
2020-01-03 17:36:15 +01:00
<div className="channel__meta">
<span>
2020-05-25 18:19:15 +02:00
{formattedSubCount} {subCount !== 1 ? __('Followers') : __('Follower')}
2020-01-03 17:36:15 +01:00
<HelpLink href="https://lbry.com/faq/views" />
</span>
2020-06-29 21:54:07 +02:00
{channelIsMine && (
2020-06-21 18:51:06 +02:00
<>
{pending ? (
<span>{__('Your changes will be live in a few minutes')}</span>
) : (
<Button
button="alt"
title={__('Edit')}
onClick={() => push(`?${PAGE_VIEW_QUERY}=${EDIT_PAGE}`)}
2020-06-21 18:51:06 +02:00
icon={ICONS.EDIT}
iconSize={18}
disabled={pending}
/>
)}
</>
)}
2020-01-03 17:36:15 +01:00
</div>
</div>
<div className="channel-cover__gradient" />
2020-01-03 17:36:15 +01:00
</header>
2020-01-03 17:36:15 +01:00
<Tabs onChange={onTabChange} index={tabIndex}>
<TabList className="tabs__list--channel-page">
<Tab disabled={editing}>{__('Content')}</Tab>
Support for multiple string context + "About" as initial example. ## Issue 4796 - i18n: Allow support for string overloading (multiple contexts) ## Approach - Minimal code and process change. - Handle on a case-by-case basis when reported by translators. - Split the affected key in the string json by appending the context. - Translators need to be aware of the new format and not translate context itself. Code is added to detect bad translations and will revert to English. Sample in json: "About --[About section in Help Page]--": "About", "About --[tab title in Channel Page]--": "About", Sample in client code: title={__('About --[About section in Help Page]--')} - "--[ ]--" was chosen as it's unique enough (unlikely for real strings to use it) and hopefully not that distracting in the client code. - In the key itself, spaces are allowed after the string (i.e. before '--[') for neatness. It will be trimmed by the system. ## First example "About" is used in 3 places: - Channel Page - Help Page - Footer (in Odysee branch) For Russian, the word "About" is "O" and is usually not used standalone, but requires something behind it. A translator said so, and seems to be the case in other sites as well. "O xxx" "O yyy" ## Other languages For other languages that are not impacted, they can just clone the same translation for each of the split keys, just like in English. ## Possible enhancement in Transifex I see that Transifex's API includes a `context` entry. It might be possible to move the context-metadata there during the upload, so translators will never see the "--[]--" messiness (it will be shown as "Context: xxx" in the Transifex GUI). I'm not sure how to test the Transifex side, so I did not investigate further.
2020-10-09 07:38:03 +02:00
<Tab>{editing ? __('Editing Your Channel') : __('About --[tab title in Channel Page]--')}</Tab>
<Tab disabled={editing}>{__('Community')}</Tab>
2020-01-03 17:36:15 +01:00
</TabList>
<TabPanels>
<TabPanel>
<ChannelContent uri={uri} channelIsBlackListed={channelIsBlackListed} />
2020-01-03 17:36:15 +01:00
</TabPanel>
<TabPanel>
2020-06-29 21:54:07 +02:00
<ChannelAbout uri={uri} />
2020-01-03 17:36:15 +01:00
</TabPanel>
<TabPanel>
{(discussionWasMounted || currentView === DISCUSSION_PAGE) && <ChannelDiscussion uri={uri} />}
2020-01-03 17:36:15 +01:00
</TabPanel>
</TabPanels>
</Tabs>
2019-03-28 17:53:13 +01:00
</Page>
);
2017-05-04 05:44:08 +02:00
}
export default ChannelPage;