Compare commits

...

18 commits

Author SHA1 Message Date
DispatchCommit
69ebf47cb5 fix scrolling issue, limit length of chat history 2021-04-04 02:53:58 -07:00
jessopb
1a7fdbb65c
improve pending livestream dashboard (#5810) 2021-04-01 16:38:38 -04:00
jessopb
aec44841a4
do not get stuck on publishing spinner (#5806) 2021-03-31 18:32:48 -04:00
Simão Gomes Viana
15d3d0a5ca
page: buy: filter out deleted countries (#5791)
There are, however, still countries that shouldn't be in the list.

Fixes: #5397
Test: Manual
2021-03-31 18:32:23 -04:00
jessopb
f727b8b7c6
change open.lbry.com code to 301 (#5804) 2021-03-31 17:16:19 -04:00
jessopb
58ec6a739c
use showmature selector (#5802) 2021-03-31 16:17:38 -04:00
Sean Yesmunt
a69875fa88 pin samtime video 2021-03-30 22:10:29 -04:00
zeppi
bb5c444f63 update meme to link 2021-03-30 14:01:21 -04:00
Julian Chandra
48f0c5e413
updating tagline
updating tagline
2021-03-30 10:34:50 -07:00
zeppi
256bf308b4 redux bump 2021-03-30 12:46:32 -04:00
zeppi
f8199d763c hide when no livestream claims 2021-03-30 12:29:28 -04:00
zeppi
dd01ad2cb2 remove redundant style 2021-03-29 23:24:43 -04:00
zeppi
84f993200b split, dedup pending and ready livestream uploads 2021-03-29 23:07:54 -04:00
zeppi
441f13979d final please 2021-03-29 22:40:54 -04:00
zeppi
6b705c995a temp-bump 2021-03-29 17:50:31 -04:00
zeppi
bd0fe4718f redirect on publish/edit 2021-03-29 17:42:07 -04:00
zeppi
f092e8cb7b fix isLivestream
channelContent - live

only poll streaming if has livestream claim

check channel for livestream claims every minute

update consts

poll livestream claims on setup page

do not poll livestream claims if live

smoother loading

unmerged redux bump
2021-03-29 01:15:27 -04:00
Sean Yesmunt
2e87b2fd22 odysee
Naomi

comment websockets

increase slow mode time to 5 seconds

fix to prevent duplicate comments

update livestream details

fix channel

pin electron boom

fix rebase

prune unused icons

updating meme

updating meme

update livestream for naomi

fix rebase

DigitalCashNetwork

remove electroboom pin

Slavguns

Joel

So he can edit his claims

add streamTypes param to claimTilesDiscover so following section can search for all types of content

fix typo

update meme

fixes

publish page fixes

pending

fix notifications

fix comments finally

fix claim preview

no mature for simplesite

Revert "no mature for simplesite"

This reverts commit 9f89242d85.

fix livestream preview click

no mature on simple site

try fixing invite page crash

probably needs more changes.
2021-03-26 18:47:28 -04:00
136 changed files with 3347 additions and 1306 deletions

View file

@ -4,12 +4,12 @@ declare type HomepageObject = {
options: any,
route: string,
title: string,
}
};
declare type HomepageData = {
[string]: HomepageObject,
default: any => any,
}
};
declare type RowDataItem = {
title: any,
@ -19,6 +19,7 @@ declare type RowDataItem = {
extra?: any,
options?: {
channelIds?: Array<string>,
limitClaimsPerChannel?: number,
pageSize: number,
},
route?: string,

File diff suppressed because it is too large Load diff

View file

@ -142,7 +142,7 @@
"imagesloaded": "^4.1.4",
"json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git",
"lbry-redux": "lbryio/lbry-redux#c494c92505cd531048de20bc37dc4d192b6ace01",
"lbry-redux": "lbryio/lbry-redux#9a1701372829c21cefbedacfc6033aec4dc7fa3c",
"lbryinc": "lbryio/lbryinc#7faea40d87b78ec91b901c62f501499dc4737025",
"lint-staged": "^7.0.2",
"localforage": "^1.7.1",

View file

@ -858,7 +858,6 @@
"Tag selection": "Tag selection",
"Select some tags to help us show you interesting things.": "Select some tags to help us show you interesting things.",
"You are currently following %followingCount% tags": "You are currently following %followingCount% tags",
"You are currently following %followingCount% tag": "You are currently following %followingCount% tag",
"Back": "Back",
"Experimental Transcoding": "Experimental Transcoding",
"A Folder containing FFmpeg": "A Folder containing FFmpeg",
@ -1417,6 +1416,7 @@
"Never fear though, there are tons of ways to earn %lbc%. You can earn or purchase %lbc%, or you can have your friends send you some.": "Never fear though, there are tons of ways to earn %lbc%. You can earn or purchase %lbc%, or you can have your friends send you some.",
"gaming, comedy, educational": "gaming, comedy, educational",
"A channel is required to comment on %SITE_NAME%": "A channel is required to comment on %SITE_NAME%",
"A channel is required to comment on lbry.tv": "A channel is required to comment on lbry.tv",
"A welcome bonus and thank you for your contribution to content freedom.": "A welcome bonus and thank you for your contribution to content freedom.",
"You earned %lbc% new user reward.": "You earned %lbc% LBC new user reward.",
"Just press play on something. Anything at all. Yep, even that one. Everybody's doing it.": "Just press play on something. Anything at all. Yep, even that one. Everybody's doing it.",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
static/img/lbry-favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -26,10 +26,10 @@ if (isProduction) {
// @endif
type Analytics = {
error: string => Promise<any>,
error: (string) => Promise<any>,
sentryError: ({} | string, {}) => Promise<any>,
pageView: (string, ?string) => void,
setUser: Object => void,
setUser: (Object) => void,
toggleInternal: (boolean, ?boolean) => void,
apiLogView: (string, string, string, ?number, ?() => void) => Promise<any>,
apiLogPublish: (ChannelClaim | StreamClaim) => void,
@ -39,6 +39,9 @@ type Analytics = {
playerStartedEvent: (?boolean) => void,
videoFetchDuration: (string, number) => void,
videoStartEvent: (string, number) => void,
adsFetchedEvent: () => void,
adsReceivedEvent: (any) => void,
adsErrorEvent: (any) => void,
videoBufferEvent: (
StreamClaim,
{
@ -55,9 +58,9 @@ type Analytics = {
emailVerifiedEvent: () => void,
rewardEligibleEvent: () => void,
startupEvent: () => void,
purchaseEvent: number => void,
readyEvent: number => void,
openUrlEvent: string => void,
purchaseEvent: (number) => void,
readyEvent: (number) => void,
openUrlEvent: (string) => void,
};
type LogPublishParams = {
@ -75,8 +78,8 @@ if (window.localStorage.getItem(SHARE_INTERNAL) === 'true') internalAnalyticsEna
// @endif
const analytics: Analytics = {
error: message => {
return new Promise(resolve => {
error: (message) => {
return new Promise((resolve) => {
if (internalAnalyticsEnabled && isProduction) {
return Lbryio.call('event', 'desktop_error', { error_message: message }).then(() => {
resolve(true);
@ -87,9 +90,9 @@ const analytics: Analytics = {
});
},
sentryError: (error, errorInfo) => {
return new Promise(resolve => {
return new Promise((resolve) => {
if (internalAnalyticsEnabled && isProduction) {
Sentry.withScope(scope => {
Sentry.withScope((scope) => {
scope.setExtras(errorInfo);
const eventId = Sentry.captureException(error);
resolve(eventId);
@ -114,7 +117,7 @@ const analytics: Analytics = {
MatomoInstance.trackPageView(params);
}
},
setUser: userId => {
setUser: (userId) => {
if (internalAnalyticsEnabled && userId) {
window._paq.push(['setUserId', String(userId)]);
// @if TARGET='app'
@ -188,7 +191,7 @@ const analytics: Analytics = {
}
},
apiSyncTags: params => {
apiSyncTags: (params) => {
if (internalAnalyticsEnabled && isProduction) {
Lbryio.call('content_tags', 'sync', params);
}
@ -238,12 +241,21 @@ const analytics: Analytics = {
});
}
},
playerLoadedEvent: embedded => {
playerLoadedEvent: (embedded) => {
sendMatomoEvent('Player', 'Loaded', embedded ? 'embedded' : 'onsite');
},
playerStartedEvent: embedded => {
playerStartedEvent: (embedded) => {
sendMatomoEvent('Player', 'Started', embedded ? 'embedded' : 'onsite');
},
adsFetchedEvent: () => {
sendMatomoEvent('Media', 'AdsFetched');
},
adsReceivedEvent: (response) => {
sendMatomoEvent('Media', 'AdsReceived', JSON.stringify(response));
},
adsErrorEvent: (response) => {
sendMatomoEvent('Media', 'AdsError', JSON.stringify(response));
},
tagFollowEvent: (tag, following) => {
sendMatomoEvent('Tag', following ? 'Tag-Follow' : 'Tag-Unfollow', tag);
},
@ -316,7 +328,7 @@ analytics.pageView(
// Listen for url changes and report
// This will include search queries
history.listen(location => {
history.listen((location) => {
const { pathname, search } = location;
const page = `${pathname}${search}`;

View file

@ -399,12 +399,12 @@ function App(props: Props) {
{/* @if TARGET='web' */}
<YoutubeWelcome />
{!shouldHideNag && <OpenInAppLink uri={uri} />}
{false && !shouldHideNag && <OpenInAppLink uri={uri} />}
{!shouldHideNag && <NagContinueFirstRun />}
{(lbryTvApiStatus === STATUS_DEGRADED || lbryTvApiStatus === STATUS_FAILING) && !shouldHideNag && (
<NagDegradedPerformance onClose={() => setLbryTvApiStatus(STATUS_OK)} />
)}
{lbryTvApiStatus === STATUS_OK && showAnalyticsNag && !shouldHideNag && (
{false && lbryTvApiStatus === STATUS_OK && showAnalyticsNag && !shouldHideNag && (
<NagDataCollection onClose={handleAnalyticsDismiss} />
)}
{/* @endif */}

View file

@ -12,7 +12,7 @@ import {
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
import { withRouter } from 'react-router';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { makeSelectClientSetting, selectShowMatureContent } from 'redux/selectors/settings';
import ChannelPage from './view';
@ -28,7 +28,7 @@ const select = (state, props) => {
channelIsBlocked: makeSelectChannelIsMuted(props.uri)(state),
claim: props.uri && makeSelectClaimForUri(props.uri)(state),
isAuthenticated: selectUserVerifiedEmail(state),
showMature: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
showMature: selectShowMatureContent(state),
tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state),
};
};

View file

@ -132,10 +132,12 @@ function ChannelContent(props: Props) {
{claim && claimsInChannel > 0 ? (
<ClaimListDiscover
hideLivestreamClaims
showHiddenByUser={viewHiddenChannels}
forceShowReposts
tileLayout={tileLayout}
uris={searchResults}
streamType={CS.CONTENT_ALL}
channelIds={[claim.claim_id]}
feeAmount={CS.FEE_AMOUNT_ANY}
defaultOrderBy={CS.ORDER_BY_NEW}

View file

@ -20,6 +20,7 @@ import analytics from 'analytics';
import LbcSymbol from 'component/common/lbc-symbol';
import SUPPORTED_LANGUAGES from 'constants/supported_languages';
import WalletSpendableBalanceHelp from 'component/walletSpendableBalanceHelp';
import { SIMPLE_SITE } from 'config';
const LANG_NONE = 'none';
@ -358,7 +359,7 @@ function ChannelForm(props: Props) {
<Card
body={
<TagsSearch
suggestMature
suggestMature={!SIMPLE_SITE}
disableAutoFocus
limitSelect={MAX_TAG_SELECT}
tagsPassedIn={params.tags || []}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View file

@ -2,7 +2,7 @@
import React from 'react';
import { parseURI } from 'lbry-redux';
import classnames from 'classnames';
import Gerbil from './gerbil.png';
import Spaceman from './spaceman.png';
import FreezeframeWrapper from 'component/fileThumbnail/FreezeframeWrapper';
import ChannelStakedIndicator from 'component/channelStakedIndicator';
@ -79,7 +79,7 @@ function ChannelThumbnail(props: Props) {
<img
alt={__('Channel profile picture')}
className="channel-thumbnail__default"
src={!thumbError && thumbnailPreview ? thumbnailPreview : Gerbil}
src={!thumbError && thumbnailPreview ? thumbnailPreview : Spaceman}
onError={() => setThumbError(true)} // if thumb fails (including due to https replace, show gerbil.
/>
)}
@ -91,7 +91,7 @@ function ChannelThumbnail(props: Props) {
<img
alt={__('Channel profile picture')}
className="channel-thumbnail__custom"
src={!thumbError ? thumbnailPreview || thumbnail : Gerbil}
src={!thumbError ? thumbnailPreview || thumbnail : Spaceman}
onError={() => setThumbError(true)} // if thumb fails (including due to https replace, show gerbil.
/>
)}

View file

@ -38,6 +38,7 @@ type Props = {
renderActions?: (Claim) => ?Node,
searchInLanguage: boolean,
hideMenu?: boolean,
hideLivestreamClaims?: boolean,
};
export default function ClaimList(props: Props) {
@ -63,6 +64,7 @@ export default function ClaimList(props: Props) {
renderActions,
searchInLanguage,
hideMenu,
hideLivestreamClaims,
} = props;
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
@ -101,7 +103,14 @@ export default function ClaimList(props: Props) {
return tileLayout && !header ? (
<section className="claim-grid">
{urisLength > 0 &&
uris.map((uri) => <ClaimPreviewTile key={uri} uri={uri} showHiddenByUser={showHiddenByUser} />)}
uris.map((uri) => (
<ClaimPreviewTile
key={uri}
uri={uri}
showHiddenByUser={showHiddenByUser}
hideLivestreamClaims={hideLivestreamClaims}
/>
))}
{!timedOut && urisLength === 0 && !loading && <div className="empty main--empty">{empty || noResultMsg}</div>}
{timedOut && timedOutMessage && <div className="empty main--empty">{timedOutMessage}</div>}
</section>
@ -157,6 +166,7 @@ export default function ClaimList(props: Props) {
renderActions={renderActions}
showUserBlocked={showHiddenByUser}
showHiddenByUser={showHiddenByUser}
hideLivestreamClaims={hideLivestreamClaims}
customShouldHide={(claim: StreamClaim) => {
// Hack to hide spee.ch thumbnail publishes
// If it meets these requirements, it was probably uploaded here:

View file

@ -9,7 +9,7 @@ import {
import { selectFollowedTags } from 'redux/selectors/tags';
import { selectMutedChannels } from 'redux/selectors/blocked';
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
import { makeSelectClientSetting, selectLanguage } from 'redux/selectors/settings';
import { makeSelectClientSetting, selectShowMatureContent, selectLanguage } from 'redux/selectors/settings';
import { selectModerationBlockList } from 'redux/selectors/comments';
import ClaimListDiscover from './view';
@ -18,7 +18,7 @@ const select = (state) => ({
claimSearchByQuery: selectClaimSearchByQuery(state),
claimSearchByQueryLastPageReached: selectClaimSearchByQueryLastPageReached(state),
loading: selectFetchingClaimSearch(state),
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
showNsfw: selectShowMatureContent(state),
hideReposts: makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state),
languageSetting: selectLanguage(state),
mutedUris: selectMutedChannels(state),

View file

@ -23,7 +23,7 @@ type Props = {
personalView: boolean,
doToggleTagFollowDesktop: (string) => void,
meta?: Node,
showNsfw: boolean,
// showNsfw: boolean,
hideReposts: boolean,
history: { action: string, push: (string) => void, replace: (string) => void },
location: { search: string, pathname: string },
@ -66,6 +66,9 @@ type Props = {
searchInLanguage: boolean,
scrollAnchor?: string,
showHiddenByUser?: boolean,
limitClaimsPerChannel?: number,
releaseTime?: string,
hideLivestreamClaims?: boolean,
};
function ClaimListDiscover(props: Props) {
@ -78,7 +81,7 @@ function ClaimListDiscover(props: Props) {
loading,
meta,
channelIds,
showNsfw,
// showNsfw,
hideReposts,
history,
location,
@ -93,8 +96,8 @@ function ClaimListDiscover(props: Props) {
claimType,
pageSize,
defaultClaimType,
streamType,
defaultStreamType,
streamType = CS.FILE_VIDEO,
defaultStreamType = CS.FILE_VIDEO,
freshness,
defaultFreshness = CS.FRESH_WEEK,
renderProperties,
@ -113,8 +116,11 @@ function ClaimListDiscover(props: Props) {
forceShowReposts = false,
languageSetting,
searchInLanguage,
limitClaimsPerChannel,
releaseTime,
scrollAnchor,
showHiddenByUser = false,
hideLivestreamClaims,
} = props;
const didNavigateForward = history.action === 'PUSH';
const { search } = location;
@ -153,12 +159,12 @@ function ClaimListDiscover(props: Props) {
const durationParam = urlParams.get(CS.DURATION_KEY) || null;
const channelIdsInUrl = urlParams.get(CS.CHANNEL_IDS_KEY);
const channelIdsParam = channelIdsInUrl ? channelIdsInUrl.split(',') : channelIds;
const feeAmountParam = urlParams.get('fee_amount') || feeAmount;
const feeAmountParam = urlParams.get('fee_amount') || feeAmount || CS.FEE_AMOUNT_ONLY_FREE;
const originalPageSize = pageSize || CS.PAGE_SIZE;
const dynamicPageSize = isLargeScreen ? Math.ceil(originalPageSize * (3 / 2)) : originalPageSize;
const historyAction = history.action;
let orderParam = orderBy || urlParams.get(CS.ORDER_BY_KEY) || defaultOrderBy;
let orderParam = orderBy || urlParams.get(CS.ORDER_BY_KEY) || defaultOrderBy || orderParamEntry;
if (!orderParam) {
if (historyAction === 'POP') {
@ -202,6 +208,7 @@ function ClaimListDiscover(props: Props) {
fee_amount?: string,
has_source?: boolean,
has_no_source?: boolean,
limit_claims_per_channel?: number,
} = {
page_size: dynamicPageSize,
page,
@ -213,7 +220,7 @@ function ClaimListDiscover(props: Props) {
not_channel_ids:
// If channelIdsParam were passed in, we don't need not_channel_ids
!channelIdsParam ? mutedAndBlockedChannelIds : [],
not_tags: !showNsfw ? MATURE_TAGS : [],
not_tags: MATURE_TAGS,
order_by:
orderParam === CS.ORDER_BY_TRENDING
? CS.ORDER_BY_TRENDING_VALUE
@ -226,6 +233,10 @@ function ClaimListDiscover(props: Props) {
options.has_source = true;
}
if (limitClaimsPerChannel) {
options.limit_claims_per_channel = limitClaimsPerChannel;
}
if (feeAmountParam && claimType !== CS.CLAIM_CHANNEL) {
options.fee_amount = feeAmountParam;
}
@ -255,7 +266,9 @@ function ClaimListDiscover(props: Props) {
options.reposted_claim_id = repostedClaimId;
}
if (claimType !== CS.CLAIM_CHANNEL) {
if (releaseTime) {
options.release_time = releaseTime;
} else if (claimType !== CS.CLAIM_CHANNEL) {
if (orderParam === CS.ORDER_BY_TOP && freshnessParam !== CS.FRESH_ALL) {
options.release_time = `>${Math.floor(moment().subtract(1, freshnessParam).startOf('hour').unix())}`;
} else if (orderParam === CS.ORDER_BY_NEW || orderParam === CS.ORDER_BY_TRENDING) {
@ -333,9 +346,25 @@ function ClaimListDiscover(props: Props) {
const hasMatureTags = tagsParam && tagsParam.split(',').some((t) => MATURE_TAGS.includes(t));
const claimSearchCacheQuery = createNormalizedClaimSearchKey(options);
const claimSearchResult = claimSearchByQuery[claimSearchCacheQuery];
let claimSearchResult = claimSearchByQuery[claimSearchCacheQuery];
const claimSearchResultLastPageReached = claimSearchByQueryLastPageReached[claimSearchCacheQuery];
// uncomment to fix an item on a page
// const fixUri = 'lbry://@corbettreport#0/lbryodysee#5';
// if (
// orderParam === CS.ORDER_BY_NEW &&
// claimSearchResult &&
// claimSearchResult.length > 2 &&
// window.location.pathname === '/$/rabbithole'
// ) {
// if (claimSearchResult.indexOf(fixUri) !== -1) {
// claimSearchResult.splice(claimSearchResult.indexOf(fixUri), 1);
// } else {
// claimSearchResult.pop();
// }
// claimSearchResult.splice(2, 0, fixUri);
// }
const [prevOptions, setPrevOptions] = React.useState(null);
if (!isJustScrollingToNewPage(prevOptions, options)) {
@ -455,7 +484,7 @@ function ClaimListDiscover(props: Props) {
claimType={claimType}
streamType={streamType}
defaultStreamType={defaultStreamType}
feeAmount={feeAmount}
// feeAmount={feeAmount}
orderBy={orderBy}
defaultOrderBy={defaultOrderBy}
hideAdvancedFilter={hideAdvancedFilter}
@ -492,6 +521,7 @@ function ClaimListDiscover(props: Props) {
includeSupportAction={includeSupportAction}
injectedItem={injectedItem}
showHiddenByUser={showHiddenByUser}
hideLivestreamClaims={hideLivestreamClaims}
/>
{loading && (
<div className="claim-grid">
@ -520,6 +550,7 @@ function ClaimListDiscover(props: Props) {
includeSupportAction={includeSupportAction}
injectedItem={injectedItem}
showHiddenByUser={showHiddenByUser}
hideLivestreamClaims={hideLivestreamClaims}
/>
{loading && new Array(dynamicPageSize).fill(1).map((x, i) => <ClaimPreview key={i} placeholder="loading" />)}
</div>

View file

@ -2,14 +2,14 @@ import { connect } from 'react-redux';
import { selectFetchingClaimSearch, SETTINGS } from 'lbry-redux';
import { selectFollowedTags } from 'redux/selectors/tags';
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
import { makeSelectClientSetting, selectLanguage } from 'redux/selectors/settings';
import { makeSelectClientSetting, selectShowMatureContent, selectLanguage } from 'redux/selectors/settings';
import { doSetClientSetting } from 'redux/actions/settings';
import ClaimListHeader from './view';
const select = state => ({
const select = (state) => ({
followedTags: selectFollowedTags(state),
loading: selectFetchingClaimSearch(state),
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
showNsfw: selectShowMatureContent(state),
searchInLanguage: makeSelectClientSetting(SETTINGS.SEARCH_IN_LANGUAGE)(state),
languageSetting: selectLanguage(state),
});

View file

@ -127,6 +127,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
const abandoned = !isResolvingUri && !claim;
const shouldHideActions = hideActions || type === 'small' || type === 'tooltip';
const canonicalUrl = claim && claim.canonical_url;
let isValid = false;
if (uri) {
try {
@ -248,106 +249,110 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
'claim-preview__wrapper--small': type === 'small',
})}
>
{!hideRepostLabel && <ClaimRepostAuthor uri={uri} />}
<>
{!hideRepostLabel && <ClaimRepostAuthor uri={uri} />}
<div
className={classnames('claim-preview', {
'claim-preview--small': type === 'small' || type === 'tooltip',
'claim-preview--large': type === 'large',
'claim-preview--inline': type === 'inline',
'claim-preview--tooltip': type === 'tooltip',
'claim-preview--channel': isChannelUri,
'claim-preview--visited': !isChannelUri && !claimIsMine && hasVisitedUri,
'claim-preview--pending': pending,
})}
>
{isChannelUri && claim ? (
<UriIndicator uri={contentUri} link>
<ChannelThumbnail uri={contentUri} />
</UriIndicator>
) : (
<>
{!pending ? (
<NavLink {...navLinkProps}>
<FileThumbnail thumbnail={thumbnailUrl}>
{/* @if TARGET='app' */}
{claim && (
<div className="claim-preview__hover-actions">
<FileDownloadLink uri={canonicalUrl} hideOpenButton hideDownloadStatus />
</div>
)}
{/* @endif */}
{!isRepost && !isChannelUri && (
<div className="claim-preview__file-property-overlay">
<FileProperties uri={contentUri} small />
</div>
)}
</FileThumbnail>
</NavLink>
) : (
<FileThumbnail thumbnail={thumbnailUrl} />
)}
</>
)}
<div className="claim-preview__text">
<div className="claim-preview-metadata">
<div className="claim-preview-info">
{pending ? (
<ClaimPreviewTitle uri={contentUri} />
) : (
<div
className={classnames('claim-preview', {
'claim-preview--small': type === 'small' || type === 'tooltip',
'claim-preview--large': type === 'large',
'claim-preview--inline': type === 'inline',
'claim-preview--tooltip': type === 'tooltip',
'claim-preview--channel': isChannelUri,
'claim-preview--visited': !isChannelUri && !claimIsMine && hasVisitedUri,
'claim-preview--pending': pending,
})}
>
{isChannelUri && claim ? (
<UriIndicator uri={contentUri} link>
<ChannelThumbnail uri={contentUri} />
</UriIndicator>
) : (
<>
{!pending ? (
<NavLink {...navLinkProps}>
<ClaimPreviewTitle uri={uri} />
<FileThumbnail thumbnail={thumbnailUrl}>
{/* @if TARGET='app' */}
{claim && (
<div className="claim-preview__hover-actions">
<FileDownloadLink uri={canonicalUrl} hideOpenButton hideDownloadStatus />
</div>
)}
{/* @endif */}
{!isRepost && !isChannelUri && !isLivestream && (
<div className="claim-preview__file-property-overlay">
<FileProperties uri={contentUri} small />
</div>
)}
</FileThumbnail>
</NavLink>
) : (
<FileThumbnail thumbnail={thumbnailUrl} />
)}
</>
)}
{/* {type !== 'small' && !isChannelUri && signingChannel && SIMPLE_SITE && (
<div className="claim-preview__text">
<div className="claim-preview-metadata">
<div className="claim-preview-info">
{pending ? (
<ClaimPreviewTitle uri={contentUri} />
) : (
<NavLink {...navLinkProps}>
<ClaimPreviewTitle uri={uri} />
</NavLink>
)}
{/* {type !== 'small' && !isChannelUri && signingChannel && SIMPLE_SITE && (
<ChannelThumbnail uri={signingChannel.permanent_url} />
)} */}
</div>
<ClaimPreviewSubtitle uri={uri} type={type} />
{(pending || !!reflectingProgress) && <PublishPending uri={uri} />}
</div>
<ClaimPreviewSubtitle uri={uri} type={type} />
{(pending || !!reflectingProgress) && <PublishPending uri={uri} />}
{type !== 'small' && (
<div className="claim-preview__actions">
{!pending && (
<>
{renderActions && claim && renderActions(claim)}
{shouldHideActions || renderActions ? null : actions !== undefined ? (
actions
) : (
<div className="claim-preview__primary-actions">
{!isChannelUri && signingChannel && (
<div className="claim-preview__channel-staked">
<ChannelThumbnail uri={signingChannel.permanent_url} />
</div>
)}
{isChannelUri && !channelIsBlocked && !claimIsMine && (
<SubscribeButton
uri={contentUri.startsWith('lbry://') ? contentUri : `lbry://${contentUri}`}
/>
)}
{includeSupportAction && <ClaimSupportButton uri={uri} />}
</div>
)}
</>
)}
{claim && (
<React.Fragment>
{typeof properties === 'function' ? (
properties(claim)
) : properties !== undefined ? (
properties
) : (
<ClaimTags uri={uri} type={type} />
)}
</React.Fragment>
)}
</div>
)}
</div>
{type !== 'small' && (
<div className="claim-preview__actions">
{!pending && (
<>
{renderActions && claim && renderActions(claim)}
{shouldHideActions || renderActions ? null : actions !== undefined ? (
actions
) : (
<div className="claim-preview__primary-actions">
{!isChannelUri && signingChannel && (
<div className="claim-preview__channel-staked">
<ChannelThumbnail uri={signingChannel.permanent_url} />
</div>
)}
{isChannelUri && !channelIsBlocked && !claimIsMine && (
<SubscribeButton uri={contentUri.startsWith('lbry://') ? contentUri : `lbry://${contentUri}`} />
)}
{includeSupportAction && <ClaimSupportButton uri={uri} />}
</div>
)}
</>
)}
{claim && (
<React.Fragment>
{typeof properties === 'function' ? (
properties(claim)
) : properties !== undefined ? (
properties
) : (
<ClaimTags uri={uri} type={type} />
)}
</React.Fragment>
)}
</div>
)}
</div>
</div>
{!hideMenu && <ClaimMenuList uri={uri} />}
{!hideMenu && <ClaimMenuList uri={uri} />}
</>
</WrapperElement>
);
});

View file

@ -8,6 +8,7 @@ import {
doFileGet,
makeSelectChannelForClaimUri,
makeSelectClaimIsNsfw,
makeSelectClaimIsStreamPlaceholder,
} from 'lbry-redux';
import { selectMutedChannels } from 'redux/selectors/blocked';
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
@ -25,6 +26,7 @@ const select = (state, props) => ({
blockedChannelUris: selectMutedChannels(state),
showMature: selectShowMatureContent(state),
isMature: makeSelectClaimIsNsfw(props.uri)(state),
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
});
const perform = (dispatch) => ({

View file

@ -40,6 +40,8 @@ type Props = {
isMature: boolean,
showMature: boolean,
showHiddenByUser?: boolean,
hideLivestreamClaims?: boolean,
isLivestream: boolean,
};
function ClaimPreviewTile(props: Props) {
@ -60,6 +62,8 @@ function ClaimPreviewTile(props: Props) {
isMature,
showMature,
showHiddenByUser,
hideLivestreamClaims,
isLivestream,
} = props;
const isRepost = claim && claim.repost_channel_url;
const shouldFetch = claim === undefined;
@ -137,7 +141,7 @@ function ClaimPreviewTile(props: Props) {
shouldHide = blockedChannelUris.some((blockedUri) => blockedUri === claim.permanent_url);
}
if (shouldHide) {
if (shouldHide || (isLivestream && hideLivestreamClaims)) {
return null;
}

View file

@ -1,13 +1,13 @@
import { connect } from 'react-redux';
import { doClaimSearch, selectClaimSearchByQuery, selectFetchingClaimSearchByQuery, SETTINGS } from 'lbry-redux';
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { makeSelectClientSetting, selectShowMatureContent } from 'redux/selectors/settings';
import ClaimListDiscover from './view';
const select = (state) => ({
claimSearchByQuery: selectClaimSearchByQuery(state),
fetchingClaimSearchByQuery: selectFetchingClaimSearchByQuery(state),
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
showNsfw: selectShowMatureContent(state),
hideReposts: makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state),
});

View file

@ -1,12 +1,12 @@
// @flow
import { ENABLE_NO_SOURCE_CLAIMS } from 'config';
import * as CS from 'constants/claim_search';
import React from 'react';
import { createNormalizedClaimSearchKey, MATURE_TAGS } from 'lbry-redux';
import ClaimPreviewTile from 'component/claimPreviewTile';
import { useHistory } from 'react-router';
type Props = {
prefixUris?: Array<string>,
uris: Array<string>,
doClaimSearch: ({}) => void,
showNsfw: boolean,
@ -32,6 +32,8 @@ type Props = {
timestamp?: string,
feeAmount?: string,
limitClaimsPerChannel?: number,
streamTypes?: Array<string>,
pin?: boolean,
};
function ClaimTilesDiscover(props: Props) {
@ -49,16 +51,18 @@ function ClaimTilesDiscover(props: Props) {
releaseTime,
languages,
claimType,
prefixUris,
timestamp,
feeAmount,
limitClaimsPerChannel,
fetchingClaimSearchByQuery,
streamTypes,
pin,
} = props;
const { location } = useHistory();
const urlParams = new URLSearchParams(location.search);
const feeAmountInUrl = urlParams.get('fee_amount');
const feeAmountParam = feeAmountInUrl || feeAmount;
const feeAmountParam = feeAmountInUrl || feeAmount || CS.FEE_AMOUNT_ONLY_FREE;
const options: {
page_size: number,
no_totals: boolean,
@ -89,6 +93,7 @@ function ClaimTilesDiscover(props: Props) {
channel_ids: channelIds || [],
not_channel_ids: [],
order_by: orderBy || ['trending_group', 'trending_mixed'],
stream_types: streamTypes || [CS.FILE_VIDEO],
};
if (!ENABLE_NO_SOURCE_CLAIMS && (!claimType || claimType === 'stream')) {
@ -129,12 +134,23 @@ function ClaimTilesDiscover(props: Props) {
}
const claimSearchCacheQuery = createNormalizedClaimSearchKey(options);
const uris = (prefixUris || []).concat(claimSearchByQuery[claimSearchCacheQuery] || []);
const uris = claimSearchByQuery[claimSearchCacheQuery] || [];
// Don't use the query from createNormalizedClaimSearchKey for the effect since that doesn't include page & release_time
const optionsStringForEffect = JSON.stringify(options);
const isLoading = fetchingClaimSearchByQuery[claimSearchCacheQuery];
const shouldPerformSearch = !isLoading && uris.length === 0;
const fixUri = 'lbry://@samtime#1/us-gov-tries-suing-a-cryptocurrency-lbry#8';
if (pin && uris && uris.length > 2 && window.location.pathname === '/') {
if (uris.indexOf(fixUri) !== -1) {
uris.splice(uris.indexOf(fixUri), 1);
} else {
uris.pop();
}
uris.splice(2, 0, fixUri);
}
React.useEffect(() => {
if (shouldPerformSearch) {
const searchOptions = JSON.parse(optionsStringForEffect);

View file

@ -7,7 +7,7 @@ import { FormField, Form } from 'component/common/form';
import Button from 'component/button';
import SelectChannel from 'component/selectChannel';
import usePersistedState from 'effects/use-persisted-state';
import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
import { FF_MAX_CHARS_IN_COMMENT, FF_MAX_CHARS_IN_LIVESTREAM_COMMENT } from 'constants/form-field';
import { useHistory } from 'react-router';
import type { ElementRef } from 'react';
import emoji from 'emoji-dictionary';
@ -33,11 +33,15 @@ type Props = {
parentId: string,
isReply: boolean,
isPostingComment: boolean,
activeChannel: string,
activeChannelClaim: ?ChannelClaim,
livestream?: boolean,
bottom: boolean,
onSubmit: (string, string) => void,
livestream: boolean,
authenticated: boolean,
embed?: boolean,
toast: (string) => void,
claimIsMine: boolean,
commentingEnabled: boolean,
};
export function CommentCreate(props: Props) {
@ -53,12 +57,20 @@ export function CommentCreate(props: Props) {
parentId,
isPostingComment,
activeChannelClaim,
onSubmit,
bottom,
livestream,
authenticated,
embed,
toast,
claimIsMine,
commentingEnabled,
} = props;
const buttonref: ElementRef<any> = React.useRef();
const { push } = useHistory();
const {
push,
location: { pathname },
} = useHistory();
const { claim_id: claimId } = claim;
const [commentValue, setCommentValue] = React.useState('');
const [lastCommentTime, setLastCommentTime] = React.useState();
@ -80,7 +92,7 @@ export function CommentCreate(props: Props) {
function altEnterListener(e: SyntheticKeyboardEvent<*>) {
const KEYCODE_ENTER = 13;
if ((e.ctrlKey || e.metaKey) && e.keyCode === KEYCODE_ENTER) {
if ((livestream || e.ctrlKey || e.metaKey) && e.keyCode === KEYCODE_ENTER) {
e.preventDefault();
buttonref.current.click();
}
@ -101,9 +113,7 @@ export function CommentCreate(props: Props) {
: (lastCommentTime - 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.ceil(timeUntilCanComment) })
);
toast(__('Slowmode is on. You can comment again in %time% seconds.', { time: Math.ceil(timeUntilCanComment) }));
return;
}
@ -112,6 +122,10 @@ export function CommentCreate(props: Props) {
setCommentValue('');
setLastCommentTime(Date.now());
if (onSubmit) {
onSubmit(commentValue, activeChannelClaim.name);
}
if (onDoneReplying) {
onDoneReplying();
}
@ -126,9 +140,18 @@ export function CommentCreate(props: Props) {
useEffect(() => setCharCount(commentValue.length), [commentValue]);
if (!hasChannels) {
if (!commentingEnabled || !hasChannels) {
return (
<div role="button" onClick={() => push(`/$/${PAGES.CHANNEL_NEW}`)}>
<div
role="button"
onClick={() =>
embed
? window.open(`https://odysee.com/$/${PAGES.AUTH}?redirect=/$/${PAGES.LIVESTREAM}`)
: authenticated
? push(`/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`)
: push(`/$/${PAGES.AUTH}?redirect=${pathname}`)
}
>
<FormField
type="textarea"
name={'comment_signup_prompt'}
@ -136,7 +159,7 @@ export function CommentCreate(props: Props) {
label={isFetchingChannels ? __('Comment') : undefined}
/>
<div className="section__actions">
<Button disabled button="primary" label={__('Post --[button to submit something]--')} requiresAuth={IS_WEB} />
<Button disabled button="primary" label={__('Post --[button to submit something]--')} />
</div>
</div>
);
@ -148,6 +171,7 @@ export function CommentCreate(props: Props) {
className={classnames('comment__create', {
'comment__create--reply': isReply,
'comment__create--nested-reply': isNested,
'comment__create--bottom': bottom,
})}
>
<FormField
@ -171,7 +195,7 @@ export function CommentCreate(props: Props) {
charCount={charCount}
onChange={handleCommentChange}
autoFocus={isReply}
textAreaMaxLength={FF_MAX_CHARS_IN_COMMENT}
textAreaMaxLength={livestream ? FF_MAX_CHARS_IN_LIVESTREAM_COMMENT : FF_MAX_CHARS_IN_COMMENT}
/>
{livestream && hasChannels && (
<div className="livestream__emoji-actions">
@ -190,7 +214,11 @@ export function CommentCreate(props: Props) {
))}
</div>
)}
<div className="section__actions section__actions--no-margin">
<div
className={classnames('section__actions', {
'section__actions--no-margin': !livestream,
})}
>
<Button
ref={buttonref}
button="primary"

View file

@ -77,7 +77,7 @@ export default function CommentReactions(props: Props) {
<Button
requiresAuth={IS_WEB}
title={__('Upvote')}
icon={ICONS.UPVOTE}
icon={myReacts.includes(REACTION_TYPES.LIKE) ? ICONS.FIRE_ACTIVE : ICONS.FIRE}
className={classnames('comment__action', {
'comment__action--active': myReacts && myReacts.includes(REACTION_TYPES.LIKE),
})}
@ -87,7 +87,7 @@ export default function CommentReactions(props: Props) {
<Button
requiresAuth={IS_WEB}
title={__('Downvote')}
icon={ICONS.DOWNVOTE}
icon={myReacts.includes(REACTION_TYPES.DISLIKE) ? ICONS.SLIME_ACTIVE : ICONS.SLIME}
className={classnames('comment__action', {
'comment__action--active': myReacts && myReacts.includes(REACTION_TYPES.DISLIKE),
})}

View file

@ -20,6 +20,8 @@ type Props = {
defaultExpand?: boolean,
nag?: Node,
smallTitle?: boolean,
onClick?: () => void,
children?: any, // not sure how this works
};
export default function Card(props: Props) {
@ -37,12 +39,21 @@ export default function Card(props: Props) {
smallTitle = false,
defaultExpand,
nag,
onClick,
children,
} = props;
const [expanded, setExpanded] = useState(defaultExpand);
const expandable = defaultExpand !== undefined;
return (
<section className={classnames(className, 'card')}>
<section
className={classnames(className, 'card')}
onClick={() => {
if (onClick) {
onClick();
}
}}
>
{(title || subtitle) && (
<div
className={classnames('card__header--between', {
@ -93,6 +104,7 @@ export default function Card(props: Props) {
</div>
)}
{actions && <div className="card__main-actions">{actions}</div>}
{children && <div className="card__main-actions">{children}</div>}
</>
)}

View file

@ -26,6 +26,7 @@ type Props = {
myChannels: ?Array<ChannelClaim>,
doToast: ({ message: string }) => void,
clearPlayingUri: () => void,
hideRepost?: boolean,
isLivestreamClaim: boolean,
};
@ -42,6 +43,7 @@ function FileActions(props: Props) {
myChannels,
clearPlayingUri,
doToast,
hideRepost,
isLivestreamClaim,
} = props;
const {
@ -83,23 +85,25 @@ function FileActions(props: Props) {
const lhsSection = (
<>
{ENABLE_FILE_REACTIONS && <FileReactions uri={uri} />}
{ENABLE_FILE_REACTIONS && <FileReactions uri={uri} livestream={isLivestreamClaim} />}
<ClaimSupportButton uri={uri} fileAction />
<Button
button="alt"
className="button--file-action"
icon={ICONS.REPOST}
label={
claim.meta.reposted > 1 ? __(`%repost_total% Reposts`, { repost_total: claim.meta.reposted }) : __('Repost')
}
description={__('Repost')}
requiresAuth={IS_WEB}
onClick={handleRepostClick}
/>
{!hideRepost && (
<Button
button="alt"
className="button--file-action"
icon={ICONS.REPOST}
label={
claim.meta.reposted > 1 ? __(`%repost_total% Reposts`, { repost_total: claim.meta.reposted }) : __('Repost')
}
description={__('Repost')}
requiresAuth={IS_WEB}
onClick={handleRepostClick}
/>
)}
<Button
className="button--file-action"
icon={ICONS.SHARE}
label={__('Share')}
label={isMobile ? undefined : __('Share')}
title={__('Share')}
onClick={() => openModal(MODALS.SOCIAL_SHARE, { uri, webShareable })}
/>

View file

@ -50,7 +50,7 @@ function FileDescription(props: Props) {
<FileDetails uri={uri} />
</div>
<div className="section__actions--between">
<div className="card__bottom-actions">
{expanded ? (
<Button button="link" label={__('Less')} onClick={() => setExpanded(!expanded)} />
) : (

View file

@ -1,4 +1,5 @@
// @flow
import * as REACTION_TYPES from 'constants/reactions';
import * as ICONS from 'constants/icons';
import React from 'react';
import classnames from 'classnames';
@ -8,25 +9,51 @@ import NudgeFloating from 'component/nudgeFloating';
type Props = {
claim: StreamClaim,
doFetchReactions: string => void,
doReactionLike: string => void,
doReactionDislike: string => void,
doFetchReactions: (string) => void,
doReactionLike: (string) => void,
doReactionDislike: (string) => void,
uri: string,
likeCount: number,
dislikeCount: number,
myReaction: ?string,
livestream?: boolean,
};
function FileReactions(props: Props) {
const { claim, uri, doFetchReactions, doReactionLike, doReactionDislike, likeCount, dislikeCount } = props;
const {
claim,
uri,
doFetchReactions,
doReactionLike,
doReactionDislike,
myReaction,
likeCount,
dislikeCount,
livestream,
} = props;
const claimId = claim && claim.claim_id;
const channel = claim && claim.signing_channel && claim.signing_channel.name;
React.useEffect(() => {
if (claimId) {
function fetchReactions() {
doFetchReactions(claimId);
}
}, [claimId, doFetchReactions]);
let fetchInterval;
if (claimId) {
fetchReactions();
if (livestream) {
fetchInterval = setInterval(fetchReactions, 10000);
}
}
return () => {
if (fetchInterval) {
clearInterval(fetchInterval);
}
};
}, [claimId, doFetchReactions, livestream]);
return (
<>
@ -41,20 +68,46 @@ function FileReactions(props: Props) {
title={__('I like this')}
requiresAuth={IS_WEB}
authSrc="filereaction_like"
className={classnames('button--file-action')}
label={formatNumberWithCommas(likeCount, 0)}
className={classnames('button--file-action', { 'button--fire': myReaction === REACTION_TYPES.LIKE })}
label={
<>
{myReaction === REACTION_TYPES.LIKE && (
<>
<div className="button__fire-glow" />
<div className="button__fire-particle1" />
<div className="button__fire-particle2" />
<div className="button__fire-particle3" />
<div className="button__fire-particle4" />
<div className="button__fire-particle5" />
<div className="button__fire-particle6" />
</>
)}
{formatNumberWithCommas(likeCount, 0)}
</>
}
iconSize={18}
icon={ICONS.UPVOTE}
icon={myReaction === REACTION_TYPES.LIKE ? ICONS.FIRE_ACTIVE : ICONS.FIRE}
onClick={() => doReactionLike(uri)}
/>
<Button
requiresAuth={IS_WEB}
authSrc={'filereaction_dislike'}
title={__('I dislike this')}
className={classnames('button--file-action')}
label={formatNumberWithCommas(dislikeCount, 0)}
className={classnames('button--file-action', { 'button--slime': myReaction === REACTION_TYPES.DISLIKE })}
label={
<>
{myReaction === REACTION_TYPES.DISLIKE && (
<>
<div className="button__slime-stain" />
<div className="button__slime-drop1" />
<div className="button__slime-drop2" />
</>
)}
{formatNumberWithCommas(dislikeCount, 0)}
</>
}
iconSize={18}
icon={ICONS.DOWNVOTE}
icon={myReaction === REACTION_TYPES.DISLIKE ? ICONS.SLIME_ACTIVE : ICONS.SLIME}
onClick={() => doReactionDislike(uri)}
/>
</>

View file

@ -23,8 +23,8 @@ function FileSubtitle(props: Props) {
<FileViewCount uri={uri} />
)}
</div>
<FileActions uri={uri} />
{ /* did I need these params? */ }
<FileActions uri={uri} hideRepost={livestream} livestream={livestream} />
</div>
);
}

View file

@ -1,10 +1,11 @@
// @flow
import { SIMPLE_SITE } from 'config';
import React, { useEffect } from 'react';
import HelpLink from 'component/common/help-link';
type Props = {
claim: ?StreamClaim,
fetchViewCount: string => void,
fetchViewCount: (string) => void,
uri: string,
viewCount: string,
};
@ -12,7 +13,6 @@ type Props = {
function FileViewCount(props: Props) {
const { claim, uri, fetchViewCount, viewCount } = props;
const claimId = claim && claim.claim_id;
useEffect(() => {
if (claimId) {
fetchViewCount(claimId);
@ -24,7 +24,7 @@ function FileViewCount(props: Props) {
return (
<span className="media__subtitle--centered">
{viewCount !== 1 ? __('%view_count% views', { view_count: formattedViewCount }) : __('1 view')}
<HelpLink href="https://lbry.com/faq/views" />
{!SIMPLE_SITE && <HelpLink href="https://lbry.com/faq/views" />}
</span>
);
}

View file

@ -5,7 +5,8 @@ import FilePrice from 'component/filePrice';
import { formatLbryUrlForWeb } from 'util/url';
import { withRouter } from 'react-router';
import { URL } from 'config';
import * as ICONS from 'constants/icons';
import OdyseeLogo from 'component/header/odysee_logo.png';
import OdyseeLogoWithText from 'component/header/odysee_white.png';
type Props = {
uri: string,
@ -36,7 +37,10 @@ function FileViewerEmbeddedTitle(props: Props) {
{...contentLinkProps}
/>
<div className="file-viewer__embedded-info">
<Button className="file-viewer__overlay-logo" icon={ICONS.LBRY} aria-label={__('Home')} {...lbryLinkProps} />
<Button className="file-viewer__overlay-logo" aria-label={__('Home')} {...lbryLinkProps}>
<img src={OdyseeLogo} className=" mobile-only" />
<img src={OdyseeLogoWithText} className=" mobile-hidden" />
</Button>
{isInApp && <FilePrice uri={uri} />}
</div>
</div>

View file

@ -19,6 +19,8 @@ import ChannelThumbnail from 'component/channelThumbnail';
import { remote } from 'electron';
import { IS_MAC } from 'component/app/view';
// @endif
import OdyseeLogoWithWhiteText from './odysee_white.png';
import OdyseeLogoWithText from './odysee.png';
type Props = {
user: ?User,
@ -86,8 +88,8 @@ const Header = (props: Props) => {
sidebarOpen,
setSidebarOpen,
isAbsoluteSideNavHidden,
user,
hideCancel,
user,
activeChannelClaim,
} = props;
const isMobile = useIsMobile();
@ -98,7 +100,7 @@ const Header = (props: Props) => {
const isPwdResetPage = history.location.pathname.includes(PAGES.AUTH_PASSWORD_RESET);
const hasBackout = Boolean(backout);
const { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {};
const notificationsEnabled = (user && user.experimental_ui) || false;
// const notificationsEnabled = (user && user.experimental_ui) || false; // fix this
const livestreamEnabled = (ENABLE_NO_SOURCE_CLAIMS && user && user.experimental_ui) || false;
const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url;
@ -233,19 +235,12 @@ const Header = (props: Props) => {
icon={ICONS.MENU}
onClick={() => setSidebarOpen(!sidebarOpen)}
>
{isAbsoluteSideNavHidden && isMobile && notificationsEnabled && <NotificationBubble />}
{isAbsoluteSideNavHidden && isMobile && <NotificationBubble />}
</Button>
</span>
)}
<Button
className="header__navigation-item header__navigation-item--lbry"
// @if TARGET='app'
label={'LBRY'}
// @endif
// @if TARGET='web'
label={LOGO_TITLE} // eslint-disable-line
// @endif
icon={ICONS.LBRY}
className="header__navigation-item header__navigation-item--lbry header__navigation-item--button-mobile"
onClick={() => {
if (history.location.pathname === '/') window.location.reload();
}}
@ -255,7 +250,12 @@ const Header = (props: Props) => {
}}
// @endif
{...homeButtonNavigationProps}
/>
>
<img
src={currentTheme === 'light' ? OdyseeLogoWithText : OdyseeLogoWithWhiteText}
className="header__odysee"
/>
</Button>
{!authHeader && (
<div className="header__center">
@ -272,7 +272,7 @@ const Header = (props: Props) => {
<HeaderMenuButtons
authenticated={authenticated}
notificationsEnabled={notificationsEnabled}
notificationsEnabled
history={history}
handleThemeToggle={handleThemeToggle}
currentTheme={currentTheme}
@ -425,6 +425,10 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
<Icon aria-hidden icon={ICONS.CHANNEL} />
{__('New Channel')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.YOUTUBE_SYNC}`)}>
<Icon aria-hidden icon={ICONS.YOUTUBE} />
{__('Sync YouTube Channel')}
</MenuItem>
{livestreamEnabled && (
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.LIVESTREAM}`)}>

View file

@ -7,7 +7,7 @@ import LivestreamFeed from './view';
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),
comments: makeSelectTopLevelCommentsForUri(props.uri)(state),
comments: makeSelectTopLevelCommentsForUri(props.uri)(state).slice(0, 25),
fetchingComments: selectIsFetchingComments(state),
});

View file

@ -54,7 +54,7 @@ export default function LivestreamFeed(props: Props) {
function handleScroll() {
if (element) {
const scrollHeight = element.scrollHeight - element.offsetHeight;
const isAtBottom = scrollHeight === element.scrollTop;
const isAtBottom = scrollHeight <= element.scrollTop + 100;
if (!isAtBottom) {
hasScrolledComments.current = true;
@ -71,7 +71,7 @@ export default function LivestreamFeed(props: Props) {
// Only update comment scroll if the user hasn't scrolled up to view old comments
// If they have, do nothing
if (!hasScrolledComments.current || !performedInitialScroll) {
element.scrollTop = element.scrollHeight - element.offsetHeight;
setTimeout(() => (element.scrollTop = element.scrollHeight - element.offsetHeight + 100), 20);
if (!performedInitialScroll) {
setPerformedInitialScroll(true);

View file

@ -0,0 +1,9 @@
import { connect } from 'react-redux';
import { makeSelectClaimForUri } from 'lbry-redux';
import LivestreamFeed from './view';
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),
});
export default connect(select)(LivestreamFeed);

View file

@ -0,0 +1,38 @@
// @flow
import { BITWAVE_USERNAME, BITWAVE_EMBED_URL } from 'constants/livestream';
import React from 'react';
import FileTitleSection from 'component/fileTitleSection';
import LivestreamComments from 'component/livestreamComments';
type Props = {
uri: string,
claim: ?StreamClaim,
activeViewers: number,
};
export default function LivestreamFeed(props: Props) {
const { claim, uri, activeViewers } = props;
if (!claim) {
return null;
}
return (
<>
<div className="section card-stack">
<div className="file-render file-render--video livestream">
<div className="file-viewer">
<iframe
src={`${BITWAVE_EMBED_URL}/${BITWAVE_USERNAME}?skin=odysee&autoplay=1`}
scrolling="no"
allowFullScreen
/>
</div>
</div>
<FileTitleSection uri={uri} livestream activeViewers={activeViewers} />
</div>
<LivestreamComments uri={uri} />
</>
);
}

View file

@ -13,7 +13,7 @@ export default function LivestreamLink(props: Props) {
const { channelClaim } = props;
const [livestreamClaim, setLivestreamClaim] = React.useState(false);
const [isLivestreaming, setIsLivestreaming] = React.useState(false);
const livestreamChannelId = channelClaim.claim_id || ''; // TODO: fail in a safer way, probably
const livestreamChannelId = (channelClaim && channelClaim.claim_id) || ''; // TODO: fail in a safer way, probably
React.useEffect(() => {
if (livestreamChannelId) {
@ -64,11 +64,12 @@ export default function LivestreamLink(props: Props) {
return null;
}
return (
<Card
className="livestream__channel-link"
title={__('Live stream in progress')}
actions={<ClaimPreview uri={livestreamClaim.canonical_url} livestream type="inline" />}
/>
// gonna pass the wrapper in so I don't have to rewrite the dmca/blocking logic in claimPreview.
const element = (props: { children: any }) => (
<Card className="livestream__channel-link" title={__('Live stream in progress')}>
{props.children}
</Card>
);
return <ClaimPreview uri={livestreamClaim.canonical_url} wrapperElement={element} type="inline" />;
}

View file

@ -5,14 +5,12 @@ import classnames from 'classnames';
type Props = {
unseenCount: number,
inline: boolean,
user: ?User,
};
export default function NotificationHeaderButton(props: Props) {
const { unseenCount, inline = false, user } = props;
const notificationsEnabled = user && user.experimental_ui;
const { unseenCount, inline = false } = props;
if (unseenCount === 0 || !notificationsEnabled) {
if (unseenCount === 0) {
return null;
}

View file

@ -19,9 +19,8 @@ export default function NotificationHeaderButton(props: Props) {
// notifications,
// fetching,
doSeeAllNotifications,
user,
// user,
} = props;
const notificationsEnabled = user && user.experimental_ui;
const { push } = useHistory();
function handleMenuClick() {
@ -32,10 +31,6 @@ export default function NotificationHeaderButton(props: Props) {
push(`/$/${PAGES.NOTIFICATIONS}`);
}
if (!notificationsEnabled) {
return null;
}
return (
<Button
onClick={handleMenuClick}

View file

@ -1,5 +1,6 @@
// @flow
import type { Node } from 'react';
import * as PAGES from 'constants/pages';
import React, { Fragment } from 'react';
import classnames from 'classnames';
import SideNavigation from 'component/sideNavigation';
@ -21,7 +22,6 @@ type Props = {
isUpgradeAvailable: boolean,
authPage: boolean,
filePage: boolean,
homePage: boolean,
noHeader: boolean,
noFooter: boolean,
noSideNavigation: boolean,
@ -56,16 +56,20 @@ function Page(props: Props) {
const {
location: { pathname },
} = useHistory();
const [sidebarOpen, setSidebarOpen] = usePersistedState('sidebar', true);
const [sidebarOpen, setSidebarOpen] = usePersistedState('sidebar', false);
const isMediumScreen = useIsMediumScreen();
const isMobile = useIsMobile();
let isOnFilePage = false;
try {
const url = pathname.slice(1).replace(/:/g, '#');
const { isChannel } = parseURI(url);
if (!isChannel) {
if (pathname.includes(`/$/${PAGES.LIVESTREAM}`)) {
isOnFilePage = true;
} else {
const url = pathname.slice(1).replace(/:/g, '#');
const { isChannel } = parseURI(url);
if (!isChannel) {
isOnFilePage = true;
}
}
} catch (e) {}

View file

@ -156,7 +156,7 @@ function PublishAdditionalOptions(props: Props) {
type="select"
name="content_language"
value={language}
onChange={event => updatePublishForm({ language: event.target.value })}
onChange={(event) => updatePublishForm({ language: event.target.value })}
>
{Object.entries(SUPPORTED_LANGUAGES).map(([langkey, langName]) => (
// $FlowFixMe
@ -176,12 +176,12 @@ function PublishAdditionalOptions(props: Props) {
licenseUrl: newLicenseUrl,
})
}
handleLicenseDescriptionChange={event =>
handleLicenseDescriptionChange={(event) =>
updatePublishForm({
otherLicenseDescription: event.target.value,
})
}
handleLicenseUrlChange={event => updatePublishForm({ licenseUrl: event.target.value })}
handleLicenseUrlChange={(event) => updatePublishForm({ licenseUrl: event.target.value })}
/>
</div>
</div>

View file

@ -346,7 +346,8 @@ function PublishFile(props: Props) {
subtitle={isStillEditing && __('You are currently editing your upload.')}
actions={
<React.Fragment>
<PublishName />
<PublishName uri={uri} />
<FormField
type="text"
name="content_title"
@ -358,12 +359,16 @@ function PublishFile(props: Props) {
/>
{isPublishFile && (
<FileSelector
label={__('File')}
label={__('Video file')}
disabled={disabled}
currentPath={currentFile}
onFileChosen={handleFileChange}
// https://stackoverflow.com/questions/19107685/safari-input-type-file-accept-video-ignores-mp4-files
accept="video/mp4,video/x-m4v,video/*"
placeholder={__('Select video file to upload')}
/>
)}
{isPublishFile && getMessage()}
{isPublishPost && (
<PostEditor
@ -375,7 +380,7 @@ function PublishFile(props: Props) {
setCurrentFileType={setCurrentFileType}
/>
)}
{isPublishFile && getMessage()}
{/* @if TARGET='app' */}
{isPublishFile && (
<FormField

View file

@ -12,6 +12,7 @@ import {
doPrepareEdit,
doCheckPublishNameAvailability,
SETTINGS,
selectMyChannelClaims,
} from 'lbry-redux';
import { doPublishDesktop } from 'redux/actions/publish';
import { selectUnclaimedRewardValue } from 'redux/selectors/rewards';
@ -35,6 +36,7 @@ const select = (state) => ({
modal: selectModal(state),
enablePublishPreview: makeSelectClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW)(state),
activeChannelClaim: selectActiveChannelClaim(state),
myChannels: selectMyChannelClaims(state),
incognito: selectIncognito(state),
});

View file

@ -26,6 +26,7 @@ import Card from 'component/common/card';
import I18nMessage from 'component/i18nMessage';
import * as PUBLISH_MODES from 'constants/publish_types';
import { useHistory } from 'react-router';
import Spinner from 'component/spinner';
// @if TARGET='app'
import fs from 'fs';
@ -201,6 +202,13 @@ function PublishForm(props: Props) {
}
}
// if you enter the page and it is stuck in publishing, "stop it."
useEffect(() => {
if (publishing) {
clearPublish();
}
}, []);
useEffect(() => {
if (!thumbnail) {
resetThumbnailStatus();
@ -249,6 +257,13 @@ function PublishForm(props: Props) {
}
}, [name, activeChannelName, resolveUri, updatePublishForm, checkAvailability]);
useEffect(() => {
// because editingURI is lbry://channel_short/claim_long and that particular shape won't map to the claimId yet
if (editingURI) {
resolveUri(editingURI);
}
}, [editingURI, resolveUri]);
useEffect(() => {
updatePublishForm({
isMarkdownPost: mode === PUBLISH_MODES.POST,
@ -396,6 +411,15 @@ function PublishForm(props: Props) {
}
}, [autoSwitchMode, editingURI, fileMimeType, myClaimForUri, mode, setMode, setAutoSwitchMode]);
if (publishing) {
return (
<div className="main--empty">
<h1 className="section__subtitle">{__('Publishing...')}</h1>
<Spinner delayed />
</div>
);
}
// Editing claim uri
return (
<div className="card-stack">
@ -415,6 +439,7 @@ function PublishForm(props: Props) {
<Button
key={String(modeName)}
icon={modeName}
iconSize={18}
label={__(MODE_TO_I18N_STR[String(modeName)] || '---')}
button="alt"
onClick={() => {
@ -433,7 +458,7 @@ function PublishForm(props: Props) {
{mode === PUBLISH_MODES.FILE && <PublishDescription disabled={formDisabled} />}
<Card actions={<SelectThumbnail />} />
<TagsSelect
suggestMature
suggestMature={!SIMPLE_SITE}
disableAutoFocus
hideHeader
label={__('Selected Tags')}

View file

@ -13,7 +13,6 @@ type Props = {
function NameHelpText(props: Props) {
const { uri, myClaimForUri, onEditMyClaim, isStillEditing } = props;
let nameHelpText;
if (isStillEditing) {

View file

@ -1,4 +1,5 @@
// @flow
import { SEARCH_OPTIONS } from 'constants/search';
import { SHOW_ADS, SIMPLE_SITE } from 'config';
import React from 'react';
import ClaimList from 'component/claimList';
@ -32,7 +33,13 @@ export default function RecommendedContent(props: Props) {
if (stringifiedClaim) {
const jsonClaim = JSON.parse(stringifiedClaim);
if (jsonClaim && jsonClaim.value && jsonClaim.claim_id) {
const options: Options = { size: 20, related_to: jsonClaim.claim_id, isBackgroundSearch: true };
const options: Options = {
size: 20,
related_to: jsonClaim.claim_id,
isBackgroundSearch: true,
[SEARCH_OPTIONS.CLAIM_TYPE]: SEARCH_OPTIONS.INCLUDE_FILES,
[SEARCH_OPTIONS.MEDIA_VIDEO]: true,
};
if (jsonClaim && !mature) {
options['nsfw'] = false;
}

View file

@ -20,20 +20,20 @@ import Spinner from 'component/spinner';
type Props = {
doToast: ({ message: string }) => void,
doClearRepostError: () => void,
doRepost: StreamRepostOptions => Promise<*>,
doRepost: (StreamRepostOptions) => Promise<*>,
title: string,
claim?: StreamClaim,
enteredContentClaim?: StreamClaim,
balance: number,
channels: ?Array<ChannelClaim>,
doCheckPublishNameAvailability: string => Promise<*>,
doCheckPublishNameAvailability: (string) => Promise<*>,
error: ?string,
reposting: boolean,
uri: string,
name: string,
contentUri: string,
setRepostUri: string => void,
setContentUri: string => void,
setRepostUri: (string) => void,
setContentUri: (string) => void,
doCheckPendingClaims: () => void,
redirectUri?: string,
passedRepostAmount: number,
@ -88,7 +88,7 @@ function RepostCreate(props: Props) {
const repostUrlName = `lbry://${!activeChannelClaim ? '' : `${activeChannelClaim.name}/`}`;
const contentFirstRender = React.useRef(true);
const setAutoRepostBid = amount => {
const setAutoRepostBid = (amount) => {
if (balance && balance > 0.02) {
if (uri) {
setRepostBid(0.01);
@ -113,7 +113,7 @@ function RepostCreate(props: Props) {
const isLbryUrl = value.startsWith('lbry://') && value !== 'lbry://';
const error = '';
const addLbryIfNot = term => {
const addLbryIfNot = (term) => {
return term.startsWith('lbry://') ? term : `lbry://${term}`;
};
if (wasCopiedFromWeb) {
@ -178,7 +178,7 @@ function RepostCreate(props: Props) {
React.useEffect(() => {
if (enteredRepostName && isNameValid(enteredRepostName, false)) {
doCheckPublishNameAvailability(enteredRepostName).then(r => setAvailable(r));
doCheckPublishNameAvailability(enteredRepostName).then((r) => setAvailable(r));
}
}, [enteredRepostName, doCheckPublishNameAvailability]);
@ -316,7 +316,7 @@ function RepostCreate(props: Props) {
<div>
{uri && (
<fieldset-section>
<ClaimPreview key={uri} uri={uri} actions={''} type={'inline'} showNullPlaceholder />
<ClaimPreview key={uri} uri={uri} actions={''} showNullPlaceholder />
</fieldset-section>
)}
{!uri && name && (
@ -327,7 +327,7 @@ function RepostCreate(props: Props) {
name="content_url"
value={enteredContent}
error={contentError}
onChange={event => setEnteredContentUri(event.target.value)}
onChange={(event) => setEnteredContentUri(event.target.value)}
placeholder={__('Enter a name or %domain% URL', { domain: SITE_URL })}
/>
</>
@ -359,7 +359,7 @@ function RepostCreate(props: Props) {
type="text"
name="repost_name"
value={enteredRepostName}
onChange={event => setEnteredRepostName(event.target.value)}
onChange={(event) => setEnteredRepostName(event.target.value)}
placeholder={__('MyFunName')}
/>
</fieldset-group>
@ -384,8 +384,8 @@ function RepostCreate(props: Props) {
</>
}
disabled={!enteredRepostName || resolvingRepost}
onChange={event => setRepostBid(event.target.value)}
onWheel={e => e.stopPropagation()}
onChange={(event) => setRepostBid(event.target.value)}
onWheel={(e) => e.stopPropagation()}
/>
</React.Fragment>

View file

@ -216,6 +216,7 @@ function AppRouter(props: Props) {
<Route path={`/`} exact component={HomePage} />
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
<Route path={`/$/${PAGES.WILD_WEST}`} exact component={DiscoverPage} />
{/* $FlowFixMe */}
{dynamicRoutes.map((dynamicRouteProps: RowDataItem) => (
<Route
@ -283,6 +284,7 @@ function AppRouter(props: Props) {
<Route path={`/$/${PAGES.EMBED}/:claimName`} exact component={EmbedWrapperPage} />
<Route path={`/$/${PAGES.EMBED}/:claimName/:claimId`} exact component={EmbedWrapperPage} />
{/* Below need to go at the end to make sure we don't match any of our pages first */}
<Route path="/:claimName" exact component={ShowPage} />
<Route path="/:claimName/:streamName" exact component={ShowPage} />

View file

@ -33,33 +33,34 @@ const SearchOptions = (props: Props) => {
/>
{expanded && (
<Form className="search__options">
<fieldset>
<legend className="search__legend">{__('Search For')}</legend>
{[
{
option: SEARCH_OPTIONS.INCLUDE_FILES,
label: __('Files'),
},
{
option: SEARCH_OPTIONS.INCLUDE_CHANNELS,
label: __('Channels'),
},
{
option: SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS,
label: __('Everything'),
},
].map(({ option, label }) => (
<FormField
key={option}
name={option}
type="radio"
blockWrap={false}
label={label}
checked={options[SEARCH_OPTIONS.CLAIM_TYPE] === option}
onChange={() => setSearchOption(SEARCH_OPTIONS.CLAIM_TYPE, option)}
/>
))}
</fieldset>
{false && (
<fieldset>
{[
{
option: SEARCH_OPTIONS.INCLUDE_FILES,
label: __('Files'),
},
{
option: SEARCH_OPTIONS.INCLUDE_CHANNELS,
label: __('Channels'),
},
{
option: SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS,
label: __('Everything'),
},
].map(({ option, label }) => (
<FormField
key={option}
name={option}
type="radio"
blockWrap={false}
label={label}
checked={options[SEARCH_OPTIONS.CLAIM_TYPE] === option}
onChange={() => setSearchOption(SEARCH_OPTIONS.CLAIM_TYPE, option)}
/>
))}
</fieldset>
)}
<fieldset disabled={isFilteringByChannel}>
<legend className="search__legend">{__('Type')}</legend>
@ -70,7 +71,7 @@ const SearchOptions = (props: Props) => {
},
{
option: SEARCH_OPTIONS.MEDIA_AUDIO,
label: __('Audio'),
label: __('Music'),
},
{
option: SEARCH_OPTIONS.MEDIA_IMAGE,
@ -98,22 +99,24 @@ const SearchOptions = (props: Props) => {
))}
</fieldset>
<fieldset>
<legend className="search__legend">{__('Other Options')}</legend>
<FormField
type="select"
name="result-count"
value={resultCount}
onChange={(e) => setSearchOption(SEARCH_OPTIONS.RESULT_COUNT, e.target.value)}
blockWrap={false}
label={__('Returned Results')}
>
<option value={10}>10</option>
<option value={30}>30</option>
<option value={50}>50</option>
<option value={100}>100</option>
</FormField>
</fieldset>
{false && (
<fieldset>
<legend className="search__legend">{__('Other Options')}</legend>
<FormField
type="select"
name="result-count"
value={resultCount}
onChange={(e) => setSearchOption(SEARCH_OPTIONS.RESULT_COUNT, e.target.value)}
blockWrap={false}
label={__('Returned Results')}
>
<option value={10}>10</option>
<option value={30}>30</option>
<option value={50}>50</option>
<option value={100}>100</option>
</FormField>
</fieldset>
)}
</Form>
)}
</div>

View file

@ -99,7 +99,7 @@ class SelectThumbnail extends React.PureComponent<Props, State> {
style={{ display: 'none' }}
src={thumbnailSrc}
alt={__('Thumbnail Preview')}
onError={e => {
onError={(e) => {
this.setState({
thumbnailError: true,
});
@ -131,9 +131,9 @@ class SelectThumbnail extends React.PureComponent<Props, State> {
<FileSelector
currentPath={thumbnailPath}
label={__('Thumbnail')}
placeholder={__('Choose a thumbnail')}
placeholder={__('Thumbnails that entice a viewer to watch a video work best')}
accept={accept}
onFileChosen={file => openModal(MODALS.CONFIRM_THUMBNAIL_UPLOAD, { file })}
onFileChosen={(file) => openModal(MODALS.CONFIRM_THUMBNAIL_UPLOAD, { file })}
/>
)}
{status === THUMBNAIL_STATUSES.COMPLETE && thumbnail && (

View file

@ -51,6 +51,7 @@ type Props = {
type SideNavLink = {
title: string,
link?: string,
route?: string,
onClick?: () => any,
icon: string,
extra?: Node,
@ -60,7 +61,6 @@ type SideNavLink = {
function SideNavigation(props: Props) {
const {
subscriptions,
followedTags,
doSignOut,
email,
purchaseSuccess,
@ -72,6 +72,7 @@ function SideNavigation(props: Props) {
unseenCount,
homepageData,
user,
followedTags,
} = props;
const { EXTRA_SIDEBAR_LINKS } = homepageData;
@ -211,7 +212,7 @@ function SideNavigation(props: Props) {
});
}
const notificationsEnabled = user && user.experimental_ui;
const notificationsEnabled = SIMPLE_SITE || (user && user.experimental_ui);
const isAuthenticated = Boolean(email);
// SIDE LINKS: FOLLOWING, HOME, [FULL,] [EXTRA]
let SIDE_LINKS: Array<SideNavLink> = [];
@ -291,8 +292,13 @@ function SideNavigation(props: Props) {
<li className="navigation-link">
<Button label={__('FAQ')} href="https://odysee.com/@OdyseeHelp:b" />
</li>
<li className="navigation-link">
<Button label={__('Support')} href="https://lbry.com/support" />
<Button label={__('Community Guidelines')} href="https://odysee.com/@OdyseeHelp:b/Community-Guidelines:c" />
</li>
<li className="navigation-link">
<Button label={__('Support --[used in footer; general help/support]--')} href="https://lbry.com/support" />
</li>
<li className="navigation-link">
<Button label={__('Terms')} href="https://lbry.com/termsofservice" />
@ -325,7 +331,7 @@ function SideNavigation(props: Props) {
// $FlowFixMe
const { hideForUnauth, ...passedProps } = linkProps;
return !email && linkProps.hideForUnauth && IS_WEB ? null : (
<li key={linkProps.link}>
<li key={linkProps.route || linkProps.link}>
<Button
{...passedProps}
label={__(linkProps.title)}

View file

@ -7,7 +7,6 @@ import {
} from 'redux/selectors/subscriptions';
import { makeSelectPermanentUrlForUri } from 'lbry-redux';
import { doToast } from 'redux/actions/notifications';
import { selectUser } from 'redux/selectors/user';
import SubscribeButton from './view';
const select = (state, props) => ({
@ -15,7 +14,6 @@ const select = (state, props) => ({
firstRunCompleted: selectFirstRunCompleted(state),
permanentUrl: makeSelectPermanentUrlForUri(props.uri)(state),
notificationsDisabled: makeSelectNotificationsDisabled(props.uri)(state),
user: selectUser(state),
});
export default connect(select, {

View file

@ -15,13 +15,12 @@ type SubscriptionArgs = {
type Props = {
permanentUrl: ?string,
isSubscribed: boolean,
doChannelSubscribe: SubscriptionArgs => void,
doChannelUnsubscribe: SubscriptionArgs => void,
doChannelSubscribe: (SubscriptionArgs) => void,
doChannelUnsubscribe: (SubscriptionArgs) => void,
showSnackBarOnSubscribe: boolean,
doToast: ({ message: string }) => void,
shrinkOnMobile: boolean,
notificationsDisabled: boolean,
user: ?User,
};
export default function SubscribeButton(props: Props) {
@ -34,14 +33,12 @@ export default function SubscribeButton(props: Props) {
doToast,
shrinkOnMobile = false,
notificationsDisabled,
user,
} = props;
const buttonRef = useRef();
const isMobile = useIsMobile();
let isHovering = useHover(buttonRef);
isHovering = isMobile ? true : isHovering;
const uiNotificationsEnabled = user && user.experimental_ui;
const { channelName } = parseURI(permanentUrl);
const claimName = '@' + channelName;
@ -66,7 +63,7 @@ export default function SubscribeButton(props: Props) {
requiresAuth={IS_WEB}
label={label}
title={titlePrefix}
onClick={e => {
onClick={(e) => {
e.stopPropagation();
subscriptionHandler({
@ -80,7 +77,7 @@ export default function SubscribeButton(props: Props) {
}
}}
/>
{isSubscribed && uiNotificationsEnabled && (
{isSubscribed && (
<Button
button="alt"
icon={notificationsDisabled ? ICONS.BELL : ICONS.BELL_ON}

View file

@ -53,7 +53,7 @@ function SyncPassword(props: Props) {
error={passwordError && __('Wrong password for %email%', { email })}
label={__('Password for %email%', { email })}
value={password}
onChange={e => setPassword(e.target.value)}
onChange={(e) => setPassword(e.target.value)}
/>
<FormField
name="remember-password"
@ -75,7 +75,7 @@ function SyncPassword(props: Props) {
<I18nMessage
tokens={{
help: <Button button="link" label={__('help guide')} href="https://lbry.com/faq/account-sync" />,
email: <Button button="link" label={'help@lbry.com'} href="mailto:help@lbry.com" />,
email: <Button button="link" label={'help@odysee.com'} href="mailto:help@odysee.com" />,
}}
>
If you are having issues, checkout our %help% or email us at %email%.

View file

@ -11,24 +11,23 @@ import { AUTO_FOLLOW_CHANNELS, SIMPLE_SITE } from 'config';
type Props = {
subscribedChannels: Array<Subscription>,
onContinue: () => void,
onBack: () => void,
channelSubscribe: (sub: Subscription) => void,
homepageData: any,
};
const channelsToSubscribe = AUTO_FOLLOW_CHANNELS.trim()
.split(' ')
.filter(x => x !== '');
.filter((x) => x !== '');
function UserChannelFollowIntro(props: Props) {
const { subscribedChannels, channelSubscribe, onContinue, onBack, homepageData } = props;
const { subscribedChannels, channelSubscribe, onContinue, homepageData } = props;
const { PRIMARY_CONTENT_CHANNEL_IDS } = homepageData;
const followingCount = (subscribedChannels && subscribedChannels.length) || 0;
// subscribe to lbry
useEffect(() => {
if (channelsToSubscribe && channelsToSubscribe.length) {
channelsToSubscribe.forEach(c =>
channelsToSubscribe.forEach((c) =>
channelSubscribe({
channelName: parseURI(c).claimName,
uri: c,
@ -41,25 +40,25 @@ function UserChannelFollowIntro(props: Props) {
<Card
title={__('Find channels to follow')}
subtitle={__(
'LBRY works better if you find and follow a couple creators you like. You can also block channels you never want to see.'
'Odysee works better if you find and follow a couple creators you like. You can also block channels you never want to see.'
)}
actions={
<React.Fragment>
<div className="section__actions--between">
<Button button="secondary" onClick={onBack} label={__('Back')} />
<Button
button={subscribedChannels.length < 1 ? 'alt' : 'primary'}
onClick={onContinue}
label={subscribedChannels.length < 1 ? __('Skip') : __('Continue')}
/>
</div>
<div className="section__body">
<ClaimListDiscover
defaultOrderBy={CS.ORDER_BY_TOP}
hideFilters
meta={
<Button
button={subscribedChannels.length < 1 ? 'alt' : 'primary'}
onClick={onContinue}
label={subscribedChannels.length < 1 ? __('Skip') : __('Continue')}
/>
}
defaultOrderBy={CS.ORDER_BY_TRENDING}
defaultFreshness={CS.FRESH_ALL}
claimType="channel"
claimIds={SIMPLE_SITE ? undefined : PRIMARY_CONTENT_CHANNEL_IDS}
defaultTags={followingCount > 3 ? CS.TAGS_FOLLOWED : undefined}
claimIds={SIMPLE_SITE ? PRIMARY_CONTENT_CHANNEL_IDS : undefined}
claimType={CS.CLAIM_CHANNEL}
maxPages={3}
/>
{followingCount > 0 && (
<Nag

View file

@ -100,7 +100,7 @@ function UserEmailNew(props: Props) {
<Card
title={__('Join %SITE_NAME%', { SITE_NAME })}
// @if TARGET='app'
subtitle={__('An account with lbry.tv allows you to earn rewards and backup your data.')}
subtitle={__('An account with %domain% allows you to earn rewards and backup your data.', { domain: DOMAIN })}
// @endif
actions={
<div className={classnames({ 'card--disabled': DOMAIN === 'lbry.tv' })}>

View file

@ -8,10 +8,10 @@ const THIRTY_SECONDS_IN_MS = 30000;
type Props = {
email: string,
isReturningUser: boolean,
resendVerificationEmail: string => void,
resendVerificationEmail: (string) => void,
resendingEmail: boolean,
checkEmailVerified: () => void,
toast: string => void,
toast: (string) => void,
user: {
has_verified_email: boolean,
},
@ -102,7 +102,7 @@ class UserEmailVerify extends React.PureComponent<Props, State> {
<p className="help--card-actions">
<I18nMessage
tokens={{
help_link: <Button button="link" href="mailto:help@lbry.com" label="help@lbry.com" />,
help_link: <Button button="link" href="mailto:help@odysee.com" label="help@odysee.com" />,
chat_link: <Button button="link" href="https://chat.lbry.com" label={__('chat')} />,
}}
>

View file

@ -6,7 +6,7 @@ import I18nMessage from 'component/i18nMessage';
import Card from 'component/common/card';
type Props = {
verifyUserPhone: string => void,
verifyUserPhone: (string) => void,
resetPhone: () => void,
phoneErrorMessage: string,
phone: string,
@ -61,7 +61,7 @@ class UserPhoneVerify extends React.PureComponent<Props, State> {
name="code"
placeholder="1234"
value={this.state.code}
onChange={event => {
onChange={(event) => {
this.handleCodeChanged(event);
}}
label={__('Verification Code')}
@ -72,7 +72,7 @@ class UserPhoneVerify extends React.PureComponent<Props, State> {
<p className="help">
<I18nMessage
tokens={{
help_link: <Button button="link" href="mailto:help@lbry.com" label="help@lbry.com" />,
help_link: <Button button="link" href="mailto:help@odysee.com" label="help@odysee.com" />,
chat_link: <Button button="link" href="https://chat.lbry.com" label={__('chat')} />,
}}
>

View file

@ -1,5 +1,4 @@
// @flow
import * as PAGES from 'constants/pages';
import React from 'react';
import classnames from 'classnames';
import { useHistory } from 'react-router';
@ -7,7 +6,6 @@ import UserEmailNew from 'component/userEmailNew';
import UserEmailVerify from 'component/userEmailVerify';
import UserFirstChannel from 'component/userFirstChannel';
import UserChannelFollowIntro from 'component/userChannelFollowIntro';
import UserTagFollowIntro from 'component/userTagFollowIntro';
import YoutubeSync from 'page/youtubeSync';
import { DEFAULT_BID_FOR_FIRST_CHANNEL } from 'component/userFirstChannel/view';
import { YOUTUBE_STATUSES } from 'lbryinc';
@ -42,7 +40,6 @@ type Props = {
creatingChannel: boolean,
setClientSetting: (string, boolean, ?boolean) => void,
followingAcknowledged: boolean,
tagsAcknowledged: boolean,
rewardsAcknowledged: boolean,
interestedInYoutubeSync: boolean,
doToggleInterestedInYoutubeSync: () => void,
@ -67,7 +64,6 @@ function UserSignUp(props: Props) {
fetchingChannels,
creatingChannel,
followingAcknowledged,
tagsAcknowledged,
rewardsAcknowledged,
setClientSetting,
interestedInYoutubeSync,
@ -118,8 +114,7 @@ function UserSignUp(props: Props) {
interestedInYoutubeSync);
const showYoutubeTransfer = hasVerifiedEmail && hasYoutubeChannels && !isYoutubeTransferComplete;
const showFollowIntro = step === 'channels' || (hasVerifiedEmail && !followingAcknowledged);
const showTagsIntro = step === 'tags' || (hasVerifiedEmail && !tagsAcknowledged);
const canHijackSignInFlowWithSpinner = hasVerifiedEmail && !showFollowIntro && !showTagsIntro && !rewardsAcknowledged;
const canHijackSignInFlowWithSpinner = hasVerifiedEmail && !showFollowIntro && !rewardsAcknowledged;
const isCurrentlyFetchingSomething = fetchingChannels || claimingReward || syncingWallet || creatingChannel;
const isWaitingForSomethingToFinish =
// If the user has claimed the email award, we need to wait until the balance updates sometime in the future
@ -206,22 +201,6 @@ function UserSignUp(props: Props) {
}}
/>
),
showTagsIntro && (
<UserTagFollowIntro
onContinue={() => {
let url = `/$/${PAGES.AUTH}?reset_scroll=1&${STEP_PARAM}=channels`;
if (redirect) {
url += `&${REDIRECT_PARAM}=${redirect}`;
}
if (shouldRedirectImmediately) {
url += `&${REDIRECT_IMMEDIATELY_PARAM}=true`;
}
replace(url);
setSettingAndSync(SETTINGS.TAGS_ACKNOWLEDGED, true);
}}
/>
),
showYoutubeTransfer && (
<div>
<YoutubeTransferStatus /> <Confetti recycle={false} style={{ position: 'fixed' }} />

View file

@ -12,7 +12,7 @@ import LbcSymbol from 'component/common/lbc-symbol';
type Props = {
errorMessage: ?string,
isPending: boolean,
verifyUserIdentity: string => void,
verifyUserIdentity: (string) => void,
verifyPhone: () => void,
fetchUser: () => void,
skipLink?: string,
@ -60,7 +60,7 @@ class UserVerify extends React.PureComponent<Props> {
SITE_NAME,
}}
>
Verified accounts are eligible to earn LBRY Credits for views, watching and reposting content, sharing
Verified accounts are eligible to earn LBRY Credits for views, watching and reposting videos, sharing
invite links etc. Verifying also helps us keep the %SITE_NAME% community safe too! %Refresh% or %Skip%.
</I18nMessage>
</p>
@ -73,9 +73,7 @@ class UserVerify extends React.PureComponent<Props> {
<Card
icon={ICONS.PHONE}
title={__('Verify phone number')}
subtitle={__(
'You will receive an SMS text message confirming your phone number is valid. Does not work for Canada and possibly other regions.'
)}
subtitle={__('You will receive an SMS text message confirming your phone number is valid.')}
actions={
<Fragment>
<Button

View file

@ -51,14 +51,14 @@ type Props = {
toggleVideoTheaterMode: () => void,
};
type VideoJSOptions = {
controls: boolean,
preload: string,
playbackRates: Array<number>,
responsive: boolean,
poster?: string,
muted?: boolean,
};
// type VideoJSOptions = {
// controls: boolean,
// preload: string,
// playbackRates: Array<number>,
// responsive: boolean,
// poster: ?string,
// muted: ?boolean,
// };
const videoPlaybackRates = [0.25, 0.5, 0.75, 1, 1.1, 1.25, 1.5, 1.75, 2];
@ -67,7 +67,7 @@ const IS_IOS =
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) &&
!window.MSStream;
const VIDEO_JS_OPTIONS: VideoJSOptions = {
const VIDEO_JS_OPTIONS = {
preload: 'auto',
playbackRates: videoPlaybackRates,
responsive: true,
@ -172,9 +172,7 @@ properties for this component should be kept to ONLY those that if changed shoul
*/
export default React.memo<Props>(function VideoJs(props: Props) {
const { autoplay, startMuted, source, sourceType, poster, isAudio, onPlayerReady, toggleVideoTheaterMode } = props;
const [reload, setReload] = useState('initial');
const playerRef = useRef();
const containerRef = useRef();
const videoJsOptions = {

View file

@ -73,6 +73,8 @@ function VideoViewer(props: Props) {
const claimId = claim && claim.claim_id;
const isAudio = contentType.includes('audio');
const forcePlayer = FORCE_CONTENT_TYPE_PLAYER.includes(contentType);
const previousUri = usePrevious(uri);
const embedded = useContext(EmbedContext);
const [isPlaying, setIsPlaying] = useState(false);
const [showAutoplayCountdown, setShowAutoplayCountdown] = useState(false);
const [isEndededEmbed, setIsEndededEmbed] = useState(false);
@ -82,9 +84,6 @@ function VideoViewer(props: Props) {
breaks because some browsers (e.g. Firefox) block autoplay but leave the player.play Promise pending */
const [isLoading, setIsLoading] = useState(false);
const previousUri = usePrevious(uri);
const embedded = useContext(EmbedContext);
// force everything to recent when URI changes, can cause weird corner cases otherwise (e.g. navigate while autoplay is true)
useEffect(() => {
if (uri && previousUri && uri !== previousUri) {

View file

@ -21,7 +21,9 @@ export default function WebUploadList(props: Props) {
!!uploadCount && (
<Card
title={__('Currently uploading')}
subtitle={uploadCount > 1 ? __('You files are currently uploading.') : __('Your file is currently uploading.')}
subtitle={
uploadCount > 1 ? __('You videos are currently uploading.') : __('Your video is currently uploading.')
}
body={
<section>
{/* $FlowFixMe */}

View file

@ -1,28 +1,28 @@
import * as MODALS from 'constants/modal_types';
import { connect } from 'react-redux';
import { selectLanguage, makeSelectClientSetting } from 'redux/selectors/settings';
import { selectLanguage, selectShowMatureContent } from 'redux/selectors/settings';
import { doToast } from 'redux/actions/notifications';
import { doSearch } from 'redux/actions/search';
import { doOpenModal, doHideModal } from 'redux/actions/app';
import { withRouter } from 'react-router';
import { doResolveUris, SETTINGS } from 'lbry-redux';
import { doResolveUris } from 'lbry-redux';
import analytics from 'analytics';
import Wunderbar from './view';
const select = (state, props) => ({
language: selectLanguage(state),
showMature: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
showMature: selectShowMatureContent(state),
});
const perform = (dispatch, ownProps) => ({
doResolveUris: uris => dispatch(doResolveUris(uris)),
doResolveUris: (uris) => dispatch(doResolveUris(uris)),
doSearch: (query, options) => dispatch(doSearch(query, options)),
navigateToSearchPage: query => {
navigateToSearchPage: (query) => {
let encodedQuery = encodeURIComponent(query);
ownProps.history.push({ pathname: `/$/search`, search: `?q=${encodedQuery}` });
analytics.apiLogSearch();
},
doShowSnackBar: message => dispatch(doToast({ isError: true, message })),
doShowSnackBar: (message) => dispatch(doToast({ isError: true, message })),
doOpenMobileSearch: () => dispatch(doOpenModal(MODALS.MOBILE_SEARCH)),
doCloseMobileSearch: () => dispatch(doHideModal()),
});

View file

@ -119,7 +119,7 @@ export default function YoutubeTransferStatus(props: Props) {
<I18nMessage
tokens={{ here: <Button button="link" href="https://lbry.com/faq/youtube" label={__('here')} /> }}
>
Email help@lbry.com if you think there has been a mistake. Make sure your channel qualifies %here%.
Email help@odysee.com if you think there has been a mistake. Make sure your channel qualifies %here%.
</I18nMessage>
)}
</span>

View file

@ -3,3 +3,22 @@ export const LINKED_COMMENT_QUERY_PARAM = 'lc';
export const SORT_COMMENTS_NEW = 'new';
export const SORT_COMMENTS_BEST = 'best';
export const SORT_COMMENTS_CONTROVERSIAL = 'controversial';
export const BANNED_LIVESTREAM_WORDS = [
'n1gga',
'f4ggot',
'faggot',
'nigga',
'nigger',
'F4G',
'fag',
'n1gger',
'faget',
'niggah',
'n1ggah',
'jew',
'j3w',
'goy',
'goyim',
'fagot',
];

View file

@ -1,3 +1,4 @@
export const FF_MAX_CHARS_DEFAULT = 2000;
export const FF_MAX_CHARS_IN_COMMENT = 2000;
export const FF_MAX_CHARS_IN_LIVESTREAM_COMMENT = 300;
export const FF_MAX_CHARS_IN_DESCRIPTION = 5000;

View file

@ -61,3 +61,4 @@ exports.CHANNEL_NEW = 'channel/new';
exports.NOTIFICATIONS = 'notifications';
exports.YOUTUBE_SYNC = 'youtube';
exports.LIVESTREAM = 'livestream';
exports.GENERAL = 'general';

View file

@ -40,18 +40,18 @@ class ModalPublishSuccess extends React.PureComponent<Props> {
'Your livestream is now pending. You will be able to start shortly at the streaming dashboard.'
);
} else {
publishMessage = __('Your file is now pending on LBRY. It will take a few minutes to appear for other users.');
publishMessage = __('Your video will appear on Odysee shortly.');
}
clearPublish();
function handleClose() {
clearPublish();
closeModal();
}
return (
<Modal isOpen type="card" contentLabel={__(contentLabel)} onAborted={handleClose}>
<Card
title={__('Success')}
title={livestream ? __('Livestream Created') : __('Upload Complete')}
subtitle={publishMessage}
body={
<React.Fragment>

View file

@ -7,6 +7,7 @@ import {
selectIsStillEditing,
selectMyChannelClaims,
SETTINGS,
doClearPublish,
} from 'lbry-redux';
import { selectFfmpegStatus, makeSelectClientSetting } from 'redux/selectors/settings';
import { doPublishDesktop } from 'redux/actions/publish';
@ -16,6 +17,8 @@ const select = (state) => ({
...selectPublishFormValues(state),
myChannels: selectMyChannelClaims(state),
isVid: makeSelectPublishFormValue('fileVid')(state),
publishSuccess: makeSelectPublishFormValue('publishSuccess')(state),
publishing: makeSelectPublishFormValue('publishing')(state),
isStillEditing: selectIsStillEditing(state),
ffmpegStatus: selectFfmpegStatus(state),
enablePublishPreview: makeSelectClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW)(state),
@ -23,6 +26,7 @@ const select = (state) => ({
const perform = (dispatch) => ({
publish: (filePath, preview) => dispatch(doPublishDesktop(filePath, preview)),
clearPublish: () => dispatch(doClearPublish()),
closeModal: () => dispatch(doHideModal()),
setEnablePublishPreview: (value) => dispatch(doSetClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW, value)),
});

View file

@ -41,17 +41,63 @@ type Props = {
setEnablePublishPreview: (boolean) => void,
isStillEditing: boolean,
myChannels: ?Array<ChannelClaim>,
publishSuccess: boolean,
publishing: boolean,
clearPublish: () => void,
};
class ModalPublishPreview extends React.PureComponent<Props> {
onConfirmed() {
const { filePath, publish, closeModal } = this.props;
// class ModalPublishPreview extends React.PureComponent<Props> {
const ModalPublishPreview = (props: Props) => {
const {
filePath,
isMarkdownPost,
optimize,
title,
description,
channel,
bid,
uri,
contentIsFree,
fee,
language,
licenseType,
otherLicenseDescription,
licenseUrl,
tags,
isVid,
ffmpegStatus = {},
previewResponse,
enablePublishPreview,
setEnablePublishPreview,
isStillEditing,
myChannels,
publishSuccess,
publishing,
publish,
closeModal,
clearPublish,
} = props;
const livestream =
// $FlowFixMe
previewResponse.outputs[0] && previewResponse.outputs[0].value && !previewResponse.outputs[0].value.source;
// @if TARGET='web'
React.useEffect(() => {
if (publishing && !livestream) {
closeModal();
}
if (publishSuccess && livestream) {
clearPublish();
closeModal();
}
}, [publishSuccess, publishing, livestream]);
// @endif
// const waitForSuccess = false;
function onConfirmed() {
// Publish for real:
publish(this.resolveFilePathName(filePath), false);
closeModal();
publish(getFilePathName(filePath), false);
}
resolveFilePathName(filePath: string | WebFile) {
function getFilePathName(filePath: string | WebFile) {
if (!filePath) {
return NO_FILE;
}
@ -63,7 +109,7 @@ class ModalPublishPreview extends React.PureComponent<Props> {
}
}
createRow(label: string, value: any) {
function createRow(label: string, value: any) {
return (
<tr>
<td>{label}</td>
@ -72,54 +118,20 @@ class ModalPublishPreview extends React.PureComponent<Props> {
);
}
togglePreviewEnabled() {
const { enablePublishPreview, setEnablePublishPreview } = this.props;
setEnablePublishPreview(!enablePublishPreview);
const txFee = previewResponse ? previewResponse['total_fee'] : null;
// $FlowFixMe add outputs[0] etc to PublishResponse type
const isOptimizeAvail = filePath && filePath !== '' && isVid && ffmpegStatus.available;
let modalTitle;
if (isStillEditing) {
modalTitle = __('Confirm Edit');
} else if (livestream) {
modalTitle = __('Create Livestream');
} else {
modalTitle = __('Confirm Upload');
}
render() {
const {
filePath,
isMarkdownPost,
optimize,
title,
description,
channel,
bid,
uri,
contentIsFree,
fee,
language,
licenseType,
otherLicenseDescription,
licenseUrl,
tags,
isVid,
ffmpegStatus = {},
previewResponse,
closeModal,
enablePublishPreview,
setEnablePublishPreview,
isStillEditing,
myChannels,
} = this.props;
const txFee = previewResponse ? previewResponse['total_fee'] : null;
// $FlowFixMe add outputs[0] etc to PublishResponse type
const livestream =
// $FlowFixMe
previewResponse.outputs[0] && previewResponse.outputs[0].value && !previewResponse.outputs[0].value.source;
const isOptimizeAvail = filePath && filePath !== '' && isVid && ffmpegStatus.available;
let modalTitle;
if (isStillEditing) {
modalTitle = __('Confirm Edit');
} else if (livestream) {
modalTitle = __('Create Livestream');
} else {
modalTitle = __('Confirm Upload');
}
let confirmBtnText;
let confirmBtnText;
if (!publishing) {
if (isStillEditing) {
confirmBtnText = __('Save');
} else if (livestream) {
@ -127,110 +139,119 @@ class ModalPublishPreview extends React.PureComponent<Props> {
} else {
confirmBtnText = __('Upload');
}
const descriptionValue = description ? (
<div className="media__info-text-preview">
<MarkdownPreview content={description} simpleLinks />
</div>
) : null;
const licenseValue =
licenseType === COPYRIGHT ? (
<p>© {otherLicenseDescription}</p>
) : licenseType === OTHER ? (
<p>
{otherLicenseDescription}
<br />
{licenseUrl}
</p>
) : (
<p>{licenseType}</p>
);
const tagsValue =
// Do nothing for onClick(). Setting to 'null' results in "View Tag" action -- we don't want to leave the modal.
tags.map((tag) => <Tag key={tag.name} title={tag.name} name={tag.name} type={'flow'} onClick={() => {}} />);
const depositValue = bid ? <LbcSymbol postfix={`${bid}`} size={14} /> : <p>---</p>;
let priceValue = __('Free');
if (!contentIsFree) {
if (fee.currency === 'LBC') {
priceValue = <LbcSymbol postfix={fee.amount} />;
} else {
priceValue = `${fee.amount} ${fee.currency}`;
}
} else {
if (isStillEditing) {
confirmBtnText = __('Saving');
} else if (livestream) {
confirmBtnText = __('Creating');
} else {
confirmBtnText = __('Uploading');
}
const channelValue = (channel) => {
const channelClaim = myChannels && myChannels.find((x) => x.name === channel);
return channel ? (
<div className="channel-value">
{channelClaim && <ChannelThumbnail uri={channelClaim.permanent_url} />}
{channel}
</div>
) : (
<div className="channel-value">
<Icon sectionIcon icon={ICONS.ANONYMOUS} />
<i>{__('Anonymous')}</i>
</div>
);
};
return (
<Modal isOpen contentLabel={modalTitle} type="card" onAborted={closeModal}>
<Form onSubmit={() => this.onConfirmed()}>
<Card
title={modalTitle}
body={
<>
<div className="section">
<table className="table table--condensed table--publish-preview">
<tbody>
{!livestream && !isMarkdownPost && this.createRow(__('File'), this.resolveFilePathName(filePath))}
{isOptimizeAvail && this.createRow(__('Transcode'), optimize ? __('Yes') : __('No'))}
{this.createRow(__('Title'), title)}
{this.createRow(__('Description'), descriptionValue)}
{this.createRow(__('Channel'), channelValue(channel))}
{this.createRow(__('URL'), uri)}
{this.createRow(__('Deposit'), depositValue)}
{this.createRow(__('Price'), priceValue)}
{this.createRow(__('Language'), language)}
{this.createRow(__('License'), licenseValue)}
{this.createRow(__('Tags'), tagsValue)}
</tbody>
</table>
</div>
{txFee && (
<div className="section" aria-label={__('Estimated transaction fee:')}>
<b>{__('Est. transaction fee:')}</b>&nbsp;&nbsp;
<em>
<LbcSymbol postfix={txFee} />
</em>
</div>
)}
</>
}
actions={
<>
<div className="section__actions">
<Button autoFocus button="primary" label={confirmBtnText} onClick={() => this.onConfirmed()} />
<Button button="link" label={__('Cancel')} onClick={closeModal} />
</div>
<p className="help">{__('Once the transaction is sent, it cannot be reversed.')}</p>
<FormField
type="checkbox"
name="sync_toggle"
label={__('Skip preview and confirmation')}
checked={!enablePublishPreview}
onChange={() => setEnablePublishPreview(!enablePublishPreview)}
/>
</>
}
/>
</Form>
</Modal>
);
}
}
const descriptionValue = description ? (
<div className="media__info-text-preview">
<MarkdownPreview content={description} simpleLinks />
</div>
) : null;
const licenseValue =
licenseType === COPYRIGHT ? (
<p>© {otherLicenseDescription}</p>
) : licenseType === OTHER ? (
<p>
{otherLicenseDescription}
<br />
{licenseUrl}
</p>
) : (
<p>{licenseType}</p>
);
const tagsValue =
// Do nothing for onClick(). Setting to 'null' results in "View Tag" action -- we don't want to leave the modal.
tags.map((tag) => <Tag key={tag.name} title={tag.name} name={tag.name} type={'flow'} onClick={() => {}} />);
const depositValue = bid ? <LbcSymbol postfix={`${bid}`} size={14} /> : <p>---</p>;
let priceValue = __('Free');
if (!contentIsFree) {
if (fee.currency === 'LBC') {
priceValue = <LbcSymbol postfix={fee.amount} />;
} else {
priceValue = `${fee.amount} ${fee.currency}`;
}
}
const channelValue = (channel) => {
const channelClaim = myChannels && myChannels.find((x) => x.name === channel);
return channel ? (
<div className="channel-value">
{channelClaim && <ChannelThumbnail uri={channelClaim.permanent_url} />}
{channel}
</div>
) : (
<div className="channel-value">
<Icon sectionIcon icon={ICONS.ANONYMOUS} />
<i>{__('Anonymous')}</i>
</div>
);
};
return (
<Modal isOpen contentLabel={modalTitle} type="card" onAborted={closeModal}>
<Form onSubmit={onConfirmed}>
<Card
title={modalTitle}
body={
<>
<div className="section">
<table className="table table--condensed table--publish-preview">
<tbody>
{!livestream && !isMarkdownPost && createRow(__('File'), getFilePathName(filePath))}
{isOptimizeAvail && createRow(__('Transcode'), optimize ? __('Yes') : __('No'))}
{createRow(__('Title'), title)}
{createRow(__('Description'), descriptionValue)}
{createRow(__('Channel'), channelValue(channel))}
{createRow(__('URL'), uri)}
{createRow(__('Deposit'), depositValue)}
{createRow(__('Price'), priceValue)}
{createRow(__('Language'), language)}
{createRow(__('License'), licenseValue)}
{createRow(__('Tags'), tagsValue)}
</tbody>
</table>
</div>
{txFee && (
<div className="section" aria-label={__('Estimated transaction fee:')}>
<b>{__('Est. transaction fee:')}</b>&nbsp;&nbsp;
<em>
<LbcSymbol postfix={txFee} />
</em>
</div>
)}
</>
}
actions={
<>
<div className="section__actions">
<Button autoFocus button="primary" disabled={publishing} label={confirmBtnText} onClick={onConfirmed} />
<Button button="link" label={__('Cancel')} onClick={closeModal} />
</div>
<p className="help">{__('Once the transaction is sent, it cannot be reversed.')}</p>
<FormField
type="checkbox"
name="sync_toggle"
label={__('Skip preview and confirmation')}
checked={!enablePublishPreview}
onChange={() => setEnablePublishPreview(!enablePublishPreview)}
/>
</>
}
/>
</Form>
</Modal>
);
};
export default ModalPublishPreview;

View file

@ -52,10 +52,8 @@ function ModalRemoveFile(props: Props) {
<FormField
name="claim_abandon"
label={
<I18nMessage
tokens={{ lbc: <LbcSymbol prefix={__('reclaim %amount%', { amount: claim.amount })} /> }}
>
Abandon on blockchain (%lbc%)
<I18nMessage tokens={{ lbc: <LbcSymbol postfix={claim.amount} /> }}>
Abandon on blockchain (reclaim %lbc%)
</I18nMessage>
}
type="checkbox"

View file

@ -12,7 +12,7 @@ class ModalTransactionFailed extends React.PureComponent<Props> {
return (
<Modal isOpen contentLabel={__('Transaction failed')} title={__('Transaction failed')} onConfirmed={closeModal}>
<p>{__('Sorry about that. Contact help@lbry.com if you continue to have issues.')}</p>
<p>{__('Sorry about that. Contact help@odysee.com if you continue to have issues.')}</p>
</Modal>
);
}

View file

@ -1,4 +1,5 @@
// @flow
import { SIMPLE_SITE } from 'config';
import * as PAGES from 'constants/pages';
import React from 'react';
import { Modal } from 'modal/modal';
@ -15,18 +16,27 @@ const YoutubeWelcome = (props: Props) => {
<Modal isOpen type="card" onAborted={doHideModal}>
<Confetti recycle={false} style={{ position: 'fixed' }} numberOfPieces={100} />
<Card
title={__("You're free!")}
title={!SIMPLE_SITE ? __("You're free!") : __('Welcome to Odysee')}
subtitle={
<React.Fragment>
<p>
{__("You've escaped the land of spying, censorship, and exploitation.")}
<span className="emoji"> 💩</span>
</p>
<p>
{__('Welcome to the land of content freedom.')}
<span className="emoji"> 🌈</span>
</p>
</React.Fragment>
!SIMPLE_SITE ? (
<React.Fragment>
<p>
{__("You've escaped the land of spying, censorship, and exploitation.")}
<span className="emoji"> 💩</span>
</p>
<p>
{__('Welcome to the land of content freedom.')}
<span className="emoji"> 🌈</span>
</p>
</React.Fragment>
) : (
<React.Fragment>
<p>
{__('You make the party extra special!')}
<span className="emoji"> 💖</span>
</p>
</React.Fragment>
)
}
actions={
<div className="card__actions">

12
ui/page/adsTest/index.js Normal file
View file

@ -0,0 +1,12 @@
import { connect } from 'react-redux';
import { makeSelectClaimForUri, doResolveUri } from 'lbry-redux';
import AdsTestPage from './view';
export default connect(
(state) => ({
claim: makeSelectClaimForUri('lbry://fullscreenrelease#7')(state),
}),
{
doResolveUri,
}
)(AdsTestPage);

20
ui/page/adsTest/view.jsx Normal file
View file

@ -0,0 +1,20 @@
// @flow
// import React from 'react';
// import FilePage from 'page/file';
type Props = {
doResolveUri: (string) => void,
claim: ?StreamClaim,
};
export default function AdsTestPage(props: Props) {
return null;
// const { doResolveUri, claim } = props;
// const hasClaim = claim !== undefined;
// React.useEffect(() => {
// if (!hasClaim) {
// doResolveUri('lbry://fullscreenrelease#7');
// }
// }, [hasClaim, doResolveUri]);
// return <div>{hasClaim && <FilePage uri="lbry://fullscreenrelease#7" />}</div>;
}

View file

@ -18,7 +18,8 @@ const MOONPAY_KEY = process.env.MOONPAY_SECRET_KEY;
const COUNTRIES = Array.from(
new Set(
countryData.all
.map(country => country.name)
.filter((country) => country.status !== 'deleted')
.map((country) => country.name)
.sort((a, b) => {
if (a > b) {
return 1;
@ -39,7 +40,7 @@ type Props = {
email: string,
user: ?User,
doGetNewAddress: () => void,
doUserSetCountry: string => void,
doUserSetCountry: (string) => void,
};
export default function BuyPage(props: Props) {
@ -65,7 +66,7 @@ export default function BuyPage(props: Props) {
React.useEffect(() => {
if (MOONPAY_KEY && !url && receiveAddress) {
let url = `https://buy.moonpay.io?apiKey=pk_live_xNFffrN5NWKy6fu0ggbV8VQIwRieRzy&colorCode=%23257761&currencyCode=lbc&showWalletAddressForm=true&walletAddress=${receiveAddress}`;
let url = `https://buy.moonpay.io?apiKey=pk_live_xNFffrN5NWKy6fu0ggbV8VQIwRieRzy&colorCode=%23fa6165&currencyCode=lbc&showWalletAddressForm=true&walletAddress=${receiveAddress}`;
if (email) {
url += `&email=${encodeURIComponent(email)}`;
}
@ -148,7 +149,7 @@ export default function BuyPage(props: Props) {
'Only some countries are eligible at this time. We are working to make this available to everyone.'
)}
value={country}
onChange={e => setCountry(e.target.value)}
onChange={(e) => setCountry(e.target.value)}
>
<option value="" disabled defaultValue>
{__('Select your country')}

View file

@ -109,7 +109,7 @@ export default function ChannelsPage(props: Props) {
subtitle={__("You haven't created a channel yet. All of your beautiful channels will be listed here!")}
actions={
<div className="section__actions">
<Button button="primary" label={__('New Channel')} navigate={`/$/${PAGES.CHANNEL_NEW}`} />
<Button button="primary" label={__('Create Channel')} navigate={`/$/${PAGES.CHANNEL_NEW}`} />
</div>
}
/>

View file

@ -23,6 +23,7 @@ function ChannelsFollowingPage(props: Props) {
) : (
<Page noFooter fullWidthPage={tileLayout}>
<ClaimListDiscover
hideAdvancedFilter
tileLayout={tileLayout}
headerLabel={
<span>
@ -31,7 +32,7 @@ function ChannelsFollowingPage(props: Props) {
</span>
}
defaultOrderBy={ORDER_BY_NEW}
channelIds={subscribedChannels.map(sub => sub.uri.split('#')[1])}
channelIds={subscribedChannels.map((sub) => sub.uri.split('#')[1])}
meta={
<Button
icon={ICONS.SEARCH}

View file

@ -1,13 +1,12 @@
// @flow
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
// import * as ICONS from 'constants/icons';
// import * as PAGES from 'constants/pages';
import * as CS from 'constants/claim_search';
import React from 'react';
import Page from 'component/page';
import Button from 'component/button';
import ClaimTilesDiscover from 'component/claimTilesDiscover';
// import Button from 'component/button';
import ClaimListDiscover from 'component/claimListDiscover';
import * as CS from 'constants/claim_search';
import { toCapitalCase } from 'util/string';
// import { toCapitalCase } from 'util/string';
import { SIMPLE_SITE } from 'config';
const MORE_CHANNELS_ANCHOR = 'MoreChannels';
@ -19,89 +18,87 @@ type Props = {
homepageData: any,
};
type ChannelsFollowingItem = {
title: string,
link?: string,
help?: any,
options?: {},
};
function ChannelsFollowingDiscover(props: Props) {
const { followedTags, subscribedChannels, blockedChannels, homepageData } = props;
const {
// followedTags,
// subscribedChannels,
// blockedChannels,
homepageData,
} = props;
const { PRIMARY_CONTENT_CHANNEL_IDS } = homepageData;
let rowData: Array<ChannelsFollowingItem> = [];
const notChannels = subscribedChannels
.map(({ uri }) => uri)
.concat(blockedChannels)
.map(uri => uri.split('#')[1]);
// let rowData: Array<ChannelsFollowingItem> = [];
// const notChannels = subscribedChannels
// .map(({ uri }) => uri)
// .concat(blockedChannels)
// .map(uri => uri.split('#')[1]);
rowData.push({
title: 'Top Channels Of All Time',
link: `/$/${PAGES.DISCOVER}?claim_type=channel&${CS.ORDER_BY_KEY}=${CS.ORDER_BY_TOP}&${CS.FRESH_KEY}=${CS.FRESH_ALL}`,
options: {
pageSize: 12,
claimType: 'channel',
orderBy: ['effective_amount'],
},
});
// rowData.push({
// title: 'Top Channels Of All Time',
// link: `/$/${PAGES.DISCOVER}?claim_type=channel&${CS.ORDER_BY_KEY}=${CS.ORDER_BY_TOP}&${CS.FRESH_KEY}=${CS.FRESH_ALL}`,
// options: {
// pageSize: 12,
// claimType: 'channel',
// orderBy: ['effective_amount'],
// },
// });
rowData.push({
title: 'Latest From @lbrycast',
link: `/@lbrycast:4`,
options: {
orderBy: ['release_time'],
pageSize: 8,
channelIds: ['4c29f8b013adea4d5cca1861fb2161d5089613ea'],
},
});
// rowData.push({
// title: 'Latest From @lbrycast',
// link: `/@lbrycast:4`,
// options: {
// orderBy: ['release_time'],
// pageSize: 8,
// channelIds: ['4c29f8b013adea4d5cca1861fb2161d5089613ea'],
// },
// });
rowData.push({
title: 'Trending Channels',
link: `/$/${PAGES.DISCOVER}?claim_type=channel`,
options: {
pageSize: 8,
claimType: 'channel',
orderBy: ['trending_group', 'trending_mixed'],
},
});
// rowData.push({
// title: 'Trending Channels',
// link: `/$/${PAGES.DISCOVER}?claim_type=channel`,
// options: {
// pageSize: 8,
// claimType: 'channel',
// orderBy: ['trending_group', 'trending_mixed'],
// },
// });
if (followedTags.length > 0 && followedTags.length < 5) {
const followedRows = followedTags.map((tag: Tag) => ({
title: `Trending Channels for #${toCapitalCase(tag.name)}`,
link: `/$/${PAGES.DISCOVER}?t=${tag.name}&claim_type=channel`,
options: {
claimType: 'channel',
pageSize: 4,
tags: [tag.name],
},
}));
rowData.push(...followedRows);
}
// if (followedTags.length > 0 && followedTags.length < 5) {
// const followedRows = followedTags.map((tag: Tag) => ({
// title: `Trending Channels for #${toCapitalCase(tag.name)}`,
// link: `/$/${PAGES.DISCOVER}?t=${tag.name}&claim_type=channel`,
// options: {
// claimType: 'channel',
// pageSize: 4,
// tags: [tag.name],
// },
// }));
// rowData.push(...followedRows);
// }
if (followedTags.length > 4) {
rowData.push({
title: 'Trending For Your Tags',
link: `/$/${PAGES.TAGS_FOLLOWING}?claim_type=channel`,
options: {
claimType: 'channel',
tags: followedTags.map(tag => tag.name),
},
});
}
// if (followedTags.length > 4) {
// rowData.push({
// title: 'Trending For Your Tags',
// link: `/$/${PAGES.TAGS_FOLLOWING}?claim_type=channel`,
// options: {
// claimType: 'channel',
// tags: followedTags.map(tag => tag.name),
// },
// });
// }
const rowDataWithGenericOptions = rowData.map(row => {
return {
...row,
options: {
...row.options,
notChannels,
},
};
});
// const rowDataWithGenericOptions = rowData.map(row => {
// return {
// ...row,
// options: {
// ...row.options,
// notChannels,
// },
// };
// });
return (
<Page>
{rowDataWithGenericOptions.map(({ title, link, help, options = {} }) => (
{/* {rowDataWithGenericOptions.map(({ title, link, help, options = {} }) => (
<div key={title} className="claim-grid__wrapper">
<h1 className="section__actions">
{link ? (
@ -120,17 +117,17 @@ function ChannelsFollowingDiscover(props: Props) {
<ClaimTilesDiscover {...options} />
</div>
))}
<h1 id={MORE_CHANNELS_ANCHOR} className="claim-grid__title">
{__('More Channels')}
</h1>
{/* odysee: claimIds = PRIMARY_CONTENT_CHANNEL_IDS if simplesite CLD */}
))} */}
<ClaimListDiscover
defaultOrderBy={CS.ORDER_BY_TRENDING}
defaultFreshness={CS.FRESH_ALL}
claimType={CS.CLAIM_CHANNEL}
claimIds={SIMPLE_SITE ? PRIMARY_CONTENT_CHANNEL_IDS : undefined}
scrollAnchor={MORE_CHANNELS_ANCHOR}
maxPages={3}
hideFilters
header={<h1 className="section__title">{__('Moon cheese is an acquired taste')}</h1>}
/>
</Page>
);

View file

@ -1,6 +1,7 @@
// @flow
import { SHOW_ADS, DOMAIN, SIMPLE_SITE } from 'config';
import * as ICONS from 'constants/icons';
import * as CS from 'constants/claim_search';
import React, { useRef } from 'react';
import Page from 'component/page';
import ClaimListDiscover from 'component/claimListDiscover';
@ -10,18 +11,18 @@ import { useIsMobile } from 'effects/use-screensize';
import analytics from 'analytics';
import HiddenNsfw from 'component/common/hidden-nsfw';
import Icon from 'component/common/icon';
import * as CS from 'constants/claim_search';
import Ads from 'web/component/ads';
import LbcSymbol from 'component/common/lbc-symbol';
import I18nMessage from 'component/i18nMessage';
import moment from 'moment';
type Props = {
location: { search: string },
followedTags: Array<Tag>,
repostedUri: string,
repostedClaim: ?GenericClaim,
doToggleTagFollowDesktop: string => void,
doResolveUri: string => void,
doToggleTagFollowDesktop: (string) => void,
doResolveUri: (string) => void,
isAuthenticated: boolean,
dynamicRouteProps: RowDataItem,
tileLayout: boolean,
@ -87,8 +88,8 @@ function DiscoverPage(props: Props) {
} else {
headerLabel = (
<span>
<Icon icon={(dynamicRouteProps && dynamicRouteProps.icon) || ICONS.DISCOVER} size={10} />
{(dynamicRouteProps && dynamicRouteProps.title) || __('All Content')}
<Icon icon={(dynamicRouteProps && dynamicRouteProps.icon) || ICONS.WILD_WEST} size={10} />
{(dynamicRouteProps && dynamicRouteProps.title) || __('Wild West')}
</span>
);
}
@ -96,9 +97,11 @@ function DiscoverPage(props: Props) {
return (
<Page noFooter fullWidthPage={tileLayout}>
<ClaimListDiscover
limitClaimsPerChannel={3}
hideAdvancedFilter
hideFilters={!dynamicRouteProps}
header={repostedUri ? <span /> : undefined}
tileLayout={repostedUri ? false : tileLayout}
defaultOrderBy={dynamicRouteProps ? undefined : CS.ORDER_BY_TRENDING}
claimType={claimType ? [claimType] : undefined}
headerLabel={headerLabel}
tags={tags}
@ -107,9 +110,18 @@ function DiscoverPage(props: Props) {
injectedItem={
SHOW_ADS && IS_WEB ? (SIMPLE_SITE ? false : !isAuthenticated && <Ads small type={'video'} />) : false
}
// Assume wild west page if no dynamicRouteProps
// Not a very good solution, but just doing it for now
// until we are sure this page will stay around
releaseTime={!dynamicRouteProps && `>${Math.floor(moment().subtract(1, 'day').startOf('week').unix())}`}
feeAmount={!dynamicRouteProps && CS.FEE_AMOUNT_ANY}
channelIds={
(dynamicRouteProps && dynamicRouteProps.options && dynamicRouteProps.options.channelIds) || undefined
}
limitClaimsPerChannel={
(dynamicRouteProps && dynamicRouteProps.options && dynamicRouteProps.options.limitClaimsPerChannel) ||
undefined
}
meta={
!dynamicRouteProps ? (
<a

View file

@ -8,6 +8,8 @@ import {
makeSelectClaimIsNsfw,
SETTINGS,
makeSelectTagInClaimOrChannelForUri,
makeSelectClaimIsMine,
makeSelectClaimIsStreamPlaceholder,
} from 'lbry-redux';
import { makeSelectCostInfoForUri, doFetchCostInfoForUri } from 'lbryinc';
import { selectShowMatureContent, makeSelectClientSetting } from 'redux/selectors/settings';
@ -32,14 +34,16 @@ const select = (state, props) => {
renderMode: makeSelectFileRenderModeForUri(props.uri)(state),
videoTheaterMode: makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)(state),
commentsDisabled: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_COMMENTS_TAG)(state),
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
};
};
const perform = dispatch => ({
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
setViewed: uri => dispatch(doSetContentHistoryItem(uri)),
setPrimaryUri: uri => dispatch(doSetPrimaryUri(uri)),
const perform = (dispatch) => ({
fetchFileInfo: (uri) => dispatch(doFetchFileInfo(uri)),
fetchCostInfo: (uri) => dispatch(doFetchCostInfoForUri(uri)),
setViewed: (uri) => dispatch(doSetContentHistoryItem(uri)),
setPrimaryUri: (uri) => dispatch(doSetPrimaryUri(uri)),
});
export default withRouter(connect(select, perform)(FilePage));

View file

@ -1,4 +1,5 @@
// @flow
import * as PAGES from 'constants/pages';
import * as React from 'react';
import classnames from 'classnames';
import Page from 'component/page';
@ -10,6 +11,9 @@ import FileRenderDownload from 'component/fileRenderDownload';
import RecommendedContent from 'component/recommendedContent';
import CommentsList from 'component/commentsList';
import PostViewer from 'component/postViewer';
import { Redirect } from 'react-router';
import Button from 'component/button';
import I18nMessage from 'component/i18nMessage';
import Empty from 'component/common/empty';
export const PRIMARY_PLAYER_WRAPPER_CLASS = 'file-page__video-container';
@ -27,7 +31,10 @@ type Props = {
linkedComment: any,
setPrimaryUri: (?string) => void,
videoTheaterMode: boolean,
claim: ?Claim,
claimIsMine: boolean,
commentsDisabled: boolean,
isLivestream: boolean,
};
function FilePage(props: Props) {
@ -44,7 +51,10 @@ function FilePage(props: Props) {
linkedComment,
setPrimaryUri,
videoTheaterMode,
claimIsMine,
commentsDisabled,
isLivestream,
} = props;
const cost = costInfo ? costInfo.cost : null;
const hasFileInfo = fileInfo !== undefined;
@ -113,6 +123,10 @@ function FilePage(props: Props) {
);
}
if (!claimIsMine && isLivestream) {
return <Redirect to={`/$/${PAGES.LIVESTREAM}`} />;
}
if (obscureNsfw && isMature) {
return (
<Page>
@ -129,6 +143,18 @@ function FilePage(props: Props) {
{!isMarkdown && (
<div className="file-page__secondary-content">
<div>
{claimIsMine && isLivestream && (
<div className="livestream__creator-message">
<h4>{__('Only visible to you')}</h4>
<I18nMessage>
People who view this link will be redirected to your livestream. Make sure to use this for sharing
so your title and thumbnail are displayed properly.
</I18nMessage>
<div className="section__actions">
<Button button="primary" navigate={`/$/${PAGES.LIVESTREAM}`} label={__('View livestream')} />
</div>
</div>
)}
{RENDER_MODES.FLOATING_MODES.includes(renderMode) && <FileTitleSection uri={uri} />}
{commentsDisabled && <Empty text={__('The creator of this content has disabled comments.')} />}
{!commentsDisabled && <CommentsList uri={uri} linkedComment={linkedComment} />}

View file

@ -1,4 +1,5 @@
// @flow
import { SITE_NAME } from 'config';
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import * as React from 'react';
@ -129,19 +130,13 @@ class HelpPage extends React.PureComponent<Props, State> {
return (
<Page className="card-stack">
<Card
title={__('Read the FAQ')}
subtitle={__('Our FAQ answers many common questions.')}
title={__('Visit the %SITE_NAME% Help Hub', { SITE_NAME })}
subtitle={__('Our support posts answer many common questions.')}
actions={
<div className="section__actions">
<Button
href="https://lbry.com/faq/lbry-basics"
label={__('Read the App Basics FAQ')}
icon={ICONS.HELP}
button="secondary"
/>
<Button
href="https://lbry.com/faq"
label={__('View all LBRY FAQs')}
href="https://odysee.com/@OdyseeHelp:b"
label={__('View %SITE_NAME% Help Hub', { SITE_NAME })}
icon={ICONS.HELP}
button="secondary"
/>
@ -160,7 +155,7 @@ class HelpPage extends React.PureComponent<Props, State> {
actions={
<div className="section__actions">
<Button button="secondary" label={__('Join Our Chat')} icon={ICONS.CHAT} href="https://chat.lbry.com" />
<Button button="secondary" label={__('Email Us')} icon={ICONS.WEB} href="mailto:help@lbry.com" />
<Button button="secondary" label={__('Email Us')} icon={ICONS.WEB} href="mailto:help@odysee.com" />
</div>
}
/>
@ -210,7 +205,6 @@ class HelpPage extends React.PureComponent<Props, State> {
/>
<WalletBackup />
{/* @endif */}
<Card
title={__('About --[About section in Help Page]--')}
@ -295,6 +289,7 @@ class HelpPage extends React.PureComponent<Props, State> {
</div>
}
/>
{/* @endif */}
</Page>
);
}

View file

@ -1,17 +1,16 @@
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
import { selectFollowedTags } from 'redux/selectors/tags';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectSubscriptions } from 'redux/selectors/subscriptions';
import { makeSelectClientSetting, selectHomepageData } from 'redux/selectors/settings';
import { selectShowMatureContent, selectHomepageData } from 'redux/selectors/settings';
import DiscoverPage from './view';
const select = state => ({
const select = (state) => ({
followedTags: selectFollowedTags(state),
subscribedChannels: selectSubscriptions(state),
authenticated: selectUserVerifiedEmail(state),
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
showNsfw: selectShowMatureContent(state),
homepageData: selectHomepageData(state),
});

View file

@ -79,9 +79,14 @@ function HomePage(props: Props) {
</p>
</div>
)}
<h1 className="home__meme">
<Button button="link" label={'HelpLBRYSaveCrypto.com'} href="https://helplbrysavecrypto.com" />
</h1>
{rowData.map(({ title, route, link, icon, help, options = {} }, index) => (
<div key={title} className="claim-grid__wrapper">
{title && (
{index !== 0 && title && typeof title === 'string' && (
<h1 className="claim-grid__header">
<Button navigate={route || link} button="link">
{icon && <Icon className="claim-grid__header-icon" sectionIcon icon={icon} size={20} />}
@ -91,8 +96,8 @@ function HomePage(props: Props) {
</h1>
)}
<ClaimTilesDiscover {...options} />
{link && (
<ClaimTilesDiscover {...options} pin={route === `/$/${PAGES.GENERAL}`} />
{(route || link) && (
<Button
className="claim-grid__title--secondary"
button="link"

View file

@ -4,6 +4,7 @@ import React from 'react';
import Page from 'component/page';
import LivestreamLayout from 'component/livestreamLayout';
import analytics from 'analytics';
import { Lbry } from 'lbry-redux';
type Props = {
uri: string,
@ -19,8 +20,41 @@ export default function LivestreamPage(props: Props) {
const [activeViewers, setActiveViewers] = React.useState(0);
const [isLive, setIsLive] = React.useState(false);
const livestreamChannelId = channelClaim && channelClaim.signing_channel && channelClaim.signing_channel.claim_id;
const [hasLivestreamClaim, setHasLivestreamClaim] = React.useState(false);
const STREAMING_POLL_INTERVAL_IN_MS = 10000;
const LIVESTREAM_CLAIM_POLL_IN_MS = 60000;
// the component needs to check if the channel has published a new livestream, so we know if it should check
React.useEffect(() => {
let checkClaimsInterval;
function checkHasLivestreamClaim() {
Lbry.claim_search({
channel_ids: [livestreamChannelId],
has_no_source: true,
claim_type: ['stream'],
})
.then((res) => {
if (res && res.items && res.items.length > 0) {
setHasLivestreamClaim(true);
}
})
.catch(() => {});
}
if (livestreamChannelId && !isLive) {
if (!checkClaimsInterval) checkHasLivestreamClaim();
checkClaimsInterval = setInterval(checkHasLivestreamClaim, LIVESTREAM_CLAIM_POLL_IN_MS);
return () => {
if (checkClaimsInterval) {
clearInterval(checkClaimsInterval);
}
};
}
}, [livestreamChannelId, isLive]);
React.useEffect(() => {
let interval;
function checkIsLive() {
// $FlowFixMe Bitwave's API can handle garbage
fetch(`${BITWAVE_API}/${livestreamChannelId}`)
@ -38,19 +72,17 @@ export default function LivestreamPage(props: Props) {
}
});
}
let interval;
if (livestreamChannelId) {
if (livestreamChannelId && hasLivestreamClaim) {
if (!interval) checkIsLive();
interval = setInterval(checkIsLive, 10 * 1000);
}
interval = setInterval(checkIsLive, STREAMING_POLL_INTERVAL_IN_MS);
return () => {
if (interval) {
clearInterval(interval);
}
};
}, [livestreamChannelId]);
return () => {
if (interval) {
clearInterval(interval);
}
};
}
}, [livestreamChannelId, hasLivestreamClaim]);
const stringifiedClaim = JSON.stringify(claim);
React.useEffect(() => {

View file

@ -15,6 +15,7 @@ import CopyableText from 'component/copyableText';
import Card from 'component/common/card';
import ClaimList from 'component/claimList';
import usePersistedState from 'effects/use-persisted-state';
import usePrevious from 'effects/use-previous';
type Props = {
channels: Array<ChannelClaim>,
@ -24,15 +25,141 @@ type Props = {
};
export default function LivestreamSetupPage(props: Props) {
const LIVESTREAM_CLAIM_POLL_IN_MS = 60000;
const { channels, fetchingChannels, activeChannelClaim, pendingClaims } = props;
const [sigData, setSigData] = React.useState({ signature: undefined, signing_ts: undefined });
const [showHelpTest, setShowHelpTest] = usePersistedState('livestream-help-seen', true);
const [spin, setSpin] = React.useState(true);
const [livestreamClaims, setLivestreamClaims] = React.useState([]);
const hasChannels = channels && channels.length > 0;
const activeChannelClaimStr = JSON.stringify(activeChannelClaim);
function createStreamKey() {
if (!activeChannelClaim || !sigData.signature || !sigData.signing_ts) return null;
return `${activeChannelClaim.claim_id}?d=${toHex(activeChannelClaim.name)}&s=${sigData.signature}&t=${
sigData.signing_ts
}`;
}
const streamKey = createStreamKey();
const pendingLiveStreamClaims = pendingClaims
? pendingClaims.filter(
(claim) =>
// $FlowFixMe
claim.value_type === 'stream' && !(claim.value && claim.value.source)
)
: [];
const [localPending, setLocalPending] = React.useState([]); //
const localPendingStr = JSON.stringify(localPending);
const pendingLivestreamClaimsStr = JSON.stringify(pendingLiveStreamClaims);
const prevPendingLiveStreamClaimStr = usePrevious(pendingLivestreamClaimsStr);
const liveStreamClaimsStr = JSON.stringify(livestreamClaims);
const prevLiveStreamClaimsStr = JSON.stringify(liveStreamClaimsStr);
const pendingLength = pendingLiveStreamClaims.length;
// maintain a pendingClaims list by channelId that locally that things are removed from only when they show up in claim_search results.
React.useEffect(() => {
if (activeChannelClaimStr) {
const channelClaim = JSON.parse(activeChannelClaimStr);
// ensure we have a channel
if (channelClaim.claim_id) {
Lbry.channel_sign({
channel_id: channelClaim.claim_id,
hexdata: toHex(channelClaim.name),
})
.then((data) => {
setSigData(data);
})
.catch((error) => {
setSigData({ signature: null, signing_ts: null });
});
}
}
}, [activeChannelClaimStr, setSigData]);
// The following 2 effects handle the time between pending disappearing and claim_search being able to find it.
// We'll maintain our own pending list:
// add to it when there are new things in pending
// remove items only when our claim_search finds it
React.useEffect(() => {
// add to localPending when pending changes
const localPending = JSON.parse(localPendingStr);
const pendingLivestreamClaims = JSON.parse(pendingLivestreamClaimsStr);
if (
pendingLiveStreamClaims !== prevPendingLiveStreamClaimStr ||
(pendingLivestreamClaims.length && !localPending.length)
) {
const prevPendingLivestreamClaims = prevPendingLiveStreamClaimStr
? JSON.parse(prevPendingLiveStreamClaimStr)
: [];
const pendingClaimIds = pendingLivestreamClaims.map((claim) => claim.claim_id);
const prevPendingClaimIds = prevPendingLivestreamClaims.map((claim) => claim.claim_id);
const newLocalPending = [];
if (pendingClaimIds.length > prevPendingClaimIds.length) {
pendingLivestreamClaims.forEach((pendingClaim) => {
if (!localPending.some((lClaim) => lClaim.claim_id === pendingClaim.claim_id)) {
newLocalPending.push(pendingClaim);
}
});
setLocalPending(localPending.concat(newLocalPending));
}
}
}, [pendingLivestreamClaimsStr, prevPendingLiveStreamClaimStr, localPendingStr, setLocalPending]);
React.useEffect(() => {
// remove from localPending when livestreamClaims found
const localPending = JSON.parse(localPendingStr);
if (liveStreamClaimsStr !== prevLiveStreamClaimsStr && localPending.length) {
const livestreamClaims = JSON.parse(liveStreamClaimsStr);
setLocalPending(
localPending.filter((pending) => !livestreamClaims.some((claim) => claim.claim_id === pending.claim_id))
);
}
}, [liveStreamClaimsStr, prevLiveStreamClaimsStr, localPendingStr, setLocalPending]);
React.useEffect(() => {
let checkClaimsInterval;
if (!activeChannelClaimStr) return;
const channelClaim = JSON.parse(activeChannelClaimStr);
function checkLivestreamClaims() {
Lbry.claim_search({
channel_ids: [channelClaim.claim_id],
has_no_source: true,
claim_type: ['stream'],
})
.then((res) => {
if (res && res.items && res.items.length > 0) {
setLivestreamClaims(res.items.reverse());
} else {
setLivestreamClaims([]);
}
setSpin(false);
})
.catch(() => {
setLivestreamClaims([]);
setSpin(false);
});
}
if (!checkClaimsInterval) {
checkLivestreamClaims();
checkClaimsInterval = setInterval(checkLivestreamClaims, LIVESTREAM_CLAIM_POLL_IN_MS);
}
return () => {
if (checkClaimsInterval) {
clearInterval(checkClaimsInterval);
}
};
}, [activeChannelClaimStr, pendingLength, setSpin]);
const activeChannelId = activeChannelClaim && activeChannelClaim.claim_id;
const localPendingForChannel = localPending.filter(
(claim) => claim.signing_channel && claim.signing_channel.claim_id === activeChannelId
);
const totalLivestreamClaims = localPendingForChannel.concat(livestreamClaims);
const helpText = (
<div className="section__subtitle">
@ -76,67 +203,10 @@ export default function LivestreamSetupPage(props: Props) {
<p>{__(`Click Save and you are done!`)}</p>
</div>
);
React.useEffect(() => {
if (activeChannelClaimStr) {
const channelClaim = JSON.parse(activeChannelClaimStr);
// ensure we have a channel
if (channelClaim.claim_id) {
Lbry.channel_sign({
channel_id: channelClaim.claim_id,
hexdata: toHex(channelClaim.name),
})
.then((data) => {
setSigData(data);
})
.catch((error) => {
setSigData({ signature: null, signing_ts: null });
});
}
}
}, [activeChannelClaimStr, setSigData]);
function createStreamKey() {
if (!activeChannelClaim || !sigData.signature || !sigData.signing_ts) return null;
return `${activeChannelClaim.claim_id}?d=${toHex(activeChannelClaim.name)}&s=${sigData.signature}&t=${
sigData.signing_ts
}`;
}
const [livestreamClaims, setLivestreamClaims] = React.useState([]);
const pendingLiveStreamClaims =
// $FlowFixMe
pendingClaims ? pendingClaims.filter((claim) => !(claim && claim.value && claim.value.source)) : [];
const pendingLength = pendingLiveStreamClaims.length;
const totalLivestreamClaims = pendingLiveStreamClaims.concat(livestreamClaims);
React.useEffect(() => {
if (!activeChannelClaimStr) return;
const channelClaim = JSON.parse(activeChannelClaimStr);
Lbry.claim_search({
channel_ids: [channelClaim.claim_id],
has_no_source: true,
claim_type: ['stream'],
})
.then((res) => {
if (res && res.items && res.items.length > 0) {
setLivestreamClaims(res.items.reverse());
} else {
setLivestreamClaims([]);
}
setSpin(false);
})
.catch(() => {
setLivestreamClaims([]);
setSpin(false);
});
}, [activeChannelClaimStr, pendingLength, setSpin]);
return (
<Page>
{fetchingChannels && (
{(fetchingChannels || spin) && (
<div className="main--empty">
<Spinner delayed />
</div>
@ -178,6 +248,7 @@ export default function LivestreamSetupPage(props: Props) {
)}
{streamKey && totalLivestreamClaims.length > 0 && (
<Card
className="section"
title={__('Your stream key')}
actions={
<>
@ -201,10 +272,26 @@ export default function LivestreamSetupPage(props: Props) {
)}
{totalLivestreamClaims.length > 0 ? (
<ClaimList
header={__('Your livestream uploads')}
uris={totalLivestreamClaims.map((claim) => claim.permanent_url)}
/>
<>
{Boolean(localPendingForChannel.length) && (
<div className="section">
<ClaimList
header={__('Your pending livestream uploads')}
uris={localPendingForChannel.map((claim) => claim.permanent_url)}
/>
</div>
)}
{Boolean(livestreamClaims.length) && (
<div className="section">
<ClaimList
header={__('Your livestream uploads')}
uris={livestreamClaims
.filter((c) => !pendingLiveStreamClaims.some((p) => p.permanent_url === c.permanent_url))
.map((claim) => claim.permanent_url)}
/>
</div>
)}
</>
) : (
<Yrbl
className="livestream__publish-intro"

View file

@ -78,8 +78,8 @@ class RewardsPage extends PureComponent<Props> {
rewards_faq: <Button button="link" label={__('Rewards FAQ')} href="https://lbry.com/faq/support" />,
}}
>
Please review the %rewards_faq% for eligibility, and send us an email to help@lbry.com if you continue
to see this message. You can continue to use LBRY without this feature.
Please review the %rewards_faq% for eligibility, and send us an email to help@odysee.com if you
continue to see this message. You can continue to use LBRY without this feature.
</I18nMessage>
{`${__('Enjoy all the awesome free content in the meantime!')}`}
</p>
@ -156,7 +156,7 @@ class RewardsPage extends PureComponent<Props> {
'card--disabled': isNotEligible,
})}
>
{rewards.map(reward => (
{rewards.map((reward) => (
<RewardTile key={reward.claim_code} reward={reward} />
))}
{this.renderCustomRewardCode()}

View file

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { doToast, SETTINGS } from 'lbry-redux';
import { doToast } from 'lbry-redux';
import { withRouter } from 'react-router';
import { doSearch } from 'redux/actions/search';
import {
@ -8,37 +8,32 @@ import {
makeSelectQueryWithOptions,
selectSearchOptions,
} from 'redux/selectors/search';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { selectShowMatureContent } from 'redux/selectors/settings';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import analytics from 'analytics';
import SearchPage from './view';
const select = (state, props) => {
const showMature = makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state);
const urlParams = new URLSearchParams(props.location.search);
let urlQuery = urlParams.get('q') || null;
if (urlQuery) {
urlQuery = urlQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
}
const query = makeSelectQueryWithOptions(
urlQuery,
showMature === false ? { nsfw: false, isBackgroundSearch: false } : { isBackgroundSearch: false }
)(state);
const query = makeSelectQueryWithOptions(urlQuery, { nsfw: false, isBackgroundSearch: false })(state);
const uris = makeSelectSearchUris(query)(state);
return {
isSearching: selectIsSearching(state),
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
showNsfw: selectShowMatureContent(state),
uris: uris,
isAuthenticated: selectUserVerifiedEmail(state),
searchOptions: selectSearchOptions(state),
};
};
const perform = dispatch => ({
const perform = (dispatch) => ({
search: (query, options) => dispatch(doSearch(query, options)),
onFeedbackPositive: query => {
onFeedbackPositive: (query) => {
analytics.apiSearchFeedback(query, 1);
dispatch(
doToast({
@ -46,7 +41,7 @@ const perform = dispatch => ({
})
);
},
onFeedbackNegative: query => {
onFeedbackNegative: (query) => {
analytics.apiSearchFeedback(query, 0);
dispatch(
doToast({

View file

@ -24,8 +24,8 @@ type Props = {
isSearching: boolean,
location: UrlLocation,
uris: Array<string>,
onFeedbackNegative: string => void,
onFeedbackPositive: string => void,
onFeedbackNegative: (string) => void,
onFeedbackPositive: (string) => void,
showNsfw: boolean,
isAuthenticated: boolean,
};
@ -38,7 +38,7 @@ export default function SearchPage(props: Props) {
onFeedbackNegative,
location,
isSearching,
showNsfw,
// showNsfw,
isAuthenticated,
searchOptions,
} = props;
@ -47,12 +47,9 @@ export default function SearchPage(props: Props) {
const urlQuery = urlParams.get('q') || '';
const additionalOptions: AdditionalOptions = { isBackgroundSearch: false };
additionalOptions['nsfw'] = showNsfw;
additionalOptions['nsfw'] = false;
const modifiedUrlQuery = urlQuery
.trim()
.replace(/\s+/g, '')
.replace(/:/g, '#');
const modifiedUrlQuery = urlQuery.trim().replace(/\s+/g, '').replace(/:/g, '#');
const uriFromQuery = `lbry://${modifiedUrlQuery}`;
let streamName;
@ -72,7 +69,7 @@ export default function SearchPage(props: Props) {
try {
const dummyUrlForClaimId = `x#${urlQuery}`;
({ claimId } = parseURI(dummyUrlForClaimId));
Lbry.claim_search({ claim_id: claimId }).then(res => {
Lbry.claim_search({ claim_id: claimId }).then((res) => {
if (res.items && res.items.length) {
const claim = res.items[0];
const url = formatLbryUrlForWeb(claim.canonical_url);
@ -105,21 +102,23 @@ export default function SearchPage(props: Props) {
SHOW_ADS && IS_WEB ? (SIMPLE_SITE ? false : !isAuthenticated && <Ads small type={'video'} />) : false
}
headerAltControls={
<>
<span>{__('Find what you were looking for?')}</span>
<Button
button="alt"
description={__('Yes')}
onClick={() => onFeedbackPositive(urlQuery)}
icon={ICONS.YES}
/>
<Button
button="alt"
description={__('No')}
onClick={() => onFeedbackNegative(urlQuery)}
icon={ICONS.NO}
/>
</>
!SIMPLE_SITE && (
<>
<span>{__('Find what you were looking for?')}</span>
<Button
button="alt"
description={__('Yes')}
onClick={() => onFeedbackPositive(urlQuery)}
icon={ICONS.YES}
/>
<Button
button="alt"
description={__('No')}
onClick={() => onFeedbackNegative(urlQuery)}
icon={ICONS.NO}
/>
</>
)
}
/>
{isSearching && new Array(5).fill(1).map((x, i) => <ClaimPreview key={i} placeholder="loading" />)}

View file

@ -10,7 +10,12 @@ import {
doExitSettingsPage,
} from 'redux/actions/settings';
import { doSetPlayingUri } from 'redux/actions/content';
import { makeSelectClientSetting, selectDaemonSettings, selectLanguage } from 'redux/selectors/settings';
import {
makeSelectClientSetting,
selectDaemonSettings,
selectLanguage,
selectShowMatureContent,
} from 'redux/selectors/settings';
import { doWalletStatus, selectWalletIsEncrypted, SETTINGS } from 'lbry-redux';
import SettingsPage from './view';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
@ -19,7 +24,7 @@ const select = (state) => ({
daemonSettings: selectDaemonSettings(state),
allowAnalytics: selectAllowAnalytics(state),
isAuthenticated: selectUserVerifiedEmail(state),
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
showNsfw: selectShowMatureContent(state),
currentTheme: makeSelectClientSetting(SETTINGS.THEME)(state),
themes: makeSelectClientSetting(SETTINGS.THEMES)(state),
automaticDarkModeEnabled: makeSelectClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED)(state),

View file

@ -1,6 +1,6 @@
// @flow
import * as PAGES from 'constants/pages';
import * as MODALS from 'constants/modal_types';
// import * as MODALS from 'constants/modal_types';
import * as ICONS from 'constants/icons';
import * as React from 'react';
import { SETTINGS } from 'lbry-redux';
@ -17,7 +17,7 @@ import classnames from 'classnames';
import { getPasswordFromCookie } from 'util/saved-passwords';
// $FlowFixMe
import homepages from 'homepages';
import { Lbryio } from 'lbryinc';
// import { Lbryio } from 'lbryinc';
import Yrbl from 'component/yrbl';
type Price = {
@ -27,10 +27,10 @@ type Price = {
type SetDaemonSettingArg = boolean | string | number;
type DarkModeTimes = {
from: { hour: string, min: string, formattedTime: string },
to: { hour: string, min: string, formattedTime: string },
};
// type DarkModeTimes = {
// from: { hour: string, min: string, formattedTime: string },
// to: { hour: string, min: string, formattedTime: string },
// };
type OptionTimes = {
fromTo: string,
@ -64,7 +64,7 @@ type Props = {
floatingPlayer: boolean,
hideReposts: ?boolean,
clearPlayingUri: () => void,
darkModeTimes: DarkModeTimes,
// darkModeTimes: DarkModeTimes,
setDarkTime: (string, {}) => void,
openModal: (string) => void,
language?: string,
@ -160,11 +160,11 @@ class SettingsPage extends React.PureComponent<Props, State> {
const {
daemonSettings,
allowAnalytics,
showNsfw,
// showNsfw,
isAuthenticated,
currentTheme,
themes,
automaticDarkModeEnabled,
// automaticDarkModeEnabled,
autoplay,
walletEncrypted,
// autoDownload,
@ -172,16 +172,16 @@ class SettingsPage extends React.PureComponent<Props, State> {
setClientSetting,
toggle3PAnalytics,
floatingPlayer,
hideReposts,
// hideReposts,
clearPlayingUri,
darkModeTimes,
// darkModeTimes,
clearCache,
openModal,
// openModal,
} = this.props;
const { storedPassword } = this.state;
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
const startHours = ['18', '19', '20', '21'];
const endHours = ['5', '6', '7', '8'];
// const startHours = ['18', '19', '20', '21'];
// const endHours = ['5', '6', '7', '8'];
return (
<Page
@ -258,7 +258,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
label={__('Theme')}
onChange={this.onThemeChange}
value={currentTheme}
disabled={automaticDarkModeEnabled}
// disabled={automaticDarkModeEnabled}
>
{themes.map((theme) => (
<option key={theme} value={theme}>
@ -267,7 +267,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
))}
</FormField>
</fieldset-section>
<fieldset-section>
{/* <fieldset-section>
<FormField
type="checkbox"
name="automatic_dark_mode"
@ -305,7 +305,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
</FormField>
</fieldset-group>
)}
</fieldset-section>
</fieldset-section> */}
</React.Fragment>
}
/>
@ -337,7 +337,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
)}
/>
<FormField
{/* <FormField
type="checkbox"
name="hide_reposts"
onChange={(e) => {
@ -351,7 +351,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
checked={hideReposts}
label={__('Hide reposts')}
helper={__('You will not see reposts by people you follow or receive email notifying about them.')}
/>
/> */}
{/* <FormField
type="checkbox"
@ -362,7 +362,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
helper={__('Anonymous content is published without a channel.')}
/> */}
<FormField
{/* <FormField
type="checkbox"
name="show_nsfw"
onChange={() =>
@ -375,7 +375,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
helper={__(
'Mature content may include nudity, intense sexuality, profanity, or other adult content. By displaying mature content, you are affirming you are of legal age to view mature content in your country or jurisdiction. '
)}
/>
/> */}
</React.Fragment>
}
/>

View file

@ -1,3 +1,4 @@
import * as PAGES from 'constants/pages';
import { DOMAIN } from 'config';
import { connect } from 'react-redux';
import { PAGE_SIZE } from 'constants/claim';
@ -11,7 +12,10 @@ import {
makeSelectClaimIsMine,
makeSelectClaimIsPending,
makeSelectClaimIsStreamPlaceholder,
doClearPublish,
doPrepareEdit,
} from 'lbry-redux';
import { push } from 'connected-react-router';
import { makeSelectChannelInSubscriptions } from 'redux/selectors/subscriptions';
import { selectBlackListedOutpoints } from 'lbryinc';
import ShowPage from './view';
@ -67,6 +71,11 @@ const select = (state, props) => {
const perform = (dispatch) => ({
resolveUri: (uri) => dispatch(doResolveUri(uri)),
beginPublish: (name) => {
dispatch(doClearPublish());
dispatch(doPrepareEdit({ name }));
dispatch(push(`/$/${PAGES.PUBLISH}`));
},
});
export default connect(select, perform)(ShowPage);

View file

@ -10,7 +10,9 @@ import Page from 'component/page';
import Button from 'component/button';
import Card from 'component/common/card';
import AbandonedChannelPreview from 'component/abandonedChannelPreview';
import Yrbl from 'component/yrbl';
import { formatLbryUrlForWeb } from 'util/url';
import { parseURI } from 'lbry-redux';
type Props = {
isResolvingUri: boolean,
@ -27,6 +29,8 @@ type Props = {
claimIsMine: boolean,
claimIsPending: boolean,
isLivestream: boolean,
claimIsMine: Boolean,
beginPublish: (string) => void,
};
function ShowPage(props: Props) {
@ -41,6 +45,7 @@ function ShowPage(props: Props) {
isSubscribed,
claimIsPending,
isLivestream,
beginPublish,
} = props;
const signingChannel = claim && claim.signing_channel;
@ -48,6 +53,7 @@ function ShowPage(props: Props) {
const claimExists = claim !== null && claim !== undefined;
const haventFetchedYet = claim === undefined;
const isMine = claim && claim.is_my_output;
const { contentName } = parseURI(uri);
useEffect(() => {
// @if TARGET='web'
@ -84,11 +90,20 @@ function ShowPage(props: Props) {
<Page>
{(claim === undefined || isResolvingUri) && (
<div className="main--empty">
<Spinner />
<Spinner delayed />
</div>
)}
{!isResolvingUri && !isSubscribed && (
<span className="empty">{__("There's nothing available at this location.")}</span>
<div className="main--empty">
<Yrbl
title={__('No Content Found')}
subtitle={
<div className="section__actions">
<Button button="primary" label={__('Publish Something')} onClick={() => beginPublish(contentName)} />
</div>
}
/>
</div>
)}
{!isResolvingUri && isSubscribed && claim === null && <AbandonedChannelPreview uri={uri} type={'large'} />}
</Page>

View file

@ -5,14 +5,14 @@ import Page from 'component/page';
import ClaimListDiscover from 'component/claimListDiscover';
import ClaimEffectiveAmount from 'component/claimEffectiveAmount';
import SearchTopClaim from 'component/searchTopClaim';
import { ORDER_BY_TOP, FRESH_ALL } from 'constants/claim_search';
import * as CS from 'constants/claim_search';
import Button from 'component/button';
import I18nMessage from 'component/i18nMessage';
import * as PAGES from 'constants/pages';
type Props = {
name: string,
beginPublish: string => void,
beginPublish: (string) => void,
};
function TopPage(props: Props) {
@ -25,8 +25,9 @@ function TopPage(props: Props) {
<SearchTopClaim query={name} hideLink setChannelActive={setChannelActive} />
<ClaimListDiscover
name={channelActive ? `@${queryName}` : queryName}
defaultFreshness={FRESH_ALL}
defaultOrderBy={ORDER_BY_TOP}
defaultFreshness={CS.FRESH_ALL}
defaultOrderBy={CS.ORDER_BY_TOP}
streamType={CS.CONTENT_ALL}
meta={
<div className="search__top-links">
<I18nMessage
@ -46,7 +47,7 @@ function TopPage(props: Props) {
</div>
}
includeSupportAction
renderProperties={claim => (
renderProperties={(claim) => (
<span className="claim-preview__custom-properties">
{claim.meta.is_controlling && <span className="help--inline">{__('Currently winning')}</span>}
<ClaimEffectiveAmount uri={claim.repost_url || claim.canonical_url} />

View file

@ -2,6 +2,7 @@
import * as ACTIONS from 'constants/action_types';
import * as REACTION_TYPES from 'constants/reactions';
import { Lbry, parseURI, buildURI, selectClaimsByUri, selectMyChannelClaims } from 'lbry-redux';
// import { BANNED_LIVESTREAM_WORDS } from 'constants/comment';
import { doToast, doSeeNotifications } from 'redux/actions/notifications';
import {
makeSelectCommentIdsForUri,
@ -216,6 +217,27 @@ export function doCommentCreate(
type: ACTIONS.COMMENT_CREATE_STARTED,
});
// if (livestream) {
// const strippedCommentText = comment.trim().toLowerCase().replace(/\s/g, '');
// for (var i = 0; i < BANNED_LIVESTREAM_WORDS.length; i++) {
// const bannedWord = BANNED_LIVESTREAM_WORDS[i];
// if (strippedCommentText.includes(bannedWord)) {
// dispatch({
// type: ACTIONS.COMMENT_CREATE_FAILED,
// });
//
// dispatch(
// doToast({
// message: 'Unable to create comment.',
// isError: true,
// })
// );
//
// return;
// }
// }
// }
if (parent_id) {
const notification = makeSelectNotificationForCommentId(parent_id)(state);
if (notification && !notification.is_seen) {

View file

@ -9,6 +9,8 @@ import {
doCheckPendingClaims,
doCheckReflectingFiles,
ACTIONS as LBRY_REDUX_ACTIONS,
makeSelectPublishFormValue,
makeSelectClaimForUri,
} from 'lbry-redux';
import { doError } from 'redux/actions/notifications';
import { push } from 'connected-react-router';
@ -24,7 +26,12 @@ export const doPublishDesktop = (filePath: string, preview?: boolean) => (dispat
);
};
const noFile = !filePath || filePath === NO_FILE;
const noFileParam = !filePath || filePath === NO_FILE;
const state = getState();
const editingUri = makeSelectPublishFormValue('editingURI')(state) || '';
const claim = makeSelectClaimForUri(editingUri)(state) || {};
const hasSourceFile = claim.value && claim.value.source;
const redirectToLivestream = noFileParam && !hasSourceFile;
const publishSuccess = (successResponse, lbryFirstError) => {
const state = getState();
@ -41,6 +48,7 @@ export const doPublishDesktop = (filePath: string, preview?: boolean) => (dispat
actions.push({
type: ACTIONS.PUBLISH_SUCCESS,
});
// We have to fake a temp claim until the new pending one is returned by claim_list_mine
// We can't rely on claim_list_mine because there might be some delay before the new claims are returned
// Doing this allows us to show the pending claim immediately, it will get overwritten by the real one
@ -73,6 +81,9 @@ export const doPublishDesktop = (filePath: string, preview?: boolean) => (dispat
// @if TARGET='app'
dispatch(doCheckReflectingFiles());
// @endif
if (redirectToLivestream) {
dispatch(push(`/$/${PAGES.LIVESTREAM}`));
}
};
const publishFail = (error) => {
@ -93,7 +104,9 @@ export const doPublishDesktop = (filePath: string, preview?: boolean) => (dispat
// on the publishes page. This doesn't exist on desktop so wait until we get a response
// from the SDK
// @if TARGET='web'
dispatch(push(noFile ? `/$/${PAGES.LIVESTREAM}` : `/$/${PAGES.UPLOADS}`));
if (!redirectToLivestream) {
dispatch(push(`/$/${PAGES.UPLOADS}`));
}
// @endif
dispatch(doPublish(publishSuccess, publishFail));

View file

@ -6,13 +6,13 @@ import { SEARCH_OPTIONS } from 'constants/search';
const defaultState: SearchState = {
// $FlowFixMe
options: {
[SEARCH_OPTIONS.RESULT_COUNT]: 30,
[SEARCH_OPTIONS.CLAIM_TYPE]: SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS,
[SEARCH_OPTIONS.MEDIA_AUDIO]: true,
[SEARCH_OPTIONS.RESULT_COUNT]: 50,
[SEARCH_OPTIONS.CLAIM_TYPE]: 'file,channel',
[SEARCH_OPTIONS.MEDIA_AUDIO]: false,
[SEARCH_OPTIONS.MEDIA_VIDEO]: true,
[SEARCH_OPTIONS.MEDIA_TEXT]: true,
[SEARCH_OPTIONS.MEDIA_IMAGE]: true,
[SEARCH_OPTIONS.MEDIA_APPLICATION]: true,
[SEARCH_OPTIONS.MEDIA_TEXT]: false,
[SEARCH_OPTIONS.MEDIA_IMAGE]: false,
[SEARCH_OPTIONS.MEDIA_APPLICATION]: false,
},
urisByQuery: {},
searching: false,

View file

@ -8,7 +8,7 @@ import { UNSYNCED_SETTINGS } from 'config';
const { CLIENT_SYNC_KEYS } = SHARED_PREFERENCES;
const settingsToIgnore = (UNSYNCED_SETTINGS && UNSYNCED_SETTINGS.trim().split(' ')) || [];
const clientSyncKeys = settingsToIgnore.length
? CLIENT_SYNC_KEYS.filter(k => !settingsToIgnore.includes(k))
? CLIENT_SYNC_KEYS.filter((k) => !settingsToIgnore.includes(k))
: CLIENT_SYNC_KEYS;
const reducers = {};
@ -69,7 +69,7 @@ const defaultState = {
[SETTINGS.AUTOPLAY_NEXT]: true,
[SETTINGS.FLOATING_PLAYER]: true,
[SETTINGS.AUTO_DOWNLOAD]: true,
[SETTINGS.HIDE_REPOSTS]: false,
[SETTINGS.HIDE_REPOSTS]: true,
// OS
[SETTINGS.AUTO_LAUNCH]: true,
@ -88,12 +88,12 @@ reducers[ACTIONS.REHYDRATE] = (state, action) => {
return Object.assign({}, state, { clientSettings });
};
reducers[ACTIONS.FINDING_FFMPEG_STARTED] = state =>
reducers[ACTIONS.FINDING_FFMPEG_STARTED] = (state) =>
Object.assign({}, state, {
findingFFmpeg: true,
});
reducers[ACTIONS.FINDING_FFMPEG_COMPLETED] = state =>
reducers[ACTIONS.FINDING_FFMPEG_COMPLETED] = (state) =>
Object.assign({}, state, {
findingFFmpeg: false,
});
@ -119,7 +119,7 @@ reducers[ACTIONS.CLIENT_SETTING_CHANGED] = (state, action) => {
});
};
reducers[ACTIONS.UPDATE_IS_NIGHT] = state => {
reducers[ACTIONS.UPDATE_IS_NIGHT] = (state) => {
const { from, to } = state.clientSettings[SETTINGS.DARK_MODE_TIMES];
const momentNow = moment();
const startNightMoment = moment(from.formattedTime, 'HH:mm');
@ -154,7 +154,7 @@ reducers[LBRY_REDUX_ACTIONS.SHARED_PREFERENCE_SET] = (state, action) => {
});
};
reducers[ACTIONS.SYNC_CLIENT_SETTINGS] = state => {
reducers[ACTIONS.SYNC_CLIENT_SETTINGS] = (state) => {
const { clientSettings } = state;
const sharedPreferences = Object.assign({}, state.sharedPreferences);
const selectedClientSettings = getSubsetFromKeysArray(clientSettings, clientSyncKeys);

Some files were not shown because too many files have changed in this diff Show more