Compare commits
18 commits
master
...
horizon-se
Author | SHA1 | Date | |
---|---|---|---|
|
2b02f5b351 | ||
|
6554e02622 | ||
|
c02f253929 | ||
|
9b7193b013 | ||
|
a7e3bceb6c | ||
|
0fbf202f2b | ||
|
1446ae2a29 | ||
|
d64d44b05a | ||
|
0fbfbb6a9c | ||
|
307322cc48 | ||
|
4a266c0b8a | ||
|
40611070cb | ||
|
80e6ded858 | ||
|
a000286998 | ||
|
b8b49dc12e | ||
|
a4c865f727 | ||
|
3dabf0d31d | ||
|
03827fcbee |
91 changed files with 2859 additions and 817 deletions
5
flow-typed/homepage.js
vendored
5
flow-typed/homepage.js
vendored
|
@ -4,12 +4,12 @@ declare type HomepageObject = {
|
||||||
options: any,
|
options: any,
|
||||||
route: string,
|
route: string,
|
||||||
title: string,
|
title: string,
|
||||||
}
|
};
|
||||||
|
|
||||||
declare type HomepageData = {
|
declare type HomepageData = {
|
||||||
[string]: HomepageObject,
|
[string]: HomepageObject,
|
||||||
default: any => any,
|
default: any => any,
|
||||||
}
|
};
|
||||||
|
|
||||||
declare type RowDataItem = {
|
declare type RowDataItem = {
|
||||||
title: any,
|
title: any,
|
||||||
|
@ -19,6 +19,7 @@ declare type RowDataItem = {
|
||||||
extra?: any,
|
extra?: any,
|
||||||
options?: {
|
options?: {
|
||||||
channelIds?: Array<string>,
|
channelIds?: Array<string>,
|
||||||
|
limitClaimsPerChannel?: number,
|
||||||
pageSize: number,
|
pageSize: number,
|
||||||
},
|
},
|
||||||
route?: string,
|
route?: string,
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -53,12 +53,15 @@
|
||||||
"electron-notarize": "^1.0.0",
|
"electron-notarize": "^1.0.0",
|
||||||
"electron-updater": "^4.2.4",
|
"electron-updater": "^4.2.4",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"feed": "^4.2.2",
|
||||||
"if-env": "^1.0.4",
|
"if-env": "^1.0.4",
|
||||||
"react-datetime-picker": "^3.2.1",
|
"react-datetime-picker": "^3.2.1",
|
||||||
"react-top-loading-bar": "^2.0.1",
|
"react-top-loading-bar": "^2.0.1",
|
||||||
"remove-markdown": "^0.3.0",
|
"remove-markdown": "^0.3.0",
|
||||||
"source-map-explorer": "^2.5.2",
|
"source-map-explorer": "^2.5.2",
|
||||||
"tempy": "^0.6.0",
|
"tempy": "^0.6.0",
|
||||||
|
"videojs-contrib-ads": "^6.9.0",
|
||||||
|
"videojs-ima": "^1.11.0",
|
||||||
"videojs-logo": "^2.1.4"
|
"videojs-logo": "^2.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
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
BIN
static/img/lbry-favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -38,6 +38,9 @@ type Analytics = {
|
||||||
playerLoadedEvent: (?boolean) => void,
|
playerLoadedEvent: (?boolean) => void,
|
||||||
playerStartedEvent: (?boolean) => void,
|
playerStartedEvent: (?boolean) => void,
|
||||||
videoStartEvent: (string, number) => void,
|
videoStartEvent: (string, number) => void,
|
||||||
|
adsFetchedEvent: () => void,
|
||||||
|
adsReceivedEvent: (any) => void,
|
||||||
|
adsErrorEvent: (any) => void,
|
||||||
videoBufferEvent: (
|
videoBufferEvent: (
|
||||||
StreamClaim,
|
StreamClaim,
|
||||||
{
|
{
|
||||||
|
@ -50,9 +53,6 @@ type Analytics = {
|
||||||
readyState: number,
|
readyState: number,
|
||||||
}
|
}
|
||||||
) => void,
|
) => void,
|
||||||
adsFetchedEvent: () => void,
|
|
||||||
adsReceivedEvent: (any) => void,
|
|
||||||
adsErrorEvent: (any) => void,
|
|
||||||
emailProvidedEvent: () => void,
|
emailProvidedEvent: () => void,
|
||||||
emailVerifiedEvent: () => void,
|
emailVerifiedEvent: () => void,
|
||||||
rewardEligibleEvent: () => void,
|
rewardEligibleEvent: () => void,
|
||||||
|
|
|
@ -136,14 +136,15 @@ function ChannelContent(props: Props) {
|
||||||
{!channelIsMine && claimsInChannel > 0 && <HiddenNsfwClaims uri={uri} />}
|
{!channelIsMine && claimsInChannel > 0 && <HiddenNsfwClaims uri={uri} />}
|
||||||
|
|
||||||
<ClaimListDiscover
|
<ClaimListDiscover
|
||||||
defaultFreshness={CS.FRESH_ALL}
|
hideLivestreamClaims
|
||||||
showHiddenByUser={viewHiddenChannels}
|
showHiddenByUser={viewHiddenChannels}
|
||||||
forceShowReposts
|
forceShowReposts
|
||||||
hideFilters={!showFilters}
|
hideFilters={!showFilters}
|
||||||
hideAdvancedFilter={!showFilters}
|
hideAdvancedFilter={!showFilters}
|
||||||
tileLayout={tileLayout}
|
tileLayout={tileLayout}
|
||||||
uris={searchResults}
|
uris={searchResults}
|
||||||
channelIds={[claim.claim_id]}
|
streamType={CS.CONTENT_ALL}
|
||||||
|
channelIds={[claimId]}
|
||||||
claimType={claimType}
|
claimType={claimType}
|
||||||
feeAmount={CS.FEE_AMOUNT_ANY}
|
feeAmount={CS.FEE_AMOUNT_ANY}
|
||||||
defaultOrderBy={CS.ORDER_BY_NEW}
|
defaultOrderBy={CS.ORDER_BY_NEW}
|
||||||
|
|
BIN
ui/component/channelThumbnail/spaceman.png
Normal file
BIN
ui/component/channelThumbnail/spaceman.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
|
@ -2,7 +2,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { parseURI } from 'lbry-redux';
|
import { parseURI } from 'lbry-redux';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Gerbil from './gerbil.png';
|
import Spaceman from './spaceman.png';
|
||||||
import Transparent from './transparent_1x1.png';
|
import Transparent from './transparent_1x1.png';
|
||||||
import FreezeframeWrapper from 'component/fileThumbnail/FreezeframeWrapper';
|
import FreezeframeWrapper from 'component/fileThumbnail/FreezeframeWrapper';
|
||||||
import ChannelStakedIndicator from 'component/channelStakedIndicator';
|
import ChannelStakedIndicator from 'component/channelStakedIndicator';
|
||||||
|
@ -100,7 +100,7 @@ function ChannelThumbnail(props: Props) {
|
||||||
ref={thumbnailRef}
|
ref={thumbnailRef}
|
||||||
alt={__('Channel profile picture')}
|
alt={__('Channel profile picture')}
|
||||||
className="channel-thumbnail__default"
|
className="channel-thumbnail__default"
|
||||||
data-src={!thumbError && url ? url : Gerbil}
|
data-src={!thumbError && url ? url : Spaceman}
|
||||||
src={Transparent}
|
src={Transparent}
|
||||||
onError={() => setThumbError(true)} // if thumb fails (including due to https replace, show gerbil.
|
onError={() => setThumbError(true)} // if thumb fails (including due to https replace, show gerbil.
|
||||||
/>
|
/>
|
||||||
|
@ -114,7 +114,7 @@ function ChannelThumbnail(props: Props) {
|
||||||
ref={thumbnailRef}
|
ref={thumbnailRef}
|
||||||
alt={__('Channel profile picture')}
|
alt={__('Channel profile picture')}
|
||||||
className="channel-thumbnail__custom"
|
className="channel-thumbnail__custom"
|
||||||
data-src={!thumbError && url ? url : Gerbil}
|
data-src={!thumbError && url ? url : Spaceman}
|
||||||
src={Transparent}
|
src={Transparent}
|
||||||
onError={() => setThumbError(true)} // if thumb fails (including due to https replace, show gerbil.
|
onError={() => setThumbError(true)} // if thumb fails (including due to https replace, show gerbil.
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -45,6 +45,7 @@ type Props = {
|
||||||
livestreamMap?: { [string]: any },
|
livestreamMap?: { [string]: any },
|
||||||
searchOptions?: any,
|
searchOptions?: any,
|
||||||
collectionId?: string,
|
collectionId?: string,
|
||||||
|
hideLivestreamClaims?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ClaimList(props: Props) {
|
export default function ClaimList(props: Props) {
|
||||||
|
@ -76,6 +77,7 @@ export default function ClaimList(props: Props) {
|
||||||
livestreamMap,
|
livestreamMap,
|
||||||
searchOptions,
|
searchOptions,
|
||||||
collectionId,
|
collectionId,
|
||||||
|
hideLivestreamClaims,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
||||||
|
@ -135,6 +137,7 @@ export default function ClaimList(props: Props) {
|
||||||
properties={renderProperties}
|
properties={renderProperties}
|
||||||
live={resolveLive(index)}
|
live={resolveLive(index)}
|
||||||
collectionId={collectionId}
|
collectionId={collectionId}
|
||||||
|
hideLivestreamClaims={hideLivestreamClaims}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{!timedOut && urisLength === 0 && !loading && <div className="empty main--empty">{empty || noResultMsg}</div>}
|
{!timedOut && urisLength === 0 && !loading && <div className="empty main--empty">{empty || noResultMsg}</div>}
|
||||||
|
@ -195,6 +198,7 @@ export default function ClaimList(props: Props) {
|
||||||
showUserBlocked={showHiddenByUser}
|
showUserBlocked={showHiddenByUser}
|
||||||
showHiddenByUser={showHiddenByUser}
|
showHiddenByUser={showHiddenByUser}
|
||||||
collectionId={collectionId}
|
collectionId={collectionId}
|
||||||
|
hideLivestreamClaims={hideLivestreamClaims}
|
||||||
customShouldHide={(claim: StreamClaim) => {
|
customShouldHide={(claim: StreamClaim) => {
|
||||||
// Hack to hide spee.ch thumbnail publishes
|
// Hack to hide spee.ch thumbnail publishes
|
||||||
// If it meets these requirements, it was probably uploaded here:
|
// If it meets these requirements, it was probably uploaded here:
|
||||||
|
|
|
@ -72,6 +72,9 @@ type Props = {
|
||||||
liveLivestreamsFirst?: boolean,
|
liveLivestreamsFirst?: boolean,
|
||||||
livestreamMap?: { [string]: any },
|
livestreamMap?: { [string]: any },
|
||||||
hasSource?: boolean,
|
hasSource?: boolean,
|
||||||
|
limitClaimsPerChannel?: number,
|
||||||
|
releaseTime?: string,
|
||||||
|
hideLivestreamClaims?: boolean,
|
||||||
isChannel?: boolean,
|
isChannel?: boolean,
|
||||||
empty?: string,
|
empty?: string,
|
||||||
};
|
};
|
||||||
|
@ -103,8 +106,8 @@ function ClaimListDiscover(props: Props) {
|
||||||
claimType,
|
claimType,
|
||||||
pageSize,
|
pageSize,
|
||||||
defaultClaimType,
|
defaultClaimType,
|
||||||
streamType,
|
streamType = CS.FILE_VIDEO,
|
||||||
defaultStreamType,
|
defaultStreamType = CS.FILE_VIDEO,
|
||||||
freshness,
|
freshness,
|
||||||
defaultFreshness = CS.FRESH_WEEK,
|
defaultFreshness = CS.FRESH_WEEK,
|
||||||
renderProperties,
|
renderProperties,
|
||||||
|
@ -123,12 +126,15 @@ function ClaimListDiscover(props: Props) {
|
||||||
forceShowReposts = false,
|
forceShowReposts = false,
|
||||||
languageSetting,
|
languageSetting,
|
||||||
searchInLanguage,
|
searchInLanguage,
|
||||||
|
limitClaimsPerChannel,
|
||||||
|
releaseTime,
|
||||||
scrollAnchor,
|
scrollAnchor,
|
||||||
showHiddenByUser = false,
|
showHiddenByUser = false,
|
||||||
liveLivestreamsFirst,
|
liveLivestreamsFirst,
|
||||||
livestreamMap,
|
livestreamMap,
|
||||||
hasSource,
|
hasSource,
|
||||||
isChannel = false,
|
isChannel = false,
|
||||||
|
hideLivestreamClaims,
|
||||||
empty,
|
empty,
|
||||||
} = props;
|
} = props;
|
||||||
const didNavigateForward = history.action === 'PUSH';
|
const didNavigateForward = history.action === 'PUSH';
|
||||||
|
@ -168,12 +174,12 @@ function ClaimListDiscover(props: Props) {
|
||||||
const durationParam = urlParams.get(CS.DURATION_KEY) || null;
|
const durationParam = urlParams.get(CS.DURATION_KEY) || null;
|
||||||
const channelIdsInUrl = urlParams.get(CS.CHANNEL_IDS_KEY);
|
const channelIdsInUrl = urlParams.get(CS.CHANNEL_IDS_KEY);
|
||||||
const channelIdsParam = channelIdsInUrl ? channelIdsInUrl.split(',') : channelIds;
|
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 originalPageSize = pageSize || CS.PAGE_SIZE;
|
||||||
const dynamicPageSize = isLargeScreen ? Math.ceil(originalPageSize * (3 / 2)) : originalPageSize;
|
const dynamicPageSize = isLargeScreen ? Math.ceil(originalPageSize * (3 / 2)) : originalPageSize;
|
||||||
const historyAction = history.action;
|
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 (!orderParam) {
|
||||||
if (historyAction === 'POP') {
|
if (historyAction === 'POP') {
|
||||||
|
@ -217,6 +223,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
fee_amount?: string,
|
fee_amount?: string,
|
||||||
has_source?: boolean,
|
has_source?: boolean,
|
||||||
has_no_source?: boolean,
|
has_no_source?: boolean,
|
||||||
|
limit_claims_per_channel?: number,
|
||||||
} = {
|
} = {
|
||||||
page_size: dynamicPageSize,
|
page_size: dynamicPageSize,
|
||||||
page,
|
page,
|
||||||
|
@ -239,6 +246,10 @@ function ClaimListDiscover(props: Props) {
|
||||||
options.has_source = true;
|
options.has_source = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (limitClaimsPerChannel) {
|
||||||
|
options.limit_claims_per_channel = limitClaimsPerChannel;
|
||||||
|
}
|
||||||
|
|
||||||
if (feeAmountParam && claimType !== CS.CLAIM_CHANNEL) {
|
if (feeAmountParam && claimType !== CS.CLAIM_CHANNEL) {
|
||||||
options.fee_amount = feeAmountParam;
|
options.fee_amount = feeAmountParam;
|
||||||
}
|
}
|
||||||
|
@ -268,7 +279,10 @@ function ClaimListDiscover(props: Props) {
|
||||||
options.reposted_claim_id = repostedClaimId;
|
options.reposted_claim_id = repostedClaimId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (claimType !== CS.CLAIM_CHANNEL) {
|
// IF release time, set it, else set fallback release times using the hack below.
|
||||||
|
if (releaseTime) {
|
||||||
|
options.release_time = releaseTime;
|
||||||
|
} else if (claimType !== CS.CLAIM_CHANNEL) {
|
||||||
if (orderParam === CS.ORDER_BY_TOP && freshnessParam !== CS.FRESH_ALL) {
|
if (orderParam === CS.ORDER_BY_TOP && freshnessParam !== CS.FRESH_ALL) {
|
||||||
options.release_time = `>${Math.floor(moment().subtract(1, freshnessParam).startOf('hour').unix())}`;
|
options.release_time = `>${Math.floor(moment().subtract(1, freshnessParam).startOf('hour').unix())}`;
|
||||||
} else if (orderParam === CS.ORDER_BY_NEW || orderParam === CS.ORDER_BY_TRENDING) {
|
} else if (orderParam === CS.ORDER_BY_NEW || orderParam === CS.ORDER_BY_TRENDING) {
|
||||||
|
@ -346,9 +360,25 @@ function ClaimListDiscover(props: Props) {
|
||||||
|
|
||||||
const hasMatureTags = tagsParam && tagsParam.split(',').some((t) => MATURE_TAGS.includes(t));
|
const hasMatureTags = tagsParam && tagsParam.split(',').some((t) => MATURE_TAGS.includes(t));
|
||||||
const claimSearchCacheQuery = createNormalizedClaimSearchKey(options);
|
const claimSearchCacheQuery = createNormalizedClaimSearchKey(options);
|
||||||
const claimSearchResult = claimSearchByQuery[claimSearchCacheQuery];
|
let claimSearchResult = claimSearchByQuery[claimSearchCacheQuery];
|
||||||
const claimSearchResultLastPageReached = claimSearchByQueryLastPageReached[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);
|
const [prevOptions, setPrevOptions] = React.useState(null);
|
||||||
|
|
||||||
if (!isJustScrollingToNewPage(prevOptions, options)) {
|
if (!isJustScrollingToNewPage(prevOptions, options)) {
|
||||||
|
@ -472,7 +502,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
claimType={claimType}
|
claimType={claimType}
|
||||||
streamType={streamType}
|
streamType={streamType}
|
||||||
defaultStreamType={defaultStreamType}
|
defaultStreamType={defaultStreamType}
|
||||||
feeAmount={feeAmount}
|
// feeAmount={feeAmount}
|
||||||
orderBy={orderBy}
|
orderBy={orderBy}
|
||||||
defaultOrderBy={defaultOrderBy}
|
defaultOrderBy={defaultOrderBy}
|
||||||
hideAdvancedFilter={hideAdvancedFilter}
|
hideAdvancedFilter={hideAdvancedFilter}
|
||||||
|
@ -512,6 +542,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
liveLivestreamsFirst={liveLivestreamsFirst}
|
liveLivestreamsFirst={liveLivestreamsFirst}
|
||||||
livestreamMap={livestreamMap}
|
livestreamMap={livestreamMap}
|
||||||
searchOptions={options}
|
searchOptions={options}
|
||||||
|
hideLivestreamClaims={hideLivestreamClaims}
|
||||||
empty={empty}
|
empty={empty}
|
||||||
/>
|
/>
|
||||||
{loading && (
|
{loading && (
|
||||||
|
@ -546,6 +577,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
liveLivestreamsFirst={liveLivestreamsFirst}
|
liveLivestreamsFirst={liveLivestreamsFirst}
|
||||||
livestreamMap={livestreamMap}
|
livestreamMap={livestreamMap}
|
||||||
searchOptions={options}
|
searchOptions={options}
|
||||||
|
hideLivestreamClaims={hideLivestreamClaims}
|
||||||
empty={empty}
|
empty={empty}
|
||||||
/>
|
/>
|
||||||
{loading &&
|
{loading &&
|
||||||
|
|
|
@ -7,7 +7,7 @@ import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { Menu, MenuButton, MenuList, MenuItem } from '@reach/menu-button';
|
import { Menu, MenuButton, MenuList, MenuItem } from '@reach/menu-button';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import { generateShareUrl } from 'util/url';
|
import { generateShareUrl, generateRssUrl } from 'util/url';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { buildURI, parseURI, COLLECTIONS_CONSTS } from 'lbry-redux';
|
import { buildURI, parseURI, COLLECTIONS_CONSTS } from 'lbry-redux';
|
||||||
|
|
||||||
|
@ -103,6 +103,7 @@ function ClaimMenuList(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const shareUrl: string = generateShareUrl(SHARE_DOMAIN, uri);
|
const shareUrl: string = generateShareUrl(SHARE_DOMAIN, uri);
|
||||||
|
const rssUrl: string = generateRssUrl(SHARE_DOMAIN, uri);
|
||||||
const isCollectionClaim = claim && claim.value_type === 'collection';
|
const isCollectionClaim = claim && claim.value_type === 'collection';
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
const isPlayable =
|
const isPlayable =
|
||||||
|
@ -182,6 +183,10 @@ function ClaimMenuList(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleCopyRssLink() {
|
||||||
|
navigator.clipboard.writeText(rssUrl);
|
||||||
|
}
|
||||||
|
|
||||||
function handleCopyLink() {
|
function handleCopyLink() {
|
||||||
navigator.clipboard.writeText(shareUrl);
|
navigator.clipboard.writeText(shareUrl);
|
||||||
}
|
}
|
||||||
|
@ -350,7 +355,18 @@ function ClaimMenuList(props: Props) {
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<hr className="menu__separator" />
|
<hr className="menu__separator" />
|
||||||
|
|
||||||
|
{isChannelPage && (
|
||||||
|
<MenuItem className="comment__menu-option" onSelect={handleCopyRssLink}>
|
||||||
|
<div className="menu__link">
|
||||||
|
<Icon aria-hidden icon={ICONS.SHARE} />
|
||||||
|
{__('Copy RSS URL')}
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
<MenuItem className="comment__menu-option" onSelect={handleCopyLink}>
|
<MenuItem className="comment__menu-option" onSelect={handleCopyLink}>
|
||||||
<div className="menu__link">
|
<div className="menu__link">
|
||||||
<Icon aria-hidden icon={ICONS.SHARE} />
|
<Icon aria-hidden icon={ICONS.SHARE} />
|
||||||
|
|
|
@ -33,7 +33,7 @@ const AbandonedChannelPreview = React.lazy(() =>
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
claim: ?Claim, // maybe?
|
claim: ?Claim,
|
||||||
obscureNsfw: boolean,
|
obscureNsfw: boolean,
|
||||||
showUserBlocked: boolean,
|
showUserBlocked: boolean,
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
|
|
|
@ -44,6 +44,8 @@ type Props = {
|
||||||
properties?: (Claim) => void,
|
properties?: (Claim) => void,
|
||||||
live?: boolean,
|
live?: boolean,
|
||||||
collectionId?: string,
|
collectionId?: string,
|
||||||
|
hideLivestreamClaims?: boolean,
|
||||||
|
isLivestream: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ClaimPreviewTile(props: Props) {
|
function ClaimPreviewTile(props: Props) {
|
||||||
|
@ -66,6 +68,8 @@ function ClaimPreviewTile(props: Props) {
|
||||||
showHiddenByUser,
|
showHiddenByUser,
|
||||||
properties,
|
properties,
|
||||||
live,
|
live,
|
||||||
|
hideLivestreamClaims,
|
||||||
|
isLivestream,
|
||||||
collectionId,
|
collectionId,
|
||||||
} = props;
|
} = props;
|
||||||
const isRepost = claim && claim.repost_channel_url;
|
const isRepost = claim && claim.repost_channel_url;
|
||||||
|
@ -149,7 +153,7 @@ function ClaimPreviewTile(props: Props) {
|
||||||
shouldHide = blockedChannelUris.some((blockedUri) => blockedUri === claim.permanent_url);
|
shouldHide = blockedChannelUris.some((blockedUri) => blockedUri === claim.permanent_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldHide) {
|
if (shouldHide || (isLivestream && hideLivestreamClaims)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +217,6 @@ function ClaimPreviewTile(props: Props) {
|
||||||
<UriIndicator uri={uri} />
|
<UriIndicator uri={uri} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* CHECK CLAIM MENU LIST PARAMS (IS REPOST?) */}
|
|
||||||
<ClaimMenuList uri={uri} collectionId={listId} channelUri={channelUri} isRepost={isRepost} />
|
<ClaimMenuList uri={uri} collectionId={listId} channelUri={channelUri} isRepost={isRepost} />
|
||||||
</h2>
|
</h2>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
|
@ -37,7 +37,7 @@ export function prioritizeActiveLivestreams(
|
||||||
// for that channel actually point to the same source).
|
// for that channel actually point to the same source).
|
||||||
// 2. 'liveChannelIds' needs to be pruned after being accounted for,
|
// 2. 'liveChannelIds' needs to be pruned after being accounted for,
|
||||||
// otherwise all livestream-claims will be "live" (we'll only take the
|
// otherwise all livestream-claims will be "live" (we'll only take the
|
||||||
// latest one as "live").
|
// latest one as "live" ).
|
||||||
return (
|
return (
|
||||||
claim &&
|
claim &&
|
||||||
claim.value_type === 'stream' &&
|
claim.value_type === 'stream' &&
|
||||||
|
@ -145,14 +145,14 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
mutedUris,
|
mutedUris,
|
||||||
liveLivestreamsFirst,
|
liveLivestreamsFirst,
|
||||||
livestreamMap,
|
livestreamMap,
|
||||||
// pin,
|
pin,
|
||||||
prefixUris,
|
prefixUris,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { location } = useHistory();
|
const { location } = useHistory();
|
||||||
const urlParams = new URLSearchParams(location.search);
|
const urlParams = new URLSearchParams(location.search);
|
||||||
const feeAmountInUrl = urlParams.get('fee_amount');
|
const feeAmountInUrl = urlParams.get('fee_amount');
|
||||||
const feeAmountParam = feeAmountInUrl || feeAmount;
|
const feeAmountParam = feeAmountInUrl || feeAmount || CS.FEE_AMOUNT_ONLY_FREE;
|
||||||
const mutedAndBlockedChannelIds = Array.from(new Set(mutedUris.concat(blockedUris).map((uri) => uri.split('#')[1])));
|
const mutedAndBlockedChannelIds = Array.from(new Set(mutedUris.concat(blockedUris).map((uri) => uri.split('#')[1])));
|
||||||
const liveUris = [];
|
const liveUris = [];
|
||||||
|
|
||||||
|
@ -239,8 +239,8 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
let uris = (prefixUris || []).concat(claimSearchByQuery[mainSearchKey] || []);
|
let uris = (prefixUris || []).concat(claimSearchByQuery[mainSearchKey] || []);
|
||||||
|
|
||||||
const isLoading = fetchingClaimSearchByQuery[mainSearchKey];
|
const isLoading = fetchingClaimSearchByQuery[mainSearchKey];
|
||||||
|
// && !isLoading isn't on master, doesn't seem to affect the functionality, don't recall why it was added.
|
||||||
if (liveLivestreamsFirst && livestreamMap) {
|
if (liveLivestreamsFirst && livestreamMap && !isLoading) {
|
||||||
prioritizeActiveLivestreams(uris, liveUris, livestreamMap, claimsByUri, claimSearchByQuery, options);
|
prioritizeActiveLivestreams(uris, liveUris, livestreamMap, claimsByUri, claimSearchByQuery, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,10 +284,26 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const modifiedUris = uris ? uris.slice() : [];
|
||||||
|
const fixUris = [
|
||||||
|
'lbry://@TheFront#e/which-countries-had-most-brutal-basic#a',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (pin && modifiedUris && modifiedUris.length > 2 && window.location.pathname === '/') {
|
||||||
|
fixUris.forEach((fixUri) => {
|
||||||
|
if (modifiedUris.indexOf(fixUri) !== -1) {
|
||||||
|
modifiedUris.splice(modifiedUris.indexOf(fixUri), 1);
|
||||||
|
} else {
|
||||||
|
modifiedUris.pop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
modifiedUris.splice(2, 0, ...fixUris);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="claim-grid">
|
<ul className="claim-grid">
|
||||||
{uris && uris.length
|
{modifiedUris && modifiedUris.length
|
||||||
? uris.map((uri, index) => (
|
? modifiedUris.map((uri, index) => (
|
||||||
<ClaimPreviewTile key={uri} uri={uri} properties={renderProperties} live={resolveLive(index)} />
|
<ClaimPreviewTile key={uri} uri={uri} properties={renderProperties} live={resolveLive(index)} />
|
||||||
))
|
))
|
||||||
: new Array(pageSize).fill(1).map((x, i) => <ClaimPreviewTile key={i} placeholder />)}
|
: new Array(pageSize).fill(1).map((x, i) => <ClaimPreviewTile key={i} placeholder />)}
|
||||||
|
|
|
@ -33,7 +33,10 @@ type Props = {
|
||||||
isReply: boolean,
|
isReply: boolean,
|
||||||
activeChannel: string,
|
activeChannel: string,
|
||||||
activeChannelClaim: ?ChannelClaim,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
livestream?: boolean,
|
bottom: boolean,
|
||||||
|
onSubmit: (string, string) => void,
|
||||||
|
livestream: boolean,
|
||||||
|
embed?: boolean,
|
||||||
toast: (string) => void,
|
toast: (string) => void,
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
sendTip: ({}, (any) => void, (any) => void) => void,
|
sendTip: ({}, (any) => void, (any) => void) => void,
|
||||||
|
@ -53,7 +56,10 @@ export function CommentCreate(props: Props) {
|
||||||
isReply,
|
isReply,
|
||||||
parentId,
|
parentId,
|
||||||
activeChannelClaim,
|
activeChannelClaim,
|
||||||
|
onSubmit,
|
||||||
|
bottom,
|
||||||
livestream,
|
livestream,
|
||||||
|
embed,
|
||||||
toast,
|
toast,
|
||||||
claimIsMine,
|
claimIsMine,
|
||||||
sendTip,
|
sendTip,
|
||||||
|
@ -91,7 +97,7 @@ export function CommentCreate(props: Props) {
|
||||||
|
|
||||||
function altEnterListener(e: SyntheticKeyboardEvent<*>) {
|
function altEnterListener(e: SyntheticKeyboardEvent<*>) {
|
||||||
const KEYCODE_ENTER = 13;
|
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();
|
e.preventDefault();
|
||||||
buttonref.current.click();
|
buttonref.current.click();
|
||||||
}
|
}
|
||||||
|
@ -169,6 +175,10 @@ export function CommentCreate(props: Props) {
|
||||||
setCommentFailure(false);
|
setCommentFailure(false);
|
||||||
justCommented.push(res.comment_id);
|
justCommented.push(res.comment_id);
|
||||||
|
|
||||||
|
if (onSubmit) {
|
||||||
|
onSubmit(commentValue, activeChannelClaim.name);
|
||||||
|
}
|
||||||
|
|
||||||
if (onDoneReplying) {
|
if (onDoneReplying) {
|
||||||
onDoneReplying();
|
onDoneReplying();
|
||||||
}
|
}
|
||||||
|
@ -193,6 +203,11 @@ export function CommentCreate(props: Props) {
|
||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (embed) {
|
||||||
|
window.open(`https://odysee.com/$/${PAGES.AUTH}?redirect=/$/${PAGES.LIVESTREAM}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const pathPlusRedirect = `/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`;
|
const pathPlusRedirect = `/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`;
|
||||||
if (livestream) {
|
if (livestream) {
|
||||||
window.open(pathPlusRedirect);
|
window.open(pathPlusRedirect);
|
||||||
|
@ -246,6 +261,7 @@ export function CommentCreate(props: Props) {
|
||||||
className={classnames('comment__create', {
|
className={classnames('comment__create', {
|
||||||
'comment__create--reply': isReply,
|
'comment__create--reply': isReply,
|
||||||
'comment__create--nested-reply': isNested,
|
'comment__create--nested-reply': isNested,
|
||||||
|
'comment__create--bottom': bottom,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
|
|
|
@ -77,7 +77,7 @@ export default function CommentReactions(props: Props) {
|
||||||
<Button
|
<Button
|
||||||
requiresAuth={IS_WEB}
|
requiresAuth={IS_WEB}
|
||||||
title={__('Upvote')}
|
title={__('Upvote')}
|
||||||
icon={ICONS.UPVOTE}
|
icon={myReacts.includes(REACTION_TYPES.LIKE) ? ICONS.FIRE_ACTIVE : ICONS.FIRE}
|
||||||
className={classnames('comment__action', {
|
className={classnames('comment__action', {
|
||||||
'comment__action--active': myReacts && myReacts.includes(REACTION_TYPES.LIKE),
|
'comment__action--active': myReacts && myReacts.includes(REACTION_TYPES.LIKE),
|
||||||
})}
|
})}
|
||||||
|
@ -87,7 +87,7 @@ export default function CommentReactions(props: Props) {
|
||||||
<Button
|
<Button
|
||||||
requiresAuth={IS_WEB}
|
requiresAuth={IS_WEB}
|
||||||
title={__('Downvote')}
|
title={__('Downvote')}
|
||||||
icon={ICONS.DOWNVOTE}
|
icon={myReacts.includes(REACTION_TYPES.DISLIKE) ? ICONS.SLIME_ACTIVE : ICONS.SLIME}
|
||||||
className={classnames('comment__action', {
|
className={classnames('comment__action', {
|
||||||
'comment__action--active': myReacts && myReacts.includes(REACTION_TYPES.DISLIKE),
|
'comment__action--active': myReacts && myReacts.includes(REACTION_TYPES.DISLIKE),
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -29,6 +29,7 @@ type Props = {
|
||||||
myChannels: ?Array<ChannelClaim>,
|
myChannels: ?Array<ChannelClaim>,
|
||||||
doToast: ({ message: string }) => void,
|
doToast: ({ message: string }) => void,
|
||||||
clearPlayingUri: () => void,
|
clearPlayingUri: () => void,
|
||||||
|
hideRepost?: boolean,
|
||||||
isLivestreamClaim: boolean,
|
isLivestreamClaim: boolean,
|
||||||
reactionsDisabled: boolean,
|
reactionsDisabled: boolean,
|
||||||
download: (string) => void,
|
download: (string) => void,
|
||||||
|
@ -48,6 +49,7 @@ function FileActions(props: Props) {
|
||||||
myChannels,
|
myChannels,
|
||||||
clearPlayingUri,
|
clearPlayingUri,
|
||||||
doToast,
|
doToast,
|
||||||
|
hideRepost,
|
||||||
isLivestreamClaim,
|
isLivestreamClaim,
|
||||||
reactionsDisabled,
|
reactionsDisabled,
|
||||||
download,
|
download,
|
||||||
|
@ -113,24 +115,26 @@ function FileActions(props: Props) {
|
||||||
|
|
||||||
const lhsSection = (
|
const lhsSection = (
|
||||||
<>
|
<>
|
||||||
{ENABLE_FILE_REACTIONS && !reactionsDisabled && <FileReactions uri={uri} />}
|
{ENABLE_FILE_REACTIONS && !reactionsDisabled && <FileReactions uri={uri} livestream={isLivestreamClaim} />}
|
||||||
<ClaimSupportButton uri={uri} fileAction />
|
<ClaimSupportButton uri={uri} fileAction />
|
||||||
<ClaimCollectionAddButton uri={uri} fileAction />
|
<ClaimCollectionAddButton uri={uri} fileAction />
|
||||||
<Button
|
{!hideRepost && (
|
||||||
button="alt"
|
<Button
|
||||||
className="button--file-action"
|
button="alt"
|
||||||
icon={ICONS.REPOST}
|
className="button--file-action"
|
||||||
label={
|
icon={ICONS.REPOST}
|
||||||
claim.meta.reposted > 1 ? __(`%repost_total% Reposts`, { repost_total: claim.meta.reposted }) : __('Repost')
|
label={
|
||||||
}
|
claim.meta.reposted > 1 ? __(`%repost_total% Reposts`, { repost_total: claim.meta.reposted }) : __('Repost')
|
||||||
description={__('Repost')}
|
}
|
||||||
requiresAuth={IS_WEB}
|
description={__('Repost')}
|
||||||
onClick={handleRepostClick}
|
requiresAuth={IS_WEB}
|
||||||
/>
|
onClick={handleRepostClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
className="button--file-action"
|
className="button--file-action"
|
||||||
icon={ICONS.SHARE}
|
icon={ICONS.SHARE}
|
||||||
label={__('Share')}
|
label={isMobile ? undefined : __('Share')}
|
||||||
title={__('Share')}
|
title={__('Share')}
|
||||||
onClick={() => openModal(MODALS.SOCIAL_SHARE, { uri, webShareable, collectionId })}
|
onClick={() => openModal(MODALS.SOCIAL_SHARE, { uri, webShareable, collectionId })}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import * as REACTION_TYPES from 'constants/reactions';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
@ -15,18 +16,44 @@ type Props = {
|
||||||
likeCount: number,
|
likeCount: number,
|
||||||
dislikeCount: number,
|
dislikeCount: number,
|
||||||
myReaction: ?string,
|
myReaction: ?string,
|
||||||
|
livestream?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function FileReactions(props: Props) {
|
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 claimId = claim && claim.claim_id;
|
||||||
const channel = claim && claim.signing_channel && claim.signing_channel.name;
|
const channel = claim && claim.signing_channel && claim.signing_channel.name;
|
||||||
const isCollection = claim && claim.value_type === 'collection'; // hack because nudge gets cut off by card on cols.
|
const isCollection = claim && claim.value_type === 'collection'; // hack because nudge gets cut off by card on cols.
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (claimId) {
|
function fetchReactions() {
|
||||||
doFetchReactions(claimId);
|
doFetchReactions(claimId);
|
||||||
}
|
}
|
||||||
}, [claimId, doFetchReactions]);
|
|
||||||
|
let fetchInterval;
|
||||||
|
if (claimId) {
|
||||||
|
fetchReactions();
|
||||||
|
|
||||||
|
if (livestream) {
|
||||||
|
fetchInterval = setInterval(fetchReactions, 45000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (fetchInterval) {
|
||||||
|
clearInterval(fetchInterval);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [claimId, doFetchReactions, livestream]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -41,20 +68,46 @@ function FileReactions(props: Props) {
|
||||||
title={__('I like this')}
|
title={__('I like this')}
|
||||||
requiresAuth={IS_WEB}
|
requiresAuth={IS_WEB}
|
||||||
authSrc="filereaction_like"
|
authSrc="filereaction_like"
|
||||||
className={classnames('button--file-action')}
|
className={classnames('button--file-action', { 'button--fire': myReaction === REACTION_TYPES.LIKE })}
|
||||||
label={formatNumberWithCommas(likeCount, 0)}
|
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}
|
iconSize={18}
|
||||||
icon={ICONS.UPVOTE}
|
icon={myReaction === REACTION_TYPES.LIKE ? ICONS.FIRE_ACTIVE : ICONS.FIRE}
|
||||||
onClick={() => doReactionLike(uri)}
|
onClick={() => doReactionLike(uri)}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
requiresAuth={IS_WEB}
|
requiresAuth={IS_WEB}
|
||||||
authSrc={'filereaction_dislike'}
|
authSrc={'filereaction_dislike'}
|
||||||
title={__('I dislike this')}
|
title={__('I dislike this')}
|
||||||
className={classnames('button--file-action')}
|
className={classnames('button--file-action', { 'button--slime': myReaction === REACTION_TYPES.DISLIKE })}
|
||||||
label={formatNumberWithCommas(dislikeCount, 0)}
|
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}
|
iconSize={18}
|
||||||
icon={ICONS.DOWNVOTE}
|
icon={myReaction === REACTION_TYPES.DISLIKE ? ICONS.SLIME_ACTIVE : ICONS.SLIME}
|
||||||
onClick={() => doReactionDislike(uri)}
|
onClick={() => doReactionDislike(uri)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -21,8 +21,8 @@ function FileSubtitle(props: Props) {
|
||||||
|
|
||||||
<FileViewCount uri={uri} livestream={livestream} activeViewers={activeViewers} isLive={isLive} />
|
<FileViewCount uri={uri} livestream={livestream} activeViewers={activeViewers} isLive={isLive} />
|
||||||
</div>
|
</div>
|
||||||
|
{/* did I need these params? */}
|
||||||
<FileActions uri={uri} />
|
<FileActions uri={uri} hideRepost={livestream} livestream={livestream} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import * as ICONS from 'constants/icons';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import * as PAGES from 'constants/pages';
|
|
||||||
import FileDescription from 'component/fileDescription';
|
import FileDescription from 'component/fileDescription';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
|
|
||||||
|
@ -65,17 +64,16 @@ function FileTitleSection(props: Props) {
|
||||||
<div className="main--empty">
|
<div className="main--empty">
|
||||||
<h2>
|
<h2>
|
||||||
<Icon className="icon--hidden" icon={ICONS.EYE_OFF} />
|
<Icon className="icon--hidden" icon={ICONS.EYE_OFF} />
|
||||||
{__('Mature content blocked.')}
|
{__('Mature content is not supported on Odysee.')}
|
||||||
</h2>
|
</h2>
|
||||||
<div>
|
<div>
|
||||||
<I18nMessage
|
<I18nMessage
|
||||||
tokens={{
|
tokens={{
|
||||||
content_settings: (
|
download_url: <Button label={__('lbry.com')} button="link" href="https://lbry.com/get" />,
|
||||||
<Button button="link" label={__('content settings')} navigate={`/$/${PAGES.SETTINGS}`} />
|
|
||||||
),
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Change this in your %content_settings%.
|
You can download the LBRY Desktop or Android app on %download_url% and enable mature content in
|
||||||
|
Settings.
|
||||||
</I18nMessage>
|
</I18nMessage>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -37,12 +37,12 @@ function FileViewCount(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="media__subtitle--centered">
|
<span className="media__subtitle--centered">
|
||||||
{isLive &&
|
{livestream &&
|
||||||
__('%viewer_count% currently %viewer_state%', {
|
__('%viewer_count% currently %viewer_state%', {
|
||||||
viewer_count: activeViewers === undefined ? '...' : activeViewers,
|
viewer_count: activeViewers === undefined ? '...' : activeViewers,
|
||||||
viewer_state: isLive ? __('watching') : __('waiting'),
|
viewer_state: isLive ? __('watching') : __('waiting'),
|
||||||
})}
|
})}
|
||||||
{!isLive &&
|
{!livestream &&
|
||||||
activeViewers === undefined &&
|
activeViewers === undefined &&
|
||||||
(viewCount !== 1 ? __('%view_count% views', { view_count: formattedViewCount }) : __('1 view'))}
|
(viewCount !== 1 ? __('%view_count% views', { view_count: formattedViewCount }) : __('1 view'))}
|
||||||
{!SIMPLE_SITE && <HelpLink href="https://lbry.com/faq/views" />}
|
{!SIMPLE_SITE && <HelpLink href="https://lbry.com/faq/views" />}
|
||||||
|
|
|
@ -5,7 +5,8 @@ import FilePrice from 'component/filePrice';
|
||||||
import { formatLbryUrlForWeb } from 'util/url';
|
import { formatLbryUrlForWeb } from 'util/url';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import { URL } from 'config';
|
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 = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
|
@ -36,7 +37,10 @@ function FileViewerEmbeddedTitle(props: Props) {
|
||||||
{...contentLinkProps}
|
{...contentLinkProps}
|
||||||
/>
|
/>
|
||||||
<div className="file-viewer__embedded-info">
|
<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} />}
|
{isInApp && <FilePrice uri={uri} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { LOGO_TITLE, ENABLE_NO_SOURCE_CLAIMS, CHANNEL_STAKED_LEVEL_LIVESTREAM } from 'config';
|
import { ENABLE_NO_SOURCE_CLAIMS, CHANNEL_STAKED_LEVEL_LIVESTREAM } from 'config';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import { SETTINGS } from 'lbry-redux';
|
import { SETTINGS } from 'lbry-redux';
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
|
@ -19,6 +19,8 @@ import ChannelThumbnail from 'component/channelThumbnail';
|
||||||
import { remote } from 'electron';
|
import { remote } from 'electron';
|
||||||
import { IS_MAC } from 'component/app/view';
|
import { IS_MAC } from 'component/app/view';
|
||||||
// @endif
|
// @endif
|
||||||
|
import OdyseeLogoWithWhiteText from './odysee_white.png';
|
||||||
|
import OdyseeLogoWithText from './odysee.png';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
user: ?User,
|
user: ?User,
|
||||||
|
@ -87,8 +89,8 @@ const Header = (props: Props) => {
|
||||||
sidebarOpen,
|
sidebarOpen,
|
||||||
setSidebarOpen,
|
setSidebarOpen,
|
||||||
isAbsoluteSideNavHidden,
|
isAbsoluteSideNavHidden,
|
||||||
user,
|
|
||||||
hideCancel,
|
hideCancel,
|
||||||
|
user,
|
||||||
activeChannelClaim,
|
activeChannelClaim,
|
||||||
activeChannelStakedLevel,
|
activeChannelStakedLevel,
|
||||||
} = props;
|
} = props;
|
||||||
|
@ -100,7 +102,7 @@ const Header = (props: Props) => {
|
||||||
const isPwdResetPage = history.location.pathname.includes(PAGES.AUTH_PASSWORD_RESET);
|
const isPwdResetPage = history.location.pathname.includes(PAGES.AUTH_PASSWORD_RESET);
|
||||||
const hasBackout = Boolean(backout);
|
const hasBackout = Boolean(backout);
|
||||||
const { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {};
|
const { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {};
|
||||||
const notificationsEnabled = (user && user.experimental_ui) || false;
|
// const notificationsEnabled = (user && user.experimental_ui) || false;
|
||||||
const livestreamEnabled = Boolean(
|
const livestreamEnabled = Boolean(
|
||||||
ENABLE_NO_SOURCE_CLAIMS &&
|
ENABLE_NO_SOURCE_CLAIMS &&
|
||||||
user &&
|
user &&
|
||||||
|
@ -246,19 +248,12 @@ const Header = (props: Props) => {
|
||||||
icon={ICONS.MENU}
|
icon={ICONS.MENU}
|
||||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||||
>
|
>
|
||||||
{isAbsoluteSideNavHidden && isMobile && notificationsEnabled && <NotificationBubble />}
|
{isAbsoluteSideNavHidden && isMobile && <NotificationBubble />}
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
className="header__navigation-item header__navigation-item--lbry"
|
className="header__navigation-item header__navigation-item--lbry header__navigation-item--button-mobile"
|
||||||
// @if TARGET='app'
|
|
||||||
label={'LBRY'}
|
|
||||||
// @endif
|
|
||||||
// @if TARGET='web'
|
|
||||||
label={LOGO_TITLE} // eslint-disable-line
|
|
||||||
// @endif
|
|
||||||
icon={ICONS.LBRY}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (history.location.pathname === '/') window.location.reload();
|
if (history.location.pathname === '/') window.location.reload();
|
||||||
}}
|
}}
|
||||||
|
@ -268,7 +263,12 @@ const Header = (props: Props) => {
|
||||||
}}
|
}}
|
||||||
// @endif
|
// @endif
|
||||||
{...homeButtonNavigationProps}
|
{...homeButtonNavigationProps}
|
||||||
/>
|
>
|
||||||
|
<img
|
||||||
|
src={currentTheme === 'light' ? OdyseeLogoWithText : OdyseeLogoWithWhiteText}
|
||||||
|
className="header__odysee"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
{!authHeader && (
|
{!authHeader && (
|
||||||
<div className="header__center">
|
<div className="header__center">
|
||||||
|
@ -285,7 +285,7 @@ const Header = (props: Props) => {
|
||||||
|
|
||||||
<HeaderMenuButtons
|
<HeaderMenuButtons
|
||||||
authenticated={authenticated}
|
authenticated={authenticated}
|
||||||
notificationsEnabled={notificationsEnabled}
|
notificationsEnabled
|
||||||
history={history}
|
history={history}
|
||||||
handleThemeToggle={handleThemeToggle}
|
handleThemeToggle={handleThemeToggle}
|
||||||
currentTheme={currentTheme}
|
currentTheme={currentTheme}
|
||||||
|
@ -438,6 +438,10 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
||||||
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
||||||
{__('New Channel')}
|
{__('New Channel')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.YOUTUBE_SYNC}`)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.YOUTUBE} />
|
||||||
|
{__('Sync YouTube Channel')}
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
{livestreamEnabled && (
|
{livestreamEnabled && (
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.LIVESTREAM}`)}>
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.LIVESTREAM}`)}>
|
||||||
|
|
|
@ -158,7 +158,7 @@ export default function LivestreamComments(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div ref={commentsRef} className="livestream__comments-wrapper">
|
<div ref={commentsRef} className="livestream__comments-wrapper">
|
||||||
{viewMode === VIEW_MODE_CHAT && superChatsTotalAmount > 0 && (
|
{viewMode === VIEW_MODE_CHAT && superChatsTotalAmount > 0 && superChats && (
|
||||||
<div className="livestream-superchats__wrapper">
|
<div className="livestream-superchats__wrapper">
|
||||||
<div className="livestream-superchats__inner">
|
<div className="livestream-superchats__inner">
|
||||||
{superChats.map((superChat: Comment) => (
|
{superChats.map((superChat: Comment) => (
|
||||||
|
|
9
ui/component/livestreamFeed/index.js
Normal file
9
ui/component/livestreamFeed/index.js
Normal 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);
|
38
ui/component/livestreamFeed/view.jsx
Normal file
38
ui/component/livestreamFeed/view.jsx
Normal 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} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ export default function LivestreamLink(props: Props) {
|
||||||
const { push } = useHistory();
|
const { push } = useHistory();
|
||||||
const [livestreamClaim, setLivestreamClaim] = React.useState(false);
|
const [livestreamClaim, setLivestreamClaim] = React.useState(false);
|
||||||
const [isLivestreaming, setIsLivestreaming] = 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(() => {
|
React.useEffect(() => {
|
||||||
if (livestreamChannelId) {
|
if (livestreamChannelId) {
|
||||||
|
@ -29,7 +29,7 @@ export default function LivestreamLink(props: Props) {
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res && res.items && res.items.length > 0) {
|
if (res && res.items && res.items.length > 0) {
|
||||||
const claim = res.items[res.items.length - 1];
|
const claim = res.items[0];
|
||||||
setLivestreamClaim(claim);
|
setLivestreamClaim(claim);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,14 +5,12 @@ import classnames from 'classnames';
|
||||||
type Props = {
|
type Props = {
|
||||||
unseenCount: number,
|
unseenCount: number,
|
||||||
inline: boolean,
|
inline: boolean,
|
||||||
user: ?User,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function NotificationHeaderButton(props: Props) {
|
export default function NotificationHeaderButton(props: Props) {
|
||||||
const { unseenCount, inline = false, user } = props;
|
const { unseenCount, inline = false } = props;
|
||||||
const notificationsEnabled = user && user.experimental_ui;
|
|
||||||
|
|
||||||
if (unseenCount === 0 || !notificationsEnabled) {
|
if (unseenCount === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,8 @@ export default function NotificationHeaderButton(props: Props) {
|
||||||
// notifications,
|
// notifications,
|
||||||
// fetching,
|
// fetching,
|
||||||
doSeeAllNotifications,
|
doSeeAllNotifications,
|
||||||
user,
|
// user,
|
||||||
} = props;
|
} = props;
|
||||||
const notificationsEnabled = user && user.experimental_ui;
|
|
||||||
const { push } = useHistory();
|
const { push } = useHistory();
|
||||||
|
|
||||||
function handleMenuClick() {
|
function handleMenuClick() {
|
||||||
|
@ -32,10 +31,6 @@ export default function NotificationHeaderButton(props: Props) {
|
||||||
push(`/$/${PAGES.NOTIFICATIONS}`);
|
push(`/$/${PAGES.NOTIFICATIONS}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!notificationsEnabled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleMenuClick}
|
onClick={handleMenuClick}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import type { Node } from 'react';
|
import type { Node } from 'react';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import SideNavigation from 'component/sideNavigation';
|
import SideNavigation from 'component/sideNavigation';
|
||||||
|
@ -22,7 +23,6 @@ type Props = {
|
||||||
isUpgradeAvailable: boolean,
|
isUpgradeAvailable: boolean,
|
||||||
authPage: boolean,
|
authPage: boolean,
|
||||||
filePage: boolean,
|
filePage: boolean,
|
||||||
homePage: boolean,
|
|
||||||
noHeader: boolean,
|
noHeader: boolean,
|
||||||
noFooter: boolean,
|
noFooter: boolean,
|
||||||
noSideNavigation: boolean,
|
noSideNavigation: boolean,
|
||||||
|
|
|
@ -612,7 +612,7 @@ function PublishFile(props: Props) {
|
||||||
{/* @if TARGET='app' */}
|
{/* @if TARGET='app' */}
|
||||||
{showFileUpload && (
|
{showFileUpload && (
|
||||||
<FileSelector
|
<FileSelector
|
||||||
label={__('File')}
|
label={__('Video file')}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
currentPath={currentFile}
|
currentPath={currentFile}
|
||||||
onFileChosen={handleFileChange}
|
onFileChosen={handleFileChange}
|
||||||
|
|
|
@ -182,9 +182,9 @@ function PublishForm(props: Props) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (editingURI) {
|
if (editingURI) {
|
||||||
customSubtitle = __('Update your video');
|
customSubtitle = __('Update your video/audio');
|
||||||
} else {
|
} else {
|
||||||
customSubtitle = __('Upload that unlabeled video you found behind the TV in 1991');
|
customSubtitle = __('Upload that unlabeled video or cassette you found behind the TV in 1991');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,6 +566,7 @@ function PublishForm(props: Props) {
|
||||||
<Button
|
<Button
|
||||||
key={String(modeName)}
|
key={String(modeName)}
|
||||||
icon={modeName}
|
icon={modeName}
|
||||||
|
iconSize={18}
|
||||||
label={__(MODE_TO_I18N_STR[String(modeName)] || '---')}
|
label={__(MODE_TO_I18N_STR[String(modeName)] || '---')}
|
||||||
button="alt"
|
button="alt"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
@ -13,7 +13,6 @@ type Props = {
|
||||||
|
|
||||||
function NameHelpText(props: Props) {
|
function NameHelpText(props: Props) {
|
||||||
const { uri, myClaimForUri, onEditMyClaim, isStillEditing } = props;
|
const { uri, myClaimForUri, onEditMyClaim, isStillEditing } = props;
|
||||||
|
|
||||||
let nameHelpText;
|
let nameHelpText;
|
||||||
|
|
||||||
if (isStillEditing) {
|
if (isStillEditing) {
|
||||||
|
|
|
@ -20,20 +20,20 @@ import Spinner from 'component/spinner';
|
||||||
type Props = {
|
type Props = {
|
||||||
doToast: ({ message: string }) => void,
|
doToast: ({ message: string }) => void,
|
||||||
doClearRepostError: () => void,
|
doClearRepostError: () => void,
|
||||||
doRepost: StreamRepostOptions => Promise<*>,
|
doRepost: (StreamRepostOptions) => Promise<*>,
|
||||||
title: string,
|
title: string,
|
||||||
claim?: StreamClaim,
|
claim?: StreamClaim,
|
||||||
enteredContentClaim?: StreamClaim,
|
enteredContentClaim?: StreamClaim,
|
||||||
balance: number,
|
balance: number,
|
||||||
channels: ?Array<ChannelClaim>,
|
channels: ?Array<ChannelClaim>,
|
||||||
doCheckPublishNameAvailability: string => Promise<*>,
|
doCheckPublishNameAvailability: (string) => Promise<*>,
|
||||||
error: ?string,
|
error: ?string,
|
||||||
reposting: boolean,
|
reposting: boolean,
|
||||||
uri: string,
|
uri: string,
|
||||||
name: string,
|
name: string,
|
||||||
contentUri: string,
|
contentUri: string,
|
||||||
setRepostUri: string => void,
|
setRepostUri: (string) => void,
|
||||||
setContentUri: string => void,
|
setContentUri: (string) => void,
|
||||||
doCheckPendingClaims: () => void,
|
doCheckPendingClaims: () => void,
|
||||||
redirectUri?: string,
|
redirectUri?: string,
|
||||||
passedRepostAmount: number,
|
passedRepostAmount: number,
|
||||||
|
@ -88,7 +88,7 @@ function RepostCreate(props: Props) {
|
||||||
const repostUrlName = `lbry://${incognito || !activeChannelClaim ? '' : `${activeChannelClaim.name}/`}`;
|
const repostUrlName = `lbry://${incognito || !activeChannelClaim ? '' : `${activeChannelClaim.name}/`}`;
|
||||||
|
|
||||||
const contentFirstRender = React.useRef(true);
|
const contentFirstRender = React.useRef(true);
|
||||||
const setAutoRepostBid = amount => {
|
const setAutoRepostBid = (amount) => {
|
||||||
if (balance && balance > 0.02) {
|
if (balance && balance > 0.02) {
|
||||||
if (uri) {
|
if (uri) {
|
||||||
setRepostBid(0.01);
|
setRepostBid(0.01);
|
||||||
|
@ -113,7 +113,7 @@ function RepostCreate(props: Props) {
|
||||||
const isLbryUrl = value.startsWith('lbry://') && value !== 'lbry://';
|
const isLbryUrl = value.startsWith('lbry://') && value !== 'lbry://';
|
||||||
const error = '';
|
const error = '';
|
||||||
|
|
||||||
const addLbryIfNot = term => {
|
const addLbryIfNot = (term) => {
|
||||||
return term.startsWith('lbry://') ? term : `lbry://${term}`;
|
return term.startsWith('lbry://') ? term : `lbry://${term}`;
|
||||||
};
|
};
|
||||||
if (wasCopiedFromWeb) {
|
if (wasCopiedFromWeb) {
|
||||||
|
@ -178,7 +178,7 @@ function RepostCreate(props: Props) {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (enteredRepostName && isNameValid(enteredRepostName, false)) {
|
if (enteredRepostName && isNameValid(enteredRepostName, false)) {
|
||||||
doCheckPublishNameAvailability(enteredRepostName).then(r => setAvailable(r));
|
doCheckPublishNameAvailability(enteredRepostName).then((r) => setAvailable(r));
|
||||||
}
|
}
|
||||||
}, [enteredRepostName, doCheckPublishNameAvailability]);
|
}, [enteredRepostName, doCheckPublishNameAvailability]);
|
||||||
|
|
||||||
|
@ -320,7 +320,7 @@ function RepostCreate(props: Props) {
|
||||||
<div>
|
<div>
|
||||||
{uri && (
|
{uri && (
|
||||||
<fieldset-section>
|
<fieldset-section>
|
||||||
<ClaimPreview key={uri} uri={uri} actions={''} type={'inline'} showNullPlaceholder />
|
<ClaimPreview key={uri} uri={uri} actions={''} showNullPlaceholder />
|
||||||
</fieldset-section>
|
</fieldset-section>
|
||||||
)}
|
)}
|
||||||
{!uri && name && (
|
{!uri && name && (
|
||||||
|
@ -331,7 +331,7 @@ function RepostCreate(props: Props) {
|
||||||
name="content_url"
|
name="content_url"
|
||||||
value={enteredContent}
|
value={enteredContent}
|
||||||
error={contentError}
|
error={contentError}
|
||||||
onChange={event => setEnteredContentUri(event.target.value)}
|
onChange={(event) => setEnteredContentUri(event.target.value)}
|
||||||
placeholder={__('Enter a name or %domain% URL', { domain: SITE_URL })}
|
placeholder={__('Enter a name or %domain% URL', { domain: SITE_URL })}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -363,7 +363,7 @@ function RepostCreate(props: Props) {
|
||||||
type="text"
|
type="text"
|
||||||
name="repost_name"
|
name="repost_name"
|
||||||
value={enteredRepostName}
|
value={enteredRepostName}
|
||||||
onChange={event => setEnteredRepostName(event.target.value)}
|
onChange={(event) => setEnteredRepostName(event.target.value)}
|
||||||
placeholder={__('MyFunName')}
|
placeholder={__('MyFunName')}
|
||||||
/>
|
/>
|
||||||
</fieldset-group>
|
</fieldset-group>
|
||||||
|
@ -388,8 +388,8 @@ function RepostCreate(props: Props) {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
disabled={!enteredRepostName || resolvingRepost}
|
disabled={!enteredRepostName || resolvingRepost}
|
||||||
onChange={event => setRepostBid(event.target.value)}
|
onChange={(event) => setRepostBid(event.target.value)}
|
||||||
onWheel={e => e.stopPropagation()}
|
onWheel={(e) => e.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
||||||
|
|
|
@ -123,7 +123,7 @@ class SelectThumbnail extends React.PureComponent<Props> {
|
||||||
<FileSelector
|
<FileSelector
|
||||||
currentPath={thumbnailPath}
|
currentPath={thumbnailPath}
|
||||||
label={__('Thumbnail')}
|
label={__('Thumbnail')}
|
||||||
placeholder={__('Choose a thumbnail')}
|
placeholder={__('Thumbnails that entice a viewer to watch a video work best')}
|
||||||
accept={accept}
|
accept={accept}
|
||||||
onFileChosen={(file) => openModal(MODALS.CONFIRM_THUMBNAIL_UPLOAD, { file })}
|
onFileChosen={(file) => openModal(MODALS.CONFIRM_THUMBNAIL_UPLOAD, { file })}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -213,7 +213,7 @@ function SideNavigation(props: Props) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const notificationsEnabled = user && user.experimental_ui;
|
const notificationsEnabled = SIMPLE_SITE || (user && user.experimental_ui);
|
||||||
const isAuthenticated = Boolean(email);
|
const isAuthenticated = Boolean(email);
|
||||||
// SIDE LINKS: FOLLOWING, HOME, [FULL,] [EXTRA]
|
// SIDE LINKS: FOLLOWING, HOME, [FULL,] [EXTRA]
|
||||||
let SIDE_LINKS: Array<SideNavLink> = [];
|
let SIDE_LINKS: Array<SideNavLink> = [];
|
||||||
|
@ -312,6 +312,11 @@ function SideNavigation(props: Props) {
|
||||||
<li className="navigation-link">
|
<li className="navigation-link">
|
||||||
<Button label={__('FAQ')} href="https://odysee.com/@OdyseeHelp:b" />
|
<Button label={__('FAQ')} href="https://odysee.com/@OdyseeHelp:b" />
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li className="navigation-link">
|
||||||
|
<Button label={__('Community Guidelines')} href="https://odysee.com/@OdyseeHelp:b/Community-Guidelines:c" />
|
||||||
|
</li>
|
||||||
|
|
||||||
<li className="navigation-link">
|
<li className="navigation-link">
|
||||||
<Button label={__('Support --[used in footer; general help/support]--')} href="https://lbry.com/support" />
|
<Button label={__('Support --[used in footer; general help/support]--')} href="https://lbry.com/support" />
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
} from 'redux/selectors/subscriptions';
|
} from 'redux/selectors/subscriptions';
|
||||||
import { makeSelectPermanentUrlForUri } from 'lbry-redux';
|
import { makeSelectPermanentUrlForUri } from 'lbry-redux';
|
||||||
import { doToast } from 'redux/actions/notifications';
|
import { doToast } from 'redux/actions/notifications';
|
||||||
import { selectUser } from 'redux/selectors/user';
|
|
||||||
import SubscribeButton from './view';
|
import SubscribeButton from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
|
@ -15,7 +14,6 @@ const select = (state, props) => ({
|
||||||
firstRunCompleted: selectFirstRunCompleted(state),
|
firstRunCompleted: selectFirstRunCompleted(state),
|
||||||
permanentUrl: makeSelectPermanentUrlForUri(props.uri)(state),
|
permanentUrl: makeSelectPermanentUrlForUri(props.uri)(state),
|
||||||
notificationsDisabled: makeSelectNotificationsDisabled(props.uri)(state),
|
notificationsDisabled: makeSelectNotificationsDisabled(props.uri)(state),
|
||||||
user: selectUser(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, {
|
export default connect(select, {
|
||||||
|
|
|
@ -43,7 +43,6 @@ export default function SubscribeButton(props: Props) {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
let isHovering = useHover(buttonRef);
|
let isHovering = useHover(buttonRef);
|
||||||
isHovering = isMobile ? true : isHovering;
|
isHovering = isMobile ? true : isHovering;
|
||||||
const uiNotificationsEnabled = user && user.experimental_ui;
|
|
||||||
|
|
||||||
const { channelName: rawChannelName } = parseURI(uri);
|
const { channelName: rawChannelName } = parseURI(uri);
|
||||||
const { channelName } = parseURI(permanentUrl);
|
const { channelName } = parseURI(permanentUrl);
|
||||||
|
@ -109,7 +108,7 @@ export default function SubscribeButton(props: Props) {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{isSubscribed && uiNotificationsEnabled && (
|
{isSubscribed && (
|
||||||
<Button
|
<Button
|
||||||
button="alt"
|
button="alt"
|
||||||
icon={notificationsDisabled ? ICONS.BELL : ICONS.BELL_ON}
|
icon={notificationsDisabled ? ICONS.BELL : ICONS.BELL_ON}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import { AUTO_FOLLOW_CHANNELS, SIMPLE_SITE } from 'config';
|
||||||
type Props = {
|
type Props = {
|
||||||
subscribedChannels: Array<Subscription>,
|
subscribedChannels: Array<Subscription>,
|
||||||
onContinue: () => void,
|
onContinue: () => void,
|
||||||
onBack: () => void,
|
|
||||||
channelSubscribe: (sub: Subscription) => void,
|
channelSubscribe: (sub: Subscription) => void,
|
||||||
homepageData: any,
|
homepageData: any,
|
||||||
prefsReady: boolean,
|
prefsReady: boolean,
|
||||||
|
@ -45,25 +44,25 @@ function UserChannelFollowIntro(props: Props) {
|
||||||
<Card
|
<Card
|
||||||
title={__('Find channels to follow')}
|
title={__('Find channels to follow')}
|
||||||
subtitle={__(
|
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={
|
actions={
|
||||||
<React.Fragment>
|
<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">
|
<div className="section__body">
|
||||||
<ClaimListDiscover
|
<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}
|
defaultFreshness={CS.FRESH_ALL}
|
||||||
claimType="channel"
|
claimIds={SIMPLE_SITE ? PRIMARY_CONTENT_CHANNEL_IDS : undefined}
|
||||||
claimIds={SIMPLE_SITE ? undefined : PRIMARY_CONTENT_CHANNEL_IDS}
|
claimType={CS.CLAIM_CHANNEL}
|
||||||
defaultTags={followingCount > 3 ? CS.TAGS_FOLLOWED : undefined}
|
maxPages={3}
|
||||||
/>
|
/>
|
||||||
{followingCount > 0 && (
|
{followingCount > 0 && (
|
||||||
<Nag
|
<Nag
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as PAGES from 'constants/pages';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
|
@ -7,7 +6,6 @@ import UserEmailNew from 'component/userEmailNew';
|
||||||
import UserEmailVerify from 'component/userEmailVerify';
|
import UserEmailVerify from 'component/userEmailVerify';
|
||||||
import UserFirstChannel from 'component/userFirstChannel';
|
import UserFirstChannel from 'component/userFirstChannel';
|
||||||
import UserChannelFollowIntro from 'component/userChannelFollowIntro';
|
import UserChannelFollowIntro from 'component/userChannelFollowIntro';
|
||||||
import UserTagFollowIntro from 'component/userTagFollowIntro';
|
|
||||||
import YoutubeSync from 'page/youtubeSync';
|
import YoutubeSync from 'page/youtubeSync';
|
||||||
import { DEFAULT_BID_FOR_FIRST_CHANNEL } from 'component/userFirstChannel/view';
|
import { DEFAULT_BID_FOR_FIRST_CHANNEL } from 'component/userFirstChannel/view';
|
||||||
import { YOUTUBE_STATUSES } from 'lbryinc';
|
import { YOUTUBE_STATUSES } from 'lbryinc';
|
||||||
|
@ -42,7 +40,6 @@ type Props = {
|
||||||
creatingChannel: boolean,
|
creatingChannel: boolean,
|
||||||
setClientSetting: (string, boolean, ?boolean) => void,
|
setClientSetting: (string, boolean, ?boolean) => void,
|
||||||
followingAcknowledged: boolean,
|
followingAcknowledged: boolean,
|
||||||
tagsAcknowledged: boolean,
|
|
||||||
rewardsAcknowledged: boolean,
|
rewardsAcknowledged: boolean,
|
||||||
interestedInYoutubeSync: boolean,
|
interestedInYoutubeSync: boolean,
|
||||||
doToggleInterestedInYoutubeSync: () => void,
|
doToggleInterestedInYoutubeSync: () => void,
|
||||||
|
@ -67,7 +64,6 @@ function UserSignUp(props: Props) {
|
||||||
fetchingChannels,
|
fetchingChannels,
|
||||||
creatingChannel,
|
creatingChannel,
|
||||||
followingAcknowledged,
|
followingAcknowledged,
|
||||||
tagsAcknowledged,
|
|
||||||
rewardsAcknowledged,
|
rewardsAcknowledged,
|
||||||
setClientSetting,
|
setClientSetting,
|
||||||
interestedInYoutubeSync,
|
interestedInYoutubeSync,
|
||||||
|
@ -118,8 +114,7 @@ function UserSignUp(props: Props) {
|
||||||
interestedInYoutubeSync);
|
interestedInYoutubeSync);
|
||||||
const showYoutubeTransfer = hasVerifiedEmail && hasYoutubeChannels && !isYoutubeTransferComplete;
|
const showYoutubeTransfer = hasVerifiedEmail && hasYoutubeChannels && !isYoutubeTransferComplete;
|
||||||
const showFollowIntro = step === 'channels' || (hasVerifiedEmail && !followingAcknowledged);
|
const showFollowIntro = step === 'channels' || (hasVerifiedEmail && !followingAcknowledged);
|
||||||
const showTagsIntro = step === 'tags' || (hasVerifiedEmail && !tagsAcknowledged);
|
const canHijackSignInFlowWithSpinner = hasVerifiedEmail && !showFollowIntro && !rewardsAcknowledged;
|
||||||
const canHijackSignInFlowWithSpinner = hasVerifiedEmail && !showFollowIntro && !showTagsIntro && !rewardsAcknowledged;
|
|
||||||
const isCurrentlyFetchingSomething = fetchingChannels || claimingReward || syncingWallet || creatingChannel;
|
const isCurrentlyFetchingSomething = fetchingChannels || claimingReward || syncingWallet || creatingChannel;
|
||||||
const isWaitingForSomethingToFinish =
|
const isWaitingForSomethingToFinish =
|
||||||
// If the user has claimed the email award, we need to wait until the balance updates sometime in the future
|
// 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 && (
|
showYoutubeTransfer && (
|
||||||
<div>
|
<div>
|
||||||
<YoutubeTransferStatus /> <Confetti recycle={false} style={{ position: 'fixed' }} />
|
<YoutubeTransferStatus /> <Confetti recycle={false} style={{ position: 'fixed' }} />
|
||||||
|
|
|
@ -12,7 +12,7 @@ import LbcSymbol from 'component/common/lbc-symbol';
|
||||||
type Props = {
|
type Props = {
|
||||||
errorMessage: ?string,
|
errorMessage: ?string,
|
||||||
isPending: boolean,
|
isPending: boolean,
|
||||||
verifyUserIdentity: string => void,
|
verifyUserIdentity: (string) => void,
|
||||||
verifyPhone: () => void,
|
verifyPhone: () => void,
|
||||||
fetchUser: () => void,
|
fetchUser: () => void,
|
||||||
skipLink?: string,
|
skipLink?: string,
|
||||||
|
@ -60,7 +60,7 @@ class UserVerify extends React.PureComponent<Props> {
|
||||||
SITE_NAME,
|
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%.
|
invite links etc. Verifying also helps us keep the %SITE_NAME% community safe too! %Refresh% or %Skip%.
|
||||||
</I18nMessage>
|
</I18nMessage>
|
||||||
</p>
|
</p>
|
||||||
|
@ -73,9 +73,7 @@ class UserVerify extends React.PureComponent<Props> {
|
||||||
<Card
|
<Card
|
||||||
icon={ICONS.PHONE}
|
icon={ICONS.PHONE}
|
||||||
title={__('Verify phone number')}
|
title={__('Verify phone number')}
|
||||||
subtitle={__(
|
subtitle={__('You will receive an SMS text message confirming your phone number is valid.')}
|
||||||
'You will receive an SMS text message confirming your phone number is valid. Does not work for Canada and possibly other regions.'
|
|
||||||
)}
|
|
||||||
actions={
|
actions={
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -9,13 +9,14 @@ import { withRouter } from 'react-router';
|
||||||
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
|
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
|
||||||
import { makeSelectClientSetting, selectHomepageData } from 'redux/selectors/settings';
|
import { makeSelectClientSetting, selectHomepageData } from 'redux/selectors/settings';
|
||||||
import { toggleVideoTheaterMode, doSetClientSetting } from 'redux/actions/settings';
|
import { toggleVideoTheaterMode, doSetClientSetting } from 'redux/actions/settings';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
const { search } = props.location;
|
const { search } = props.location;
|
||||||
const urlParams = new URLSearchParams(search);
|
const urlParams = new URLSearchParams(search);
|
||||||
const autoplay = urlParams.get('autoplay');
|
const autoplay = urlParams.get('autoplay');
|
||||||
const position = urlParams.get('t') !== null ? urlParams.get('t') : makeSelectContentPositionForUri(props.uri)(state);
|
const position = urlParams.get('t') !== null ? urlParams.get('t') : makeSelectContentPositionForUri(props.uri)(state);
|
||||||
|
const userId = selectUser(state) && selectUser(state).id;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
autoplayIfEmbedded: Boolean(autoplay),
|
autoplayIfEmbedded: Boolean(autoplay),
|
||||||
|
@ -29,6 +30,7 @@ const select = (state, props) => {
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
homepageData: selectHomepageData(state),
|
homepageData: selectHomepageData(state),
|
||||||
authenticated: selectUserVerifiedEmail(state),
|
authenticated: selectUserVerifiedEmail(state),
|
||||||
|
userId: userId,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
// Created by xander on 6/21/2021
|
||||||
|
import videojs from 'video.js';
|
||||||
|
import 'videojs-ima';
|
||||||
|
const VERSION = '0.0.1';
|
||||||
|
|
||||||
|
const macroUrl =
|
||||||
|
'https://vast.aniview.com/api/adserver61/vast/?AV_PUBLISHERID=60afcbc58cfdb065440d2426&AV_CHANNELID=60b354389c7adb506d0bd9a4&AV_URL=[URL_MACRO]&cb=[TIMESTAMP_MACRO]&AV_WIDTH=[WIDTH_MACRO]&AV_HEIGHT=[HEIGHT_MACRO]&AV_SCHAIN=[SCHAIN_MACRO]&AV_CCPA=[CCPA_MACRO]&AV_GDPR=[GDPR_MACRO]&AV_CONSENT=[CONSENT_MACRO]&skip=true&skiptimer=5&usevslot=true&hidecontrols=false';
|
||||||
|
|
||||||
|
const defaults = {
|
||||||
|
adTagUrl: macroUrl,
|
||||||
|
debug: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Component = videojs.getComponent('Component');
|
||||||
|
const registerPlugin = videojs.registerPlugin || videojs.plugin;
|
||||||
|
|
||||||
|
class AniviewPlugin extends Component {
|
||||||
|
constructor(player, options) {
|
||||||
|
super(player, options);
|
||||||
|
|
||||||
|
// Plugin started
|
||||||
|
if (options.debug) {
|
||||||
|
this.log(`Created aniview plugin.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// To help with debugging, we'll add a global vjs object with the video js player
|
||||||
|
window.aniview = player;
|
||||||
|
|
||||||
|
this.player = player;
|
||||||
|
|
||||||
|
player.ima({
|
||||||
|
// adTagUrl: macroUrl,
|
||||||
|
id: 'ad_content_video',
|
||||||
|
vpaidMode: google.ima.ImaSdkSettings.VpaidMode.INSECURE,
|
||||||
|
adTagUrl:
|
||||||
|
'https://vast.aniview.com/api/adserver61/vast/?AV_PUBLISHERID=60afcbc58cfdb065440d2426&AV_CHANNELID=60b354389c7adb506d0bd9a4',
|
||||||
|
});
|
||||||
|
|
||||||
|
// this.player.ads();
|
||||||
|
// const serverUrl = this.player.ads.adMacroReplacement(macroUrl);
|
||||||
|
// this.log(serverUrl);
|
||||||
|
|
||||||
|
// request ads whenever there's new video content
|
||||||
|
player.on('contentchanged', () => {
|
||||||
|
// in a real plugin, you might fetch your ad inventory here
|
||||||
|
player.trigger('adsready');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Plugin event listeners
|
||||||
|
player.on('readyforpreroll', (event) => this.onReadyForPreroll(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
onReadyForPreroll(event) {
|
||||||
|
this.player.ads.startLinearAdMode();
|
||||||
|
|
||||||
|
// play your linear ad content
|
||||||
|
// in this example, we use a static mp4
|
||||||
|
this.player.src('kitteh.mp4');
|
||||||
|
|
||||||
|
// send event when ad is playing to remove loading spinner
|
||||||
|
this.player.one('adplaying', () => {
|
||||||
|
this.player.trigger('ads-ad-started');
|
||||||
|
});
|
||||||
|
|
||||||
|
// resume content when all your linear ads have finished
|
||||||
|
this.player.one('adended', () => {
|
||||||
|
this.player.ads.endLinearAdMode();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log(...args) {
|
||||||
|
if (this.options_.debug) {
|
||||||
|
console.log(`Aniview Debug:`, JSON.stringify(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
videojs.registerComponent('recsys', AniviewPlugin);
|
||||||
|
|
||||||
|
const onPlayerReady = (player, options) => {
|
||||||
|
player.aniview = new AniviewPlugin(player, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the plugin.
|
||||||
|
*
|
||||||
|
* @function plugin
|
||||||
|
* @param {Object} [options={}]
|
||||||
|
*/
|
||||||
|
const plugin = function (options) {
|
||||||
|
this.ready(() => {
|
||||||
|
onPlayerReady(this, videojs.mergeOptions(defaults, options));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
plugin.VERSION = VERSION;
|
||||||
|
|
||||||
|
registerPlugin('aniview', plugin);
|
||||||
|
|
||||||
|
export default plugin;
|
|
@ -1,4 +1,4 @@
|
||||||
import videojs from 'video.js/dist/video.min.js';
|
import videojs from 'video.js';
|
||||||
import './touchOverlay.js';
|
import './touchOverlay.js';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
import './plugin.scss';
|
import './plugin.scss';
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* Touch UI component
|
* Touch UI component
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import videojs from 'video.js/dist/video.min.js';
|
import videojs from 'video.js';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
|
|
||||||
const Component = videojs.getComponent('Component');
|
const Component = videojs.getComponent('Component');
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import videojs from 'video.js/dist/video.min.js';
|
import videojs from 'video.js';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
const VERSION = '2.1.4';
|
const VERSION = '2.1.4';
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,239 @@
|
||||||
|
// Created by xander on 6/21/2021
|
||||||
|
import videojs from 'video.js';
|
||||||
|
import { v4 as uuidV4 } from 'uuid';
|
||||||
|
const VERSION = '0.0.1';
|
||||||
|
|
||||||
|
const recsysEndpoint = 'https://clickstream.odysee.com/log/video/view';
|
||||||
|
const recsysId = 'lighthouse-v0';
|
||||||
|
|
||||||
|
/* RecSys */
|
||||||
|
const RecsysData = {
|
||||||
|
event: {
|
||||||
|
start: 0,
|
||||||
|
stop: 1,
|
||||||
|
scrub: 2,
|
||||||
|
speed: 3,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function createRecsys(claimId, userId, events, loadedAt, isEmbed) {
|
||||||
|
// TODO: use a UUID generator
|
||||||
|
const uuid = uuidV4();
|
||||||
|
const pageLoadedAt = loadedAt;
|
||||||
|
const pageExitedAt = Date.now();
|
||||||
|
return {
|
||||||
|
uuid: uuid,
|
||||||
|
parentUuid: null,
|
||||||
|
uid: userId,
|
||||||
|
claimId: claimId,
|
||||||
|
pageLoadedAt: pageLoadedAt,
|
||||||
|
pageExitedAt: pageExitedAt,
|
||||||
|
recsysId: recsysId,
|
||||||
|
recClaimIds: null,
|
||||||
|
recClickedVideoIdx: null,
|
||||||
|
events: events,
|
||||||
|
isEmbed: isEmbed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function newRecsysEvent(eventType, offset, arg) {
|
||||||
|
if (arg) {
|
||||||
|
return {
|
||||||
|
event: eventType,
|
||||||
|
offset: offset,
|
||||||
|
arg: arg,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
event: eventType,
|
||||||
|
offset: offset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendRecsysEvents(recsys) {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'text/plain' }, // application/json
|
||||||
|
body: JSON.stringify(recsys),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
fetch(recsysEndpoint, requestOptions)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
// console.log(`Recsys response data:`, data);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// console.error(`Recsys Error`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaults = {
|
||||||
|
endpoint: recsysEndpoint,
|
||||||
|
recsysId: recsysId,
|
||||||
|
videoId: null,
|
||||||
|
userId: 0,
|
||||||
|
debug: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Component = videojs.getComponent('Component');
|
||||||
|
const registerPlugin = videojs.registerPlugin || videojs.plugin;
|
||||||
|
|
||||||
|
class RecsysPlugin extends Component {
|
||||||
|
constructor(player, options) {
|
||||||
|
super(player, options);
|
||||||
|
|
||||||
|
// Plugin started
|
||||||
|
if (options.debug) {
|
||||||
|
this.log(`Created recsys plugin for: videoId:${options.videoId}, userId:${options.userId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// To help with debugging, we'll add a global vjs object with the video js player
|
||||||
|
window.vjs = player;
|
||||||
|
|
||||||
|
this.player = player;
|
||||||
|
|
||||||
|
this.recsysEvents = [];
|
||||||
|
this.loadedAt = Date.now();
|
||||||
|
this.lastTimeUpdate = null;
|
||||||
|
this.currentTimeUpdate = null;
|
||||||
|
this.inPause = false;
|
||||||
|
|
||||||
|
// Plugin event listeners
|
||||||
|
player.on('playing', (event) => this.onPlay(event));
|
||||||
|
player.on('pause', (event) => this.onPause(event));
|
||||||
|
player.on('ended', (event) => this.onEnded(event));
|
||||||
|
player.on('ratechange', (event) => this.onRateChange(event));
|
||||||
|
player.on('timeupdate', (event) => this.onTimeUpdate(event));
|
||||||
|
player.on('seeked', (event) => this.onSeeked(event));
|
||||||
|
|
||||||
|
// Event trigger to send recsys event
|
||||||
|
player.on('dispose', (event) => this.onDispose(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
addRecsysEvent(recsysEvent) {
|
||||||
|
// For now, don't do client-side preprocessing. I think there
|
||||||
|
// are browser inconsistencies and preprocessing loses too much info.
|
||||||
|
this.recsysEvents.push(recsysEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRecsysEvents() {
|
||||||
|
return this.recsysEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRecsysEvents() {
|
||||||
|
const event = createRecsys(
|
||||||
|
this.options_.videoId,
|
||||||
|
this.options_.userId,
|
||||||
|
this.getRecsysEvents(),
|
||||||
|
this.loadedAt,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
sendRecsysEvents(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPlay(event) {
|
||||||
|
const recsysEvent = newRecsysEvent(RecsysData.event.start, this.player.currentTime());
|
||||||
|
this.log('onPlay', recsysEvent);
|
||||||
|
this.addRecsysEvent(recsysEvent);
|
||||||
|
|
||||||
|
this.inPause = false;
|
||||||
|
this.lastTimeUpdate = recsysEvent.offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
onPause(event) {
|
||||||
|
const recsysEvent = newRecsysEvent(RecsysData.event.stop, this.player.currentTime());
|
||||||
|
this.log('onPause', recsysEvent);
|
||||||
|
this.addRecsysEvent(recsysEvent);
|
||||||
|
|
||||||
|
this.inPause = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onEnded(event) {
|
||||||
|
const recsysEvent = newRecsysEvent(RecsysData.event.stop, this.player.currentTime());
|
||||||
|
this.log('onEnded', recsysEvent);
|
||||||
|
this.addRecsysEvent(recsysEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
onRateChange(event) {
|
||||||
|
const recsysEvent = newRecsysEvent(RecsysData.event.speed, this.player.currentTime(), this.player.playbackRate());
|
||||||
|
this.log('onRateChange', recsysEvent);
|
||||||
|
this.addRecsysEvent(recsysEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
onTimeUpdate(event) {
|
||||||
|
const nextCurrentTime = this.player.currentTime();
|
||||||
|
|
||||||
|
if (!this.inPause && Math.abs(this.lastTimeUpdate - nextCurrentTime) < 0.5) {
|
||||||
|
// Don't update lastTimeUpdate if we are in a pause segment.
|
||||||
|
//
|
||||||
|
// However, if we aren't in a pause and the time jumped
|
||||||
|
// the onTimeUpdate event probably fired before the pause and seek.
|
||||||
|
// Don't update in that case, either.
|
||||||
|
this.lastTimeUpdate = this.currentTimeUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentTimeUpdate = nextCurrentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSeeked(event) {
|
||||||
|
const curTime = this.player.currentTime();
|
||||||
|
|
||||||
|
// There are three patterns for seeking:
|
||||||
|
//
|
||||||
|
// Assuming the video is playing,
|
||||||
|
//
|
||||||
|
// 1. Dragging the player head emits: onPause -> onSeeked -> onSeeked -> ... -> onPlay
|
||||||
|
// 2. Key press left right emits: onSeeked -> onPlay
|
||||||
|
// 3. Clicking a position emits: onPause -> onSeeked -> onPlay
|
||||||
|
//
|
||||||
|
// If the video is NOT playing,
|
||||||
|
//
|
||||||
|
// 1. Dragging the player head emits: onSeeked
|
||||||
|
// 2. Key press left right emits: onSeeked
|
||||||
|
// 3. Clicking a position emits: onSeeked
|
||||||
|
const fromTime = this.lastTimeUpdate;
|
||||||
|
|
||||||
|
if (fromTime !== curTime) {
|
||||||
|
// This removes duplicates that aren't useful.
|
||||||
|
const recsysEvent = newRecsysEvent(RecsysData.event.scrub, fromTime, curTime);
|
||||||
|
this.log('onSeeked', recsysEvent);
|
||||||
|
this.addRecsysEvent(recsysEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDispose(event) {
|
||||||
|
this.sendRecsysEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
log(...args) {
|
||||||
|
if (this.options_.debug) {
|
||||||
|
console.log(`Recsys Debug:`, JSON.stringify(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
videojs.registerComponent('recsys', RecsysPlugin);
|
||||||
|
|
||||||
|
const onPlayerReady = (player, options) => {
|
||||||
|
player.recsys = new RecsysPlugin(player, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the plugin.
|
||||||
|
*
|
||||||
|
* @function plugin
|
||||||
|
* @param {Object} [options={}]
|
||||||
|
*/
|
||||||
|
const plugin = function (options) {
|
||||||
|
this.ready(() => {
|
||||||
|
onPlayerReady(this, videojs.mergeOptions(defaults, options));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
plugin.VERSION = VERSION;
|
||||||
|
|
||||||
|
registerPlugin('recsys', plugin);
|
||||||
|
|
||||||
|
export default plugin;
|
|
@ -3,16 +3,36 @@ import React, { useEffect, useRef, useState } from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import videojs from 'video.js/dist/video.min.js';
|
import videojs from 'video.js';
|
||||||
// import 'video.js/dist/alt/video-js-cdn.min.css'; --> 'scss/third-party.scss'
|
// import 'video.js/dist/alt/video-js-cdn.min.css'; --> 'scss/third-party.scss'
|
||||||
import eventTracking from 'videojs-event-tracking';
|
import eventTracking from 'videojs-event-tracking';
|
||||||
import * as OVERLAY from './overlays';
|
import * as OVERLAY from './overlays';
|
||||||
import './plugins/videojs-mobile-ui/plugin';
|
import './plugins/videojs-mobile-ui/plugin';
|
||||||
import hlsQualitySelector from './plugins/videojs-hls-quality-selector/plugin';
|
import hlsQualitySelector from './plugins/videojs-hls-quality-selector/plugin';
|
||||||
|
import recsys from './plugins/videojs-recsys/plugin';
|
||||||
import qualityLevels from 'videojs-contrib-quality-levels';
|
import qualityLevels from 'videojs-contrib-quality-levels';
|
||||||
import isUserTyping from 'util/detect-typing';
|
import isUserTyping from 'util/detect-typing';
|
||||||
|
import 'videojs-contrib-ads';
|
||||||
|
import 'videojs-ima';
|
||||||
|
import aniview from './plugins/videojs-aniview/plugin';
|
||||||
|
|
||||||
const isDev = process.env.NODE_ENV !== 'production';
|
const isDev = process.env.NODE_ENV !== 'production';
|
||||||
|
const macroUrl =
|
||||||
|
`https://vast.aniview.com/api/adserver61/vast/` +
|
||||||
|
`?AV_PUBLISHERID=60afcbc58cfdb065440d2426` +
|
||||||
|
`&AV_CHANNELID=60b354389c7adb506d0bd9a4` +
|
||||||
|
`&AV_URL=[URL_MACRO]` +
|
||||||
|
`&cb=[TIMESTAMP_MACRO]` +
|
||||||
|
`&AV_WIDTH=[WIDTH_MACRO]` +
|
||||||
|
`&AV_HEIGHT=[HEIGHT_MACRO]` +
|
||||||
|
`&AV_SCHAIN=[SCHAIN_MACRO]` +
|
||||||
|
`&AV_CCPA=[CCPA_MACRO]` +
|
||||||
|
`&AV_GDPR=[GDPR_MACRO]` +
|
||||||
|
`&AV_CONSENT=[CONSENT_MACRO]` +
|
||||||
|
`&skip=true` +
|
||||||
|
`&skiptimer=5` +
|
||||||
|
`&usevslot=true` +
|
||||||
|
`&hidecontrols=false`;
|
||||||
|
|
||||||
export type Player = {
|
export type Player = {
|
||||||
on: (string, (any) => void) => void,
|
on: (string, (any) => void) => void,
|
||||||
|
@ -50,16 +70,19 @@ type Props = {
|
||||||
autoplay: boolean,
|
autoplay: boolean,
|
||||||
toggleVideoTheaterMode: () => void,
|
toggleVideoTheaterMode: () => void,
|
||||||
adUrl: ?string,
|
adUrl: ?string,
|
||||||
|
claimId: ?string,
|
||||||
|
userId: ?number,
|
||||||
|
allowPreRoll: ?boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
type VideoJSOptions = {
|
// type VideoJSOptions = {
|
||||||
controls: boolean,
|
// controls: boolean,
|
||||||
preload: string,
|
// preload: string,
|
||||||
playbackRates: Array<number>,
|
// playbackRates: Array<number>,
|
||||||
responsive: boolean,
|
// responsive: boolean,
|
||||||
poster?: string,
|
// poster: ?string,
|
||||||
muted?: boolean,
|
// muted: ?boolean,
|
||||||
};
|
// };
|
||||||
|
|
||||||
const videoPlaybackRates = [0.25, 0.5, 0.75, 1, 1.1, 1.25, 1.5, 1.75, 2];
|
const videoPlaybackRates = [0.25, 0.5, 0.75, 1, 1.1, 1.25, 1.5, 1.75, 2];
|
||||||
|
|
||||||
|
@ -68,13 +91,13 @@ const IS_IOS =
|
||||||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) &&
|
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) &&
|
||||||
!window.MSStream;
|
!window.MSStream;
|
||||||
|
|
||||||
const VIDEO_JS_OPTIONS: VideoJSOptions = {
|
const VIDEO_JS_OPTIONS = {
|
||||||
preload: 'auto',
|
preload: 'auto',
|
||||||
playbackRates: videoPlaybackRates,
|
playbackRates: videoPlaybackRates,
|
||||||
responsive: true,
|
responsive: true,
|
||||||
controls: true,
|
controls: true,
|
||||||
html5: {
|
html5: {
|
||||||
hls: {
|
vhs: {
|
||||||
overrideNative: !videojs.browser.IS_ANY_SAFARI,
|
overrideNative: !videojs.browser.IS_ANY_SAFARI,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -121,6 +144,10 @@ if (!Object.keys(videojs.getPlugins()).includes('qualityLevels')) {
|
||||||
videojs.registerPlugin('qualityLevels', qualityLevels);
|
videojs.registerPlugin('qualityLevels', qualityLevels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Object.keys(videojs.getPlugins()).includes('recsys')) {
|
||||||
|
videojs.registerPlugin('recsys', recsys);
|
||||||
|
}
|
||||||
|
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
// LbryVolumeBarClass
|
// LbryVolumeBarClass
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
|
@ -180,6 +207,9 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
onPlayerReady,
|
onPlayerReady,
|
||||||
toggleVideoTheaterMode,
|
toggleVideoTheaterMode,
|
||||||
adUrl,
|
adUrl,
|
||||||
|
claimId,
|
||||||
|
userId,
|
||||||
|
allowPreRoll,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [reload, setReload] = useState('initial');
|
const [reload, setReload] = useState('initial');
|
||||||
|
@ -282,10 +312,14 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
const controlBar = player.getChild('controlBar');
|
const controlBar = player.getChild('controlBar');
|
||||||
switch (e.type) {
|
switch (e.type) {
|
||||||
case 'play':
|
case 'play':
|
||||||
controlBar.getChild('PlayToggle').controlText(__('Pause (space)'));
|
controlBar
|
||||||
|
.getChild('PlayToggle')
|
||||||
|
.controlText(__('Pause (space)'));
|
||||||
break;
|
break;
|
||||||
case 'pause':
|
case 'pause':
|
||||||
controlBar.getChild('PlayToggle').controlText(__('Play (space)'));
|
controlBar
|
||||||
|
.getChild('PlayToggle')
|
||||||
|
.controlText(__('Play (space)'));
|
||||||
break;
|
break;
|
||||||
case 'volumechange':
|
case 'volumechange':
|
||||||
controlBar
|
controlBar
|
||||||
|
@ -300,8 +334,12 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
break;
|
break;
|
||||||
case 'loadstart':
|
case 'loadstart':
|
||||||
// --- Do everything ---
|
// --- Do everything ---
|
||||||
controlBar.getChild('PlaybackRateMenuButton').controlText(__('Playback Rate (<, >)'));
|
controlBar
|
||||||
controlBar.getChild('QualityButton').controlText(__('Quality'));
|
.getChild('PlaybackRateMenuButton')
|
||||||
|
.controlText(__('Playback Rate (<, >)'));
|
||||||
|
controlBar
|
||||||
|
.getChild('QualityButton')
|
||||||
|
.controlText(__('Quality'));
|
||||||
resolveCtrlText({ type: 'play' });
|
resolveCtrlText({ type: 'play' });
|
||||||
resolveCtrlText({ type: 'pause' });
|
resolveCtrlText({ type: 'pause' });
|
||||||
resolveCtrlText({ type: 'volumechange' });
|
resolveCtrlText({ type: 'volumechange' });
|
||||||
|
@ -311,8 +349,10 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
// clashes if we add a new button in the future.
|
// clashes if we add a new button in the future.
|
||||||
// (2) We'll have to get 'makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)'
|
// (2) We'll have to get 'makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)'
|
||||||
// as a prop here so we can say "Theater mode|Default mode" instead of
|
// as a prop here so we can say "Theater mode|Default mode" instead of
|
||||||
// "Toggle Theather mode".
|
// "Toggle Theater mode".
|
||||||
controlBar.getChild('Button').controlText(__('Toggle Theater mode (t)'));
|
controlBar
|
||||||
|
.getChild('Button')
|
||||||
|
.controlText(__('Toggle Theater mode (t)'));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (isDev) throw Error('Unexpected: ' + e.type);
|
if (isDev) throw Error('Unexpected: ' + e.type);
|
||||||
|
@ -327,6 +367,8 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
// The css starts as "hidden". We make it visible here without
|
// The css starts as "hidden". We make it visible here without
|
||||||
// re-rendering the whole thing.
|
// re-rendering the whole thing.
|
||||||
showTapButton(TAP.UNMUTE);
|
showTapButton(TAP.UNMUTE);
|
||||||
|
} else {
|
||||||
|
showTapButton(TAP.NONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,6 +383,9 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
const player = playerRef.current;
|
const player = playerRef.current;
|
||||||
showTapButton(TAP.RETRY);
|
showTapButton(TAP.RETRY);
|
||||||
|
|
||||||
|
// reattach initial play listener in case we recover from error successfully
|
||||||
|
player.one('play', onInitialPlay);
|
||||||
|
|
||||||
if (player && player.loadingSpinner) {
|
if (player && player.loadingSpinner) {
|
||||||
player.loadingSpinner.hide();
|
player.loadingSpinner.hide();
|
||||||
}
|
}
|
||||||
|
@ -463,15 +508,13 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
|
|
||||||
// Create the video DOM element and wrapper
|
// Create the video DOM element and wrapper
|
||||||
function createVideoPlayerDOM(container) {
|
function createVideoPlayerDOM(container) {
|
||||||
if (!container) {
|
if (!container) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This seems like a poor way to generate the DOM for video.js
|
// This seems like a poor way to generate the DOM for video.js
|
||||||
const wrapper = document.createElement('div');
|
const wrapper = document.createElement('div');
|
||||||
wrapper.setAttribute('data-vjs-player', 'true');
|
wrapper.setAttribute('data-vjs-player', 'true');
|
||||||
const el = document.createElement(isAudio ? 'audio' : 'video');
|
const el = document.createElement(isAudio ? 'audio' : 'video');
|
||||||
el.className = 'video-js';
|
el.className = 'video-js vjs-big-play-centered ';
|
||||||
wrapper.appendChild(el);
|
wrapper.appendChild(el);
|
||||||
|
|
||||||
container.appendChild(wrapper);
|
container.appendChild(wrapper);
|
||||||
|
@ -479,19 +522,55 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function detectFileType() {
|
||||||
|
console.log(`Detecting file type via pre-fetch...`);
|
||||||
|
return new Promise(async (res, rej) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(source, { method: 'HEAD', cache: 'no-store' });
|
||||||
|
|
||||||
|
// Temp variables to hold results
|
||||||
|
let finalType = sourceType;
|
||||||
|
let finalSource = source;
|
||||||
|
|
||||||
|
// override type if we receive an .m3u8 (transcoded mp4)
|
||||||
|
// do we need to check if explicitly redirected
|
||||||
|
// or is checking extension only a safer method
|
||||||
|
if (response &&
|
||||||
|
response.redirected &&
|
||||||
|
response.url &&
|
||||||
|
response.url.endsWith('m3u8')
|
||||||
|
) {
|
||||||
|
finalType = 'application/x-mpegURL';
|
||||||
|
finalSource = response.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`File type is: ${finalType}`);
|
||||||
|
|
||||||
|
// Modify video source in options
|
||||||
|
videoJsOptions.sources = [
|
||||||
|
{
|
||||||
|
src: finalSource,
|
||||||
|
type: finalType,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return res(videoJsOptions);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to pre-fetch video!`);
|
||||||
|
return rej(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize video.js
|
// Initialize video.js
|
||||||
function initializeVideoPlayer(el) {
|
function initializeVideoPlayer(el) {
|
||||||
if (!el) {
|
if (!el) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const vjs = videojs(el, videoJsOptions, () => {
|
const vjs = videojs(el, videoJsOptions, () => {
|
||||||
const player = playerRef.current;
|
const player = playerRef.current;
|
||||||
|
|
||||||
// this seems like a weird thing to have to check for here
|
// this seems like a weird thing to have to check for here
|
||||||
if (!player) {
|
if (!player) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add various event listeners to player
|
// Add various event listeners to player
|
||||||
player.one('play', onInitialPlay);
|
player.one('play', onInitialPlay);
|
||||||
|
@ -507,13 +586,44 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
// Replace volume bar with custom LBRY volume bar
|
// Replace volume bar with custom LBRY volume bar
|
||||||
LbryVolumeBarClass.replaceExisting(player);
|
LbryVolumeBarClass.replaceExisting(player);
|
||||||
|
|
||||||
|
// Add reloadSourceOnError plugin
|
||||||
|
player.reloadSourceOnError({ errorInterval: 10 });
|
||||||
|
|
||||||
// initialize mobile UI
|
// initialize mobile UI
|
||||||
player.mobileUi(); // Inits mobile version. No-op if Desktop.
|
player.mobileUi(); // Inits mobile version. No-op if Desktop.
|
||||||
|
|
||||||
|
// Add quality selector to player
|
||||||
|
player.hlsQualitySelector({
|
||||||
|
displayCurrentQuality: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add recsys plugin
|
||||||
|
// TODO: Add an if(odysee.com) around this function to only use recsys on odysee
|
||||||
|
player.recsys({
|
||||||
|
videoId: claimId,
|
||||||
|
userId: userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// set playsinline for mobile
|
||||||
|
// TODO: make this better
|
||||||
|
player.children_[0].setAttribute('playsinline', '');
|
||||||
|
|
||||||
// I think this is a callback function
|
// I think this is a callback function
|
||||||
onPlayerReady(player);
|
onPlayerReady(player);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// pre-roll ads
|
||||||
|
// This must be initialized earlier than everything else
|
||||||
|
// otherwise a race condition occurs if we place this in the onReady call back
|
||||||
|
if (allowPreRoll) {
|
||||||
|
// player.aniview();
|
||||||
|
vjs.ima({
|
||||||
|
// $FlowFixMe
|
||||||
|
vpaidMode: google.ima.ImaSdkSettings.VpaidMode.INSECURE,
|
||||||
|
adTagUrl: macroUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// fixes #3498 (https://github.com/lbryio/lbry-desktop/issues/3498)
|
// fixes #3498 (https://github.com/lbryio/lbry-desktop/issues/3498)
|
||||||
// summary: on firefox the focus would stick to the fullscreen button which caused buggy behavior with spacebar
|
// summary: on firefox the focus would stick to the fullscreen button which caused buggy behavior with spacebar
|
||||||
vjs.on('fullscreenchange', () => document.activeElement && document.activeElement.blur());
|
vjs.on('fullscreenchange', () => document.activeElement && document.activeElement.blur());
|
||||||
|
@ -524,16 +634,21 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
// This lifecycle hook is only called once (on mount), or when `isAudio` changes.
|
// This lifecycle hook is only called once (on mount), or when `isAudio` changes.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const vjsElement = createVideoPlayerDOM(containerRef.current);
|
const vjsElement = createVideoPlayerDOM(containerRef.current);
|
||||||
const vjsPlayer = initializeVideoPlayer(vjsElement);
|
|
||||||
|
|
||||||
// Add reference to player to global scope
|
// Detect source file type via pre-fetch (async)
|
||||||
window.player = vjsPlayer;
|
detectFileType().then(() => {
|
||||||
|
// Initialize Video.js
|
||||||
|
const vjsPlayer = initializeVideoPlayer(vjsElement);
|
||||||
|
|
||||||
// Set reference in component state
|
// Add reference to player to global scope
|
||||||
playerRef.current = vjsPlayer;
|
window.player = vjsPlayer;
|
||||||
|
|
||||||
// Add event listener for keyboard shortcuts
|
// Set reference in component state
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
playerRef.current = vjsPlayer;
|
||||||
|
|
||||||
|
// Add event listener for keyboard shortcuts
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
});
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -551,43 +666,28 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// For some reason the video player is responsible for detecting content type this way
|
// For some reason the video player is responsible for detecting content type this way
|
||||||
fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => {
|
fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => {
|
||||||
const player = playerRef.current;
|
let finalType = sourceType;
|
||||||
|
|
||||||
if (!player) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let type = sourceType;
|
|
||||||
let finalSource = source;
|
let finalSource = source;
|
||||||
|
|
||||||
// override type if we receive an .m3u8 (transcoded mp4)
|
// override type if we receive an .m3u8 (transcoded mp4)
|
||||||
|
// do we need to check if explicitly redirected
|
||||||
|
// or is checking extension only a safer method
|
||||||
if (response && response.redirected && response.url && response.url.endsWith('m3u8')) {
|
if (response && response.redirected && response.url && response.url.endsWith('m3u8')) {
|
||||||
type = 'application/x-mpegURL';
|
finalType = 'application/x-mpegURL';
|
||||||
finalSource = response.url;
|
finalSource = response.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update player poster
|
// Modify video source in options
|
||||||
// note: the poster prop seems to return null usually.
|
videoJsOptions.sources = [
|
||||||
if (poster) player.poster(poster);
|
{
|
||||||
|
src: finalSource,
|
||||||
|
type: finalType,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// Update player source
|
// Update player source
|
||||||
player.src({
|
const player = playerRef.current;
|
||||||
src: finalSource,
|
if (!player) return;
|
||||||
type: type,
|
|
||||||
});
|
|
||||||
|
|
||||||
// set playsinline for mobile
|
|
||||||
player.children_[0].setAttribute('playsinline', '');
|
|
||||||
|
|
||||||
// Add quality selector to player
|
|
||||||
player.hlsQualitySelector({
|
|
||||||
displayCurrentQuality: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update player source
|
|
||||||
player.src({
|
|
||||||
src: finalSource,
|
|
||||||
type: type,
|
|
||||||
});
|
|
||||||
|
|
||||||
// PR #5570: Temp workaround to avoid double Play button until the next re-architecture.
|
// PR #5570: Temp workaround to avoid double Play button until the next re-architecture.
|
||||||
if (!player.paused()) {
|
if (!player.paused()) {
|
||||||
|
@ -596,6 +696,20 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
});
|
});
|
||||||
}, [source, reload]);
|
}, [source, reload]);
|
||||||
|
|
||||||
|
// Load IMA3 SDK for aniview
|
||||||
|
useEffect(() => {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = `https://imasdk.googleapis.com/js/sdkloader/ima3.js`;
|
||||||
|
script.async = true;
|
||||||
|
// $FlowFixMe
|
||||||
|
document.body.appendChild(script);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// $FlowFixMe
|
||||||
|
document.body.removeChild(script);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
<div className={classnames('video-js-parent', { 'video-js-parent--ios': IS_IOS })} ref={containerRef}>
|
<div className={classnames('video-js-parent', { 'video-js-parent--ios': IS_IOS })} ref={containerRef}>
|
||||||
|
|
|
@ -48,6 +48,7 @@ type Props = {
|
||||||
toggleVideoTheaterMode: () => void,
|
toggleVideoTheaterMode: () => void,
|
||||||
setVideoPlaybackRate: (number) => void,
|
setVideoPlaybackRate: (number) => void,
|
||||||
authenticated: boolean,
|
authenticated: boolean,
|
||||||
|
userId: number,
|
||||||
homepageData: {
|
homepageData: {
|
||||||
PRIMARY_CONTENT_CHANNEL_IDS?: Array<string>,
|
PRIMARY_CONTENT_CHANNEL_IDS?: Array<string>,
|
||||||
ENLIGHTENMENT_CHANNEL_IDS?: Array<string>,
|
ENLIGHTENMENT_CHANNEL_IDS?: Array<string>,
|
||||||
|
@ -89,6 +90,7 @@ function VideoViewer(props: Props) {
|
||||||
setVideoPlaybackRate,
|
setVideoPlaybackRate,
|
||||||
homepageData,
|
homepageData,
|
||||||
authenticated,
|
authenticated,
|
||||||
|
userId,
|
||||||
} = props;
|
} = props;
|
||||||
const {
|
const {
|
||||||
PRIMARY_CONTENT_CHANNEL_IDS = [],
|
PRIMARY_CONTENT_CHANNEL_IDS = [],
|
||||||
|
@ -115,12 +117,12 @@ function VideoViewer(props: Props) {
|
||||||
const {
|
const {
|
||||||
location: { pathname },
|
location: { pathname },
|
||||||
} = useHistory();
|
} = useHistory();
|
||||||
|
const previousUri = usePrevious(uri);
|
||||||
|
const embedded = useContext(EmbedContext);
|
||||||
const [isPlaying, setIsPlaying] = useState(false);
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
const [showAutoplayCountdown, setShowAutoplayCountdown] = useState(false);
|
const [showAutoplayCountdown, setShowAutoplayCountdown] = useState(false);
|
||||||
const [isEndededEmbed, setIsEndededEmbed] = useState(false);
|
const [isEndededEmbed, setIsEndededEmbed] = useState(false);
|
||||||
const vjsCallbackDataRef: any = React.useRef();
|
const vjsCallbackDataRef: any = React.useRef();
|
||||||
const previousUri = usePrevious(uri);
|
|
||||||
const embedded = useContext(EmbedContext);
|
|
||||||
const approvedVideo = Boolean(channelClaimId) && adApprovedChannelIds.includes(channelClaimId);
|
const approvedVideo = Boolean(channelClaimId) && adApprovedChannelIds.includes(channelClaimId);
|
||||||
const adsEnabled = ENABLE_PREROLL_ADS && !authenticated && !embedded && approvedVideo;
|
const adsEnabled = ENABLE_PREROLL_ADS && !authenticated && !embedded && approvedVideo;
|
||||||
const [adUrl, setAdUrl, isFetchingAd] = useGetAds(approvedVideo, adsEnabled);
|
const [adUrl, setAdUrl, isFetchingAd] = useGetAds(approvedVideo, adsEnabled);
|
||||||
|
@ -146,7 +148,10 @@ function VideoViewer(props: Props) {
|
||||||
}, [embedded, videoPlaybackRate]);
|
}, [embedded, videoPlaybackRate]);
|
||||||
|
|
||||||
function doTrackingBuffered(e: Event, data: any) {
|
function doTrackingBuffered(e: Event, data: any) {
|
||||||
|
console.log(`tracking buffered`, e);
|
||||||
|
console.log(`Source (tracking buffered): ${source}`);
|
||||||
fetch(source, { method: 'HEAD' }).then((response) => {
|
fetch(source, { method: 'HEAD' }).then((response) => {
|
||||||
|
console.log(`Source Response (tracking buffered)`, response);
|
||||||
data.playerPoweredBy = response.headers.get('x-powered-by');
|
data.playerPoweredBy = response.headers.get('x-powered-by');
|
||||||
doAnalyticsBuffer(uri, data);
|
doAnalyticsBuffer(uri, data);
|
||||||
});
|
});
|
||||||
|
@ -177,7 +182,9 @@ function VideoViewer(props: Props) {
|
||||||
} else if (autoplaySetting) {
|
} else if (autoplaySetting) {
|
||||||
setShowAutoplayCountdown(true);
|
setShowAutoplayCountdown(true);
|
||||||
}
|
}
|
||||||
}, [embedded, setIsEndededEmbed, autoplaySetting, setShowAutoplayCountdown, adUrl, setAdUrl]);
|
|
||||||
|
clearPosition(uri);
|
||||||
|
}, [embedded, setIsEndededEmbed, autoplaySetting, setShowAutoplayCountdown, adUrl, setAdUrl, clearPosition, uri]);
|
||||||
|
|
||||||
function onPlay() {
|
function onPlay() {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
@ -186,12 +193,17 @@ function VideoViewer(props: Props) {
|
||||||
setIsEndededEmbed(false);
|
setIsEndededEmbed(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onPause(event, player) {
|
||||||
|
setIsPlaying(false);
|
||||||
|
handlePosition(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDispose(event, player) {
|
||||||
|
handlePosition(player);
|
||||||
|
}
|
||||||
|
|
||||||
function handlePosition(player) {
|
function handlePosition(player) {
|
||||||
if (player.ended()) {
|
savePosition(uri, player.currentTime());
|
||||||
clearPosition(uri);
|
|
||||||
} else {
|
|
||||||
savePosition(uri, player.currentTime());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function restorePlaybackRate(player) {
|
function restorePlaybackRate(player) {
|
||||||
|
@ -224,20 +236,12 @@ function VideoViewer(props: Props) {
|
||||||
Promise.race([playPromise, timeoutPromise]).catch((error) => {
|
Promise.race([playPromise, timeoutPromise]).catch((error) => {
|
||||||
if (typeof error === 'object' && error.name && error.name === 'NotAllowedError') {
|
if (typeof error === 'object' && error.name && error.name === 'NotAllowedError') {
|
||||||
if (player.autoplay() && !player.muted()) {
|
if (player.autoplay() && !player.muted()) {
|
||||||
player.muted(true);
|
// player.muted(true);
|
||||||
|
// another version had player.play()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setIsLoading(false);
|
||||||
if (PLAY_TIMEOUT_ERROR) {
|
setIsPlaying(false);
|
||||||
const retryPlayPromise = player.play();
|
|
||||||
Promise.race([retryPlayPromise, timeoutPromise]).catch((error) => {
|
|
||||||
setIsLoading(false);
|
|
||||||
setIsPlaying(false);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setIsLoading(false);
|
|
||||||
setIsPlaying(false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,10 +257,8 @@ function VideoViewer(props: Props) {
|
||||||
player.on('tracking:firstplay', doTrackingFirstPlay);
|
player.on('tracking:firstplay', doTrackingFirstPlay);
|
||||||
player.on('ended', onEnded);
|
player.on('ended', onEnded);
|
||||||
player.on('play', onPlay);
|
player.on('play', onPlay);
|
||||||
player.on('pause', () => {
|
player.on('pause', (event) => onPause(event, player));
|
||||||
setIsPlaying(false);
|
player.on('dispose', (event) => onDispose(event, player));
|
||||||
handlePosition(player);
|
|
||||||
});
|
|
||||||
player.on('error', () => {
|
player.on('error', () => {
|
||||||
const error = player.error();
|
const error = player.error();
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -283,9 +285,6 @@ function VideoViewer(props: Props) {
|
||||||
if (position) {
|
if (position) {
|
||||||
player.currentTime(position);
|
player.currentTime(position);
|
||||||
}
|
}
|
||||||
player.on('dispose', () => {
|
|
||||||
handlePosition(player);
|
|
||||||
});
|
|
||||||
}, playerReadyDependencyList);
|
}, playerReadyDependencyList);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -344,6 +343,9 @@ function VideoViewer(props: Props) {
|
||||||
startMuted={autoplayIfEmbedded}
|
startMuted={autoplayIfEmbedded}
|
||||||
toggleVideoTheaterMode={toggleVideoTheaterMode}
|
toggleVideoTheaterMode={toggleVideoTheaterMode}
|
||||||
autoplay={!embedded || autoplayIfEmbedded}
|
autoplay={!embedded || autoplayIfEmbedded}
|
||||||
|
claimId={claimId}
|
||||||
|
userId={userId}
|
||||||
|
allowPreRoll={!embedded && !authenticated}
|
||||||
/>
|
/>
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -21,7 +21,9 @@ export default function WebUploadList(props: Props) {
|
||||||
!!uploadCount && (
|
!!uploadCount && (
|
||||||
<Card
|
<Card
|
||||||
title={__('Currently uploading')}
|
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={
|
body={
|
||||||
<section>
|
<section>
|
||||||
{/* $FlowFixMe */}
|
{/* $FlowFixMe */}
|
||||||
|
|
|
@ -118,9 +118,7 @@ export default function YoutubeTransferStatus(props: Props) {
|
||||||
{isNotElligible && (
|
{isNotElligible && (
|
||||||
<I18nMessage
|
<I18nMessage
|
||||||
tokens={{
|
tokens={{
|
||||||
here: (
|
here: <Button button="link" href="https://lbry.com/faq/youtube" label={__('here')} />,
|
||||||
<Button button="link" href="https://lbry.com/faq/youtube" label={__('here')} />
|
|
||||||
),
|
|
||||||
email: SITE_HELP_EMAIL,
|
email: SITE_HELP_EMAIL,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export const FF_MAX_CHARS_DEFAULT = 2000;
|
export const FF_MAX_CHARS_DEFAULT = 2000;
|
||||||
export const FF_MAX_CHARS_IN_COMMENT = 2000;
|
export const FF_MAX_CHARS_IN_COMMENT = 2000;
|
||||||
export const FF_MAX_CHARS_IN_LIVESTREAM_COMMENT = 500;
|
export const FF_MAX_CHARS_IN_LIVESTREAM_COMMENT = 300;
|
||||||
export const FF_MAX_CHARS_IN_DESCRIPTION = 5000;
|
export const FF_MAX_CHARS_IN_DESCRIPTION = 5000;
|
||||||
export const FF_MAX_CHARS_REPORT_CONTENT_DETAILS = 500;
|
export const FF_MAX_CHARS_REPORT_CONTENT_DETAILS = 500;
|
||||||
export const FF_MAX_CHARS_REPORT_CONTENT_ADDRESS = 255;
|
export const FF_MAX_CHARS_REPORT_CONTENT_ADDRESS = 255;
|
||||||
|
|
|
@ -44,8 +44,9 @@ class ModalPublishSuccess extends React.PureComponent<Props> {
|
||||||
'Your livestream is now pending. You will be able to start shortly at the streaming dashboard.'
|
'Your livestream is now pending. You will be able to start shortly at the streaming dashboard.'
|
||||||
);
|
);
|
||||||
} else {
|
} 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() {
|
function handleClose() {
|
||||||
closeModal();
|
closeModal();
|
||||||
|
@ -54,7 +55,7 @@ class ModalPublishSuccess extends React.PureComponent<Props> {
|
||||||
return (
|
return (
|
||||||
<Modal isOpen type="card" contentLabel={__(contentLabel)} onAborted={handleClose}>
|
<Modal isOpen type="card" contentLabel={__(contentLabel)} onAborted={handleClose}>
|
||||||
<Card
|
<Card
|
||||||
title={__('Success')}
|
title={livestream ? __('Livestream Created') : __('Upload Complete')}
|
||||||
subtitle={publishMessage}
|
subtitle={publishMessage}
|
||||||
body={
|
body={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
selectMyChannelClaims,
|
selectMyChannelClaims,
|
||||||
makeSelectClaimIsStreamPlaceholder,
|
makeSelectClaimIsStreamPlaceholder,
|
||||||
SETTINGS,
|
SETTINGS,
|
||||||
|
doClearPublish,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectFfmpegStatus, makeSelectClientSetting } from 'redux/selectors/settings';
|
import { selectFfmpegStatus, makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { doPublishDesktop } from 'redux/actions/publish';
|
import { doPublishDesktop } from 'redux/actions/publish';
|
||||||
|
@ -32,6 +33,7 @@ const select = (state, props) => {
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
publish: (filePath, preview) => dispatch(doPublishDesktop(filePath, preview)),
|
publish: (filePath, preview) => dispatch(doPublishDesktop(filePath, preview)),
|
||||||
|
clearPublish: () => dispatch(doClearPublish()),
|
||||||
closeModal: () => dispatch(doHideModal()),
|
closeModal: () => dispatch(doHideModal()),
|
||||||
setEnablePublishPreview: (value) => dispatch(doSetClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW, value)),
|
setEnablePublishPreview: (value) => dispatch(doSetClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW, value)),
|
||||||
});
|
});
|
||||||
|
|
|
@ -52,10 +52,8 @@ function ModalRemoveFile(props: Props) {
|
||||||
<FormField
|
<FormField
|
||||||
name="claim_abandon"
|
name="claim_abandon"
|
||||||
label={
|
label={
|
||||||
<I18nMessage
|
<I18nMessage tokens={{ lbc: <LbcSymbol postfix={claim.amount} /> }}>
|
||||||
tokens={{ lbc: <LbcSymbol prefix={__('reclaim %amount%', { amount: claim.amount })} /> }}
|
Abandon on blockchain (reclaim %lbc%)
|
||||||
>
|
|
||||||
Abandon on blockchain (%lbc%)
|
|
||||||
</I18nMessage>
|
</I18nMessage>
|
||||||
}
|
}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
|
@ -12,7 +12,7 @@ class ModalTransactionFailed extends React.PureComponent<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen contentLabel={__('Transaction failed')} title={__('Transaction failed')} onConfirmed={closeModal}>
|
<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>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import { SIMPLE_SITE } from 'config';
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Modal } from 'modal/modal';
|
import { Modal } from 'modal/modal';
|
||||||
|
@ -15,18 +16,27 @@ const YoutubeWelcome = (props: Props) => {
|
||||||
<Modal isOpen type="card" onAborted={doHideModal}>
|
<Modal isOpen type="card" onAborted={doHideModal}>
|
||||||
<Confetti recycle={false} style={{ position: 'fixed' }} numberOfPieces={100} />
|
<Confetti recycle={false} style={{ position: 'fixed' }} numberOfPieces={100} />
|
||||||
<Card
|
<Card
|
||||||
title={__("You're free!")}
|
title={!SIMPLE_SITE ? __("You're free!") : __('Welcome to Odysee')}
|
||||||
subtitle={
|
subtitle={
|
||||||
<React.Fragment>
|
!SIMPLE_SITE ? (
|
||||||
<p>
|
<React.Fragment>
|
||||||
{__("You've escaped the land of spying, censorship, and exploitation.")}
|
<p>
|
||||||
<span className="emoji"> 💩</span>
|
{__("You've escaped the land of spying, censorship, and exploitation.")}
|
||||||
</p>
|
<span className="emoji"> 💩</span>
|
||||||
<p>
|
</p>
|
||||||
{__('Welcome to the land of content freedom.')}
|
<p>
|
||||||
<span className="emoji"> 🌈</span>
|
{__('Welcome to the land of content freedom.')}
|
||||||
</p>
|
<span className="emoji"> 🌈</span>
|
||||||
</React.Fragment>
|
</p>
|
||||||
|
</React.Fragment>
|
||||||
|
) : (
|
||||||
|
<React.Fragment>
|
||||||
|
<p>
|
||||||
|
{__('You make the party extra special!')}
|
||||||
|
<span className="emoji"> 💖</span>
|
||||||
|
</p>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
<div className="card__actions">
|
<div className="card__actions">
|
||||||
|
|
12
ui/page/adsTest/index.js
Normal file
12
ui/page/adsTest/index.js
Normal 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
20
ui/page/adsTest/view.jsx
Normal 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>;
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ const MOONPAY_KEY = process.env.MOONPAY_SECRET_KEY;
|
||||||
const COUNTRIES = Array.from(
|
const COUNTRIES = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
countryData.all
|
countryData.all
|
||||||
|
.filter((country) => country.status !== 'deleted')
|
||||||
.map((country) => country.name)
|
.map((country) => country.name)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
if (a > b) {
|
if (a > b) {
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as ICONS from 'constants/icons';
|
// import * as ICONS from 'constants/icons';
|
||||||
import * as PAGES from 'constants/pages';
|
// import * as PAGES from 'constants/pages';
|
||||||
|
import * as CS from 'constants/claim_search';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import Button from 'component/button';
|
// import Button from 'component/button';
|
||||||
import ClaimTilesDiscover from 'component/claimTilesDiscover';
|
|
||||||
import ClaimListDiscover from 'component/claimListDiscover';
|
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';
|
import { SIMPLE_SITE } from 'config';
|
||||||
|
|
||||||
const MORE_CHANNELS_ANCHOR = 'MoreChannels';
|
const MORE_CHANNELS_ANCHOR = 'MoreChannels';
|
||||||
|
@ -19,89 +18,87 @@ type Props = {
|
||||||
homepageData: any,
|
homepageData: any,
|
||||||
};
|
};
|
||||||
|
|
||||||
type ChannelsFollowingItem = {
|
|
||||||
title: string,
|
|
||||||
link?: string,
|
|
||||||
help?: any,
|
|
||||||
options?: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
function ChannelsFollowingDiscover(props: Props) {
|
function ChannelsFollowingDiscover(props: Props) {
|
||||||
const { followedTags, subscribedChannels, blockedChannels, homepageData } = props;
|
const {
|
||||||
|
// followedTags,
|
||||||
|
// subscribedChannels,
|
||||||
|
// blockedChannels,
|
||||||
|
homepageData,
|
||||||
|
} = props;
|
||||||
const { PRIMARY_CONTENT_CHANNEL_IDS } = homepageData;
|
const { PRIMARY_CONTENT_CHANNEL_IDS } = homepageData;
|
||||||
let rowData: Array<ChannelsFollowingItem> = [];
|
// let rowData: Array<ChannelsFollowingItem> = [];
|
||||||
const notChannels = subscribedChannels
|
// const notChannels = subscribedChannels
|
||||||
.map(({ uri }) => uri)
|
// .map(({ uri }) => uri)
|
||||||
.concat(blockedChannels)
|
// .concat(blockedChannels)
|
||||||
.map(uri => uri.split('#')[1]);
|
// .map(uri => uri.split('#')[1]);
|
||||||
|
|
||||||
rowData.push({
|
// rowData.push({
|
||||||
title: 'Top Channels Of All Time',
|
// 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}`,
|
// link: `/$/${PAGES.DISCOVER}?claim_type=channel&${CS.ORDER_BY_KEY}=${CS.ORDER_BY_TOP}&${CS.FRESH_KEY}=${CS.FRESH_ALL}`,
|
||||||
options: {
|
// options: {
|
||||||
pageSize: 12,
|
// pageSize: 12,
|
||||||
claimType: 'channel',
|
// claimType: 'channel',
|
||||||
orderBy: ['effective_amount'],
|
// orderBy: ['effective_amount'],
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
|
|
||||||
rowData.push({
|
// rowData.push({
|
||||||
title: 'Latest From @lbrycast',
|
// title: 'Latest From @lbrycast',
|
||||||
link: `/@lbrycast:4`,
|
// link: `/@lbrycast:4`,
|
||||||
options: {
|
// options: {
|
||||||
orderBy: ['release_time'],
|
// orderBy: ['release_time'],
|
||||||
pageSize: 8,
|
// pageSize: 8,
|
||||||
channelIds: ['4c29f8b013adea4d5cca1861fb2161d5089613ea'],
|
// channelIds: ['4c29f8b013adea4d5cca1861fb2161d5089613ea'],
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
|
|
||||||
rowData.push({
|
// rowData.push({
|
||||||
title: 'Trending Channels',
|
// title: 'Trending Channels',
|
||||||
link: `/$/${PAGES.DISCOVER}?claim_type=channel`,
|
// link: `/$/${PAGES.DISCOVER}?claim_type=channel`,
|
||||||
options: {
|
// options: {
|
||||||
pageSize: 8,
|
// pageSize: 8,
|
||||||
claimType: 'channel',
|
// claimType: 'channel',
|
||||||
orderBy: ['trending_group', 'trending_mixed'],
|
// orderBy: ['trending_group', 'trending_mixed'],
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (followedTags.length > 0 && followedTags.length < 5) {
|
// if (followedTags.length > 0 && followedTags.length < 5) {
|
||||||
const followedRows = followedTags.map((tag: Tag) => ({
|
// const followedRows = followedTags.map((tag: Tag) => ({
|
||||||
title: `Trending Channels for #${toCapitalCase(tag.name)}`,
|
// title: `Trending Channels for #${toCapitalCase(tag.name)}`,
|
||||||
link: `/$/${PAGES.DISCOVER}?t=${tag.name}&claim_type=channel`,
|
// link: `/$/${PAGES.DISCOVER}?t=${tag.name}&claim_type=channel`,
|
||||||
options: {
|
// options: {
|
||||||
claimType: 'channel',
|
// claimType: 'channel',
|
||||||
pageSize: 4,
|
// pageSize: 4,
|
||||||
tags: [tag.name],
|
// tags: [tag.name],
|
||||||
},
|
// },
|
||||||
}));
|
// }));
|
||||||
rowData.push(...followedRows);
|
// rowData.push(...followedRows);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (followedTags.length > 4) {
|
// if (followedTags.length > 4) {
|
||||||
rowData.push({
|
// rowData.push({
|
||||||
title: 'Trending For Your Tags',
|
// title: 'Trending For Your Tags',
|
||||||
link: `/$/${PAGES.TAGS_FOLLOWING}?claim_type=channel`,
|
// link: `/$/${PAGES.TAGS_FOLLOWING}?claim_type=channel`,
|
||||||
options: {
|
// options: {
|
||||||
claimType: 'channel',
|
// claimType: 'channel',
|
||||||
tags: followedTags.map(tag => tag.name),
|
// tags: followedTags.map(tag => tag.name),
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
const rowDataWithGenericOptions = rowData.map(row => {
|
// const rowDataWithGenericOptions = rowData.map(row => {
|
||||||
return {
|
// return {
|
||||||
...row,
|
// ...row,
|
||||||
options: {
|
// options: {
|
||||||
...row.options,
|
// ...row.options,
|
||||||
notChannels,
|
// notChannels,
|
||||||
},
|
// },
|
||||||
};
|
// };
|
||||||
});
|
// });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
{rowDataWithGenericOptions.map(({ title, link, help, options = {} }) => (
|
{/* {rowDataWithGenericOptions.map(({ title, link, help, options = {} }) => (
|
||||||
<div key={title} className="claim-grid__wrapper">
|
<div key={title} className="claim-grid__wrapper">
|
||||||
<h1 className="section__actions">
|
<h1 className="section__actions">
|
||||||
{link ? (
|
{link ? (
|
||||||
|
@ -120,17 +117,17 @@ function ChannelsFollowingDiscover(props: Props) {
|
||||||
|
|
||||||
<ClaimTilesDiscover {...options} />
|
<ClaimTilesDiscover {...options} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))} */}
|
||||||
<h1 id={MORE_CHANNELS_ANCHOR} className="claim-grid__title">
|
|
||||||
{__('More Channels')}
|
|
||||||
</h1>
|
|
||||||
{/* odysee: claimIds = PRIMARY_CONTENT_CHANNEL_IDS if simplesite CLD */}
|
|
||||||
<ClaimListDiscover
|
<ClaimListDiscover
|
||||||
defaultOrderBy={CS.ORDER_BY_TRENDING}
|
defaultOrderBy={CS.ORDER_BY_TRENDING}
|
||||||
defaultFreshness={CS.FRESH_ALL}
|
defaultFreshness={CS.FRESH_ALL}
|
||||||
claimType={CS.CLAIM_CHANNEL}
|
claimType={CS.CLAIM_CHANNEL}
|
||||||
claimIds={SIMPLE_SITE ? PRIMARY_CONTENT_CHANNEL_IDS : undefined}
|
claimIds={SIMPLE_SITE ? PRIMARY_CONTENT_CHANNEL_IDS : undefined}
|
||||||
scrollAnchor={MORE_CHANNELS_ANCHOR}
|
scrollAnchor={MORE_CHANNELS_ANCHOR}
|
||||||
|
maxPages={3}
|
||||||
|
hideFilters
|
||||||
|
header={<h1 className="section__title">{__('Moon cheese is an acquired taste')}</h1>}
|
||||||
/>
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { SHOW_ADS, DOMAIN, SIMPLE_SITE } from 'config';
|
import { SHOW_ADS, DOMAIN, SIMPLE_SITE } from 'config';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
|
import * as CS from 'constants/claim_search';
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import ClaimListDiscover from 'component/claimListDiscover';
|
import ClaimListDiscover from 'component/claimListDiscover';
|
||||||
|
@ -11,11 +12,11 @@ import { useIsMobile } from 'effects/use-screensize';
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
import HiddenNsfw from 'component/common/hidden-nsfw';
|
import HiddenNsfw from 'component/common/hidden-nsfw';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import * as CS from 'constants/claim_search';
|
|
||||||
import Ads from 'web/component/ads';
|
import Ads from 'web/component/ads';
|
||||||
import LbcSymbol from 'component/common/lbc-symbol';
|
import LbcSymbol from 'component/common/lbc-symbol';
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
import useGetLivestreams from 'effects/use-get-livestreams';
|
import useGetLivestreams from 'effects/use-get-livestreams';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
location: { search: string },
|
location: { search: string },
|
||||||
|
@ -97,8 +98,8 @@ function DiscoverPage(props: Props) {
|
||||||
} else {
|
} else {
|
||||||
headerLabel = (
|
headerLabel = (
|
||||||
<span>
|
<span>
|
||||||
<Icon icon={(dynamicRouteProps && dynamicRouteProps.icon) || ICONS.DISCOVER} size={10} />
|
<Icon icon={(dynamicRouteProps && dynamicRouteProps.icon) || ICONS.WILD_WEST} size={10} />
|
||||||
{(dynamicRouteProps && dynamicRouteProps.title) || __('All Content')}
|
{(dynamicRouteProps && dynamicRouteProps.title) || __('Wild West')}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -106,9 +107,11 @@ function DiscoverPage(props: Props) {
|
||||||
return (
|
return (
|
||||||
<Page noFooter fullWidthPage={tileLayout}>
|
<Page noFooter fullWidthPage={tileLayout}>
|
||||||
<ClaimListDiscover
|
<ClaimListDiscover
|
||||||
limitClaimsPerChannel={3}
|
hideAdvancedFilter
|
||||||
|
hideFilters={!dynamicRouteProps}
|
||||||
header={repostedUri ? <span /> : undefined}
|
header={repostedUri ? <span /> : undefined}
|
||||||
tileLayout={repostedUri ? false : tileLayout}
|
tileLayout={repostedUri ? false : tileLayout}
|
||||||
|
defaultOrderBy={dynamicRouteProps ? undefined : CS.ORDER_BY_TRENDING}
|
||||||
claimType={claimType ? [claimType] : undefined}
|
claimType={claimType ? [claimType] : undefined}
|
||||||
headerLabel={headerLabel}
|
headerLabel={headerLabel}
|
||||||
tags={tags}
|
tags={tags}
|
||||||
|
@ -117,9 +120,18 @@ function DiscoverPage(props: Props) {
|
||||||
injectedItem={
|
injectedItem={
|
||||||
SHOW_ADS && IS_WEB ? (SIMPLE_SITE ? false : !isAuthenticated && <Ads small type={'video'} />) : false
|
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={
|
channelIds={
|
||||||
(dynamicRouteProps && dynamicRouteProps.options && dynamicRouteProps.options.channelIds) || undefined
|
(dynamicRouteProps && dynamicRouteProps.options && dynamicRouteProps.options.channelIds) || undefined
|
||||||
}
|
}
|
||||||
|
limitClaimsPerChannel={
|
||||||
|
(dynamicRouteProps && dynamicRouteProps.options && dynamicRouteProps.options.limitClaimsPerChannel) ||
|
||||||
|
undefined
|
||||||
|
}
|
||||||
meta={
|
meta={
|
||||||
!dynamicRouteProps ? (
|
!dynamicRouteProps ? (
|
||||||
<a
|
<a
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
|
@ -10,6 +11,9 @@ import FileRenderDownload from 'component/fileRenderDownload';
|
||||||
import RecommendedContent from 'component/recommendedContent';
|
import RecommendedContent from 'component/recommendedContent';
|
||||||
import CollectionContent from 'component/collectionContentSidebar';
|
import CollectionContent from 'component/collectionContentSidebar';
|
||||||
import CommentsList from 'component/commentsList';
|
import CommentsList from 'component/commentsList';
|
||||||
|
import { Redirect } from 'react-router';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import I18nMessage from 'component/i18nMessage';
|
||||||
import Empty from 'component/common/empty';
|
import Empty from 'component/common/empty';
|
||||||
|
|
||||||
const PostViewer = React.lazy(() => import('component/postViewer' /* webpackChunkName: "postViewer" */));
|
const PostViewer = React.lazy(() => import('component/postViewer' /* webpackChunkName: "postViewer" */));
|
||||||
|
@ -31,7 +35,9 @@ type Props = {
|
||||||
collection?: Collection,
|
collection?: Collection,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
videoTheaterMode: boolean,
|
videoTheaterMode: boolean,
|
||||||
|
claimIsMine: boolean,
|
||||||
commentsDisabled: boolean,
|
commentsDisabled: boolean,
|
||||||
|
isLivestream: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function FilePage(props: Props) {
|
function FilePage(props: Props) {
|
||||||
|
@ -48,9 +54,12 @@ function FilePage(props: Props) {
|
||||||
linkedComment,
|
linkedComment,
|
||||||
setPrimaryUri,
|
setPrimaryUri,
|
||||||
videoTheaterMode,
|
videoTheaterMode,
|
||||||
|
|
||||||
|
claimIsMine,
|
||||||
commentsDisabled,
|
commentsDisabled,
|
||||||
collection,
|
collection,
|
||||||
collectionId,
|
collectionId,
|
||||||
|
isLivestream,
|
||||||
} = props;
|
} = props;
|
||||||
const cost = costInfo ? costInfo.cost : null;
|
const cost = costInfo ? costInfo.cost : null;
|
||||||
const hasFileInfo = fileInfo !== undefined;
|
const hasFileInfo = fileInfo !== undefined;
|
||||||
|
@ -123,6 +132,10 @@ function FilePage(props: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!claimIsMine && isLivestream) {
|
||||||
|
return <Redirect to={`/$/${PAGES.LIVESTREAM}`} />;
|
||||||
|
}
|
||||||
|
|
||||||
if (obscureNsfw && isMature) {
|
if (obscureNsfw && isMature) {
|
||||||
return (
|
return (
|
||||||
<Page className="file-page" filePage isMarkdown={isMarkdown}>
|
<Page className="file-page" filePage isMarkdown={isMarkdown}>
|
||||||
|
@ -143,6 +156,18 @@ function FilePage(props: Props) {
|
||||||
{!isMarkdown && (
|
{!isMarkdown && (
|
||||||
<div className="file-page__secondary-content">
|
<div className="file-page__secondary-content">
|
||||||
<div>
|
<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} />}
|
{RENDER_MODES.FLOATING_MODES.includes(renderMode) && <FileTitleSection uri={uri} />}
|
||||||
{commentsDisabled && <Empty text={__('The creator of this content has disabled comments.')} />}
|
{commentsDisabled && <Empty text={__('The creator of this content has disabled comments.')} />}
|
||||||
{!commentsDisabled && <CommentsList uri={uri} linkedComment={linkedComment} />}
|
{!commentsDisabled && <CommentsList uri={uri} linkedComment={linkedComment} />}
|
||||||
|
|
|
@ -47,7 +47,15 @@ function HomePage(props: Props) {
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
const claimTiles = <ClaimTilesDiscover {...options} liveLivestreamsFirst livestreamMap={livestreamMap} hasSource />;
|
const claimTiles = (
|
||||||
|
<ClaimTilesDiscover
|
||||||
|
{...options}
|
||||||
|
liveLivestreamsFirst
|
||||||
|
livestreamMap={livestreamMap}
|
||||||
|
hasSource
|
||||||
|
pin={route === `/$/${PAGES.GENERAL}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={title} className="claim-grid__wrapper">
|
<div key={title} className="claim-grid__wrapper">
|
||||||
|
@ -125,6 +133,13 @@ function HomePage(props: Props) {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<h1 className="home__meme">
|
||||||
|
<Button button="link" href="https://odysee.com/@Odysee:8?view=discussion">
|
||||||
|
{'this is my favorite store on the citadel'}
|
||||||
|
</Button>
|
||||||
|
</h1>
|
||||||
|
|
||||||
{rowData.map(({ title, route, link, icon, help, options = {} }, index) => {
|
{rowData.map(({ title, route, link, icon, help, options = {} }, index) => {
|
||||||
return getRowElements(title, route, link, icon, help, options, index);
|
return getRowElements(title, route, link, icon, help, options, index);
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -14,12 +14,12 @@ import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
import SearchPage from './view';
|
import SearchPage from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
const showMature = selectShowMatureContent(state);
|
|
||||||
const urlParams = new URLSearchParams(props.location.search);
|
const urlParams = new URLSearchParams(props.location.search);
|
||||||
let urlQuery = urlParams.get('q') || null;
|
let urlQuery = urlParams.get('q') || null;
|
||||||
if (urlQuery) {
|
if (urlQuery) {
|
||||||
urlQuery = urlQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
|
urlQuery = urlQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
|
||||||
}
|
}
|
||||||
|
const showMature = selectShowMatureContent(state);
|
||||||
const query = makeSelectQueryWithOptions(
|
const query = makeSelectQueryWithOptions(
|
||||||
urlQuery,
|
urlQuery,
|
||||||
SIMPLE_SITE
|
SIMPLE_SITE
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as PAGES from 'constants/pages';
|
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 ICONS from 'constants/icons';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { SETTINGS } from 'lbry-redux';
|
import { SETTINGS } from 'lbry-redux';
|
||||||
|
@ -17,7 +17,7 @@ import classnames from 'classnames';
|
||||||
import { getPasswordFromCookie } from 'util/saved-passwords';
|
import { getPasswordFromCookie } from 'util/saved-passwords';
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
import homepages from 'homepages';
|
import homepages from 'homepages';
|
||||||
import { Lbryio } from 'lbryinc';
|
// import { Lbryio } from 'lbryinc';
|
||||||
import Yrbl from 'component/yrbl';
|
import Yrbl from 'component/yrbl';
|
||||||
|
|
||||||
type Price = {
|
type Price = {
|
||||||
|
@ -170,7 +170,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
const {
|
const {
|
||||||
daemonSettings,
|
daemonSettings,
|
||||||
allowAnalytics,
|
allowAnalytics,
|
||||||
showNsfw,
|
// showNsfw,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
currentTheme,
|
currentTheme,
|
||||||
themes,
|
themes,
|
||||||
|
@ -183,12 +183,12 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
setClientSetting,
|
setClientSetting,
|
||||||
toggle3PAnalytics,
|
toggle3PAnalytics,
|
||||||
floatingPlayer,
|
floatingPlayer,
|
||||||
hideReposts,
|
// hideReposts,
|
||||||
clearPlayingUri,
|
clearPlayingUri,
|
||||||
darkModeTimes,
|
darkModeTimes,
|
||||||
clearCache,
|
clearCache,
|
||||||
openModal,
|
|
||||||
myChannelUrls,
|
myChannelUrls,
|
||||||
|
// openModal,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { storedPassword } = this.state;
|
const { storedPassword } = this.state;
|
||||||
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
||||||
|
@ -358,7 +358,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
{/* <FormField
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="hide_reposts"
|
name="hide_reposts"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
@ -372,7 +372,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
checked={hideReposts}
|
checked={hideReposts}
|
||||||
label={__('Hide reposts')}
|
label={__('Hide reposts')}
|
||||||
helper={__('You will not see reposts by people you follow or receive email notifying about them.')}
|
helper={__('You will not see reposts by people you follow or receive email notifying about them.')}
|
||||||
/>
|
/> */}
|
||||||
|
|
||||||
{/* <FormField
|
{/* <FormField
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
@ -383,7 +383,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
helper={__('Anonymous content is published without a channel.')}
|
helper={__('Anonymous content is published without a channel.')}
|
||||||
/> */}
|
/> */}
|
||||||
|
|
||||||
<FormField
|
{/* <FormField
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="show_nsfw"
|
name="show_nsfw"
|
||||||
onChange={() =>
|
onChange={() =>
|
||||||
|
@ -396,7 +396,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
helper={__(
|
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. '
|
'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>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -33,6 +33,7 @@ type Props = {
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
claimIsPending: boolean,
|
claimIsPending: boolean,
|
||||||
isLivestream: boolean,
|
isLivestream: boolean,
|
||||||
|
claimIsMine: Boolean,
|
||||||
beginPublish: (string) => void,
|
beginPublish: (string) => void,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
|
import { SEARCH_OPTIONS } from 'constants/search';
|
||||||
import { buildURI, doResolveUris, batchActions, isURIValid, makeSelectClaimForUri } from 'lbry-redux';
|
import { buildURI, doResolveUris, batchActions, isURIValid, makeSelectClaimForUri } from 'lbry-redux';
|
||||||
import {
|
import {
|
||||||
makeSelectSearchUris,
|
makeSelectSearchUris,
|
||||||
|
@ -135,6 +136,10 @@ export const doFetchRecommendedContent = (uri: string, mature: boolean) => (disp
|
||||||
if (!mature) {
|
if (!mature) {
|
||||||
options['nsfw'] = false;
|
options['nsfw'] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options[SEARCH_OPTIONS.CLAIM_TYPE] = SEARCH_OPTIONS.INCLUDE_FILES;
|
||||||
|
options[SEARCH_OPTIONS.MEDIA_VIDEO] = true;
|
||||||
|
|
||||||
const { title } = claim.value;
|
const { title } = claim.value;
|
||||||
if (title && options) {
|
if (title && options) {
|
||||||
dispatch(doSearch(title, options));
|
dispatch(doSearch(title, options));
|
||||||
|
|
|
@ -11,9 +11,9 @@ const defaultState: SearchState = {
|
||||||
[SEARCH_OPTIONS.CLAIM_TYPE]: SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS,
|
[SEARCH_OPTIONS.CLAIM_TYPE]: SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS,
|
||||||
[SEARCH_OPTIONS.MEDIA_AUDIO]: true,
|
[SEARCH_OPTIONS.MEDIA_AUDIO]: true,
|
||||||
[SEARCH_OPTIONS.MEDIA_VIDEO]: true,
|
[SEARCH_OPTIONS.MEDIA_VIDEO]: true,
|
||||||
[SEARCH_OPTIONS.MEDIA_TEXT]: true,
|
[SEARCH_OPTIONS.MEDIA_TEXT]: false,
|
||||||
[SEARCH_OPTIONS.MEDIA_IMAGE]: true,
|
[SEARCH_OPTIONS.MEDIA_IMAGE]: false,
|
||||||
[SEARCH_OPTIONS.MEDIA_APPLICATION]: true,
|
[SEARCH_OPTIONS.MEDIA_APPLICATION]: false,
|
||||||
},
|
},
|
||||||
urisByQuery: {},
|
urisByQuery: {},
|
||||||
hasReachedMaxResultsLength: {},
|
hasReachedMaxResultsLength: {},
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { UNSYNCED_SETTINGS } from 'config';
|
||||||
const { CLIENT_SYNC_KEYS } = SHARED_PREFERENCES;
|
const { CLIENT_SYNC_KEYS } = SHARED_PREFERENCES;
|
||||||
const settingsToIgnore = (UNSYNCED_SETTINGS && UNSYNCED_SETTINGS.trim().split(' ')) || [];
|
const settingsToIgnore = (UNSYNCED_SETTINGS && UNSYNCED_SETTINGS.trim().split(' ')) || [];
|
||||||
const clientSyncKeys = settingsToIgnore.length
|
const clientSyncKeys = settingsToIgnore.length
|
||||||
? CLIENT_SYNC_KEYS.filter(k => !settingsToIgnore.includes(k))
|
? CLIENT_SYNC_KEYS.filter((k) => !settingsToIgnore.includes(k))
|
||||||
: CLIENT_SYNC_KEYS;
|
: CLIENT_SYNC_KEYS;
|
||||||
|
|
||||||
const reducers = {};
|
const reducers = {};
|
||||||
|
@ -70,7 +70,7 @@ const defaultState = {
|
||||||
[SETTINGS.AUTOPLAY_NEXT]: true,
|
[SETTINGS.AUTOPLAY_NEXT]: true,
|
||||||
[SETTINGS.FLOATING_PLAYER]: true,
|
[SETTINGS.FLOATING_PLAYER]: true,
|
||||||
[SETTINGS.AUTO_DOWNLOAD]: true,
|
[SETTINGS.AUTO_DOWNLOAD]: true,
|
||||||
[SETTINGS.HIDE_REPOSTS]: false,
|
[SETTINGS.HIDE_REPOSTS]: true,
|
||||||
|
|
||||||
// OS
|
// OS
|
||||||
[SETTINGS.AUTO_LAUNCH]: true,
|
[SETTINGS.AUTO_LAUNCH]: true,
|
||||||
|
@ -89,12 +89,12 @@ reducers[ACTIONS.REHYDRATE] = (state, action) => {
|
||||||
return Object.assign({}, state, { clientSettings });
|
return Object.assign({}, state, { clientSettings });
|
||||||
};
|
};
|
||||||
|
|
||||||
reducers[ACTIONS.FINDING_FFMPEG_STARTED] = state =>
|
reducers[ACTIONS.FINDING_FFMPEG_STARTED] = (state) =>
|
||||||
Object.assign({}, state, {
|
Object.assign({}, state, {
|
||||||
findingFFmpeg: true,
|
findingFFmpeg: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
reducers[ACTIONS.FINDING_FFMPEG_COMPLETED] = state =>
|
reducers[ACTIONS.FINDING_FFMPEG_COMPLETED] = (state) =>
|
||||||
Object.assign({}, state, {
|
Object.assign({}, state, {
|
||||||
findingFFmpeg: false,
|
findingFFmpeg: false,
|
||||||
});
|
});
|
||||||
|
@ -120,7 +120,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 { from, to } = state.clientSettings[SETTINGS.DARK_MODE_TIMES];
|
||||||
const momentNow = moment();
|
const momentNow = moment();
|
||||||
const startNightMoment = moment(from.formattedTime, 'HH:mm');
|
const startNightMoment = moment(from.formattedTime, 'HH:mm');
|
||||||
|
@ -155,7 +155,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 { clientSettings } = state;
|
||||||
const sharedPreferences = Object.assign({}, state.sharedPreferences);
|
const sharedPreferences = Object.assign({}, state.sharedPreferences);
|
||||||
const selectedClientSettings = getSubsetFromKeysArray(clientSettings, clientSyncKeys);
|
const selectedClientSettings = getSubsetFromKeysArray(clientSettings, clientSyncKeys);
|
||||||
|
|
|
@ -620,6 +620,12 @@ svg + .button__label {
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button--file-action {
|
||||||
|
&:first-child {
|
||||||
|
margin-right: var(--spacing-s);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button--file-action {
|
.button--file-action {
|
||||||
|
|
|
@ -221,13 +221,28 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-viewer__overlay-logo {
|
.file-viewer__overlay-logo {
|
||||||
color: var(--color-white);
|
height: 3.5rem;
|
||||||
font-weight: bold;
|
width: 12rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.icon {
|
&:hover {
|
||||||
height: 30px;
|
filter: drop-shadow(1px 2px 10px var(--color-gray-3));
|
||||||
stroke-width: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
margin-right: var(--spacing-m);
|
||||||
|
width: 2.5rem;
|
||||||
|
|
||||||
|
.button__label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-viewer__overlay-logo--videoend {
|
||||||
|
height: 3.5rem;
|
||||||
|
width: 12rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-viewer--is-playing:not(:hover) .file-viewer__embedded-header {
|
.file-viewer--is-playing:not(:hover) .file-viewer__embedded-header {
|
||||||
|
@ -237,11 +252,15 @@
|
||||||
.file-viewer__embedded-header {
|
.file-viewer__embedded-header {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
height: 4rem;
|
||||||
|
padding-left: var(--spacing-m);
|
||||||
|
padding-right: var(--spacing-s);
|
||||||
font-size: var(--font-large);
|
font-size: var(--font-large);
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
@ -251,7 +270,6 @@
|
||||||
border-top-right-radius: var(--border-radius);
|
border-top-right-radius: var(--border-radius);
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
padding: var(--spacing-s);
|
|
||||||
color: var(--color-white);
|
color: var(--color-white);
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|
||||||
|
@ -571,16 +589,16 @@ video::-internal-media-controls-overlay-cast-button {
|
||||||
|
|
||||||
.file-render {
|
.file-render {
|
||||||
.video-js {
|
.video-js {
|
||||||
display: flex;
|
/*display: flex;*/
|
||||||
align-items: center;
|
/*align-items: center;*/
|
||||||
justify-content: center;
|
/*justify-content: center;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-big-play-button {
|
.vjs-big-play-button {
|
||||||
@extend .button--icon;
|
@extend .button--icon;
|
||||||
@extend .button--play;
|
@extend .button--play;
|
||||||
border: none;
|
border: none;
|
||||||
position: static;
|
/*position: static;*/
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|
||||||
.vjs-icon-placeholder {
|
.vjs-icon-placeholder {
|
||||||
|
|
|
@ -203,7 +203,7 @@ input-submit {
|
||||||
height: var(--height-checkbox);
|
height: var(--height-checkbox);
|
||||||
width: var(--height-checkbox);
|
width: var(--height-checkbox);
|
||||||
border: 1px solid var(--color-input-border);
|
border: 1px solid var(--color-input-border);
|
||||||
border-radius: var(--border-radius);
|
border-radius: 3px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
top: -1px;
|
top: -1px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,6 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
color: var(--color-text);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
|
|
||||||
|
|
|
@ -281,8 +281,8 @@ $discussion-header__height: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.livestream-superchat__banner {
|
.livestream-superchat__banner {
|
||||||
border-top-right-radius: var(--border-radius);
|
border-top-left-radius: 0;
|
||||||
border-bottom-right-radius: var(--border-radius);
|
border-bottom-left-radius: 0;
|
||||||
padding: 0.25rem var(--spacing-s);
|
padding: 0.25rem var(--spacing-s);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -298,7 +298,7 @@ $discussion-header__height: 3rem;
|
||||||
background-color: var(--color-superchat);
|
background-color: var(--color-superchat);
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
transform: translateX(25%) translateY(50%);
|
transform: translateX(0) translateY(50%);
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -316,6 +316,11 @@ $discussion-header__height: 3rem;
|
||||||
.livestream-comment__text {
|
.livestream-comment__text {
|
||||||
padding-right: var(--spacing-xxs);
|
padding-right: var(--spacing-xxs);
|
||||||
padding-bottom: var(--spacing-xxs);
|
padding-bottom: var(--spacing-xxs);
|
||||||
|
.markdown-preview {
|
||||||
|
p {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.livestream-superchat__tooltip-amount {
|
.livestream-superchat__tooltip-amount {
|
||||||
|
|
|
@ -39,6 +39,10 @@
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation--mac {
|
.navigation--mac {
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
|
|
||||||
.section__title {
|
.section__title {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: var(--font-title);
|
font-size: var(--font-large);
|
||||||
font-weight: var(--font-weight-light);
|
font-weight: var(--font-weight-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
:root {
|
|
||||||
// Generic colors
|
|
||||||
--color-primary: #257761;
|
|
||||||
--color-primary-alt: #e4f4ef;
|
|
||||||
--color-primary-alt-2: #4b8576;
|
|
||||||
--color-secondary: #295284;
|
|
||||||
--color-secondary-alt: #d9eaff;
|
|
||||||
--color-tertiary: #552470;
|
|
||||||
--color-tertiary-alt: #f7e8ff;
|
|
||||||
--color-danger: #9b2023;
|
|
||||||
--color-danger-alt: #fccdce;
|
|
||||||
--color-warning: #fff58c;
|
|
||||||
--color-cost: #ffd580;
|
|
||||||
--color-focus: #93cff2;
|
|
||||||
--color-notification: #f02849;
|
|
||||||
--color-live: #cc190f;
|
|
||||||
|
|
||||||
--color-black: #111;
|
|
||||||
--color-white: #fdfdfd;
|
|
||||||
--color-white-alt: #fafafa;
|
|
||||||
--color-gray-1: #eff1f4;
|
|
||||||
--color-gray-2: #d8dde1;
|
|
||||||
--color-gray-3: #ced4da;
|
|
||||||
--color-gray-4: #abb1b7;
|
|
||||||
--color-gray-5: #666a6d;
|
|
||||||
|
|
||||||
// Text
|
|
||||||
--color-text: var(--color-black);
|
|
||||||
--color-text-subtitle: var(--color-gray-5);
|
|
||||||
--color-text-inverse: #fdfdfd;
|
|
||||||
|
|
||||||
// Components
|
|
||||||
|
|
||||||
// Button
|
|
||||||
--color-button-primary-bg: var(--color-primary);
|
|
||||||
--color-button-primary-text: var(--color-primary-alt);
|
|
||||||
--color-button-primary-bg-hover: var(--color-primary-alt-2);
|
|
||||||
--color-button-primary-hover-text: var(--color-primary-alt);
|
|
||||||
--color-button-secondary-bg: var(--color-secondary-alt);
|
|
||||||
--color-button-secondary-border: var(--color-secondary-alt);
|
|
||||||
--color-button-secondary-text: var(--color-secondary);
|
|
||||||
--color-button-secondary-bg-hover: #b9d0e9;
|
|
||||||
--color-button-alt-bg: var(--color-gray-1);
|
|
||||||
--color-button-alt-text: var(--color-text);
|
|
||||||
--color-button-alt-bg-hover: var(--color-gray-2);
|
|
||||||
--color-link: var(--color-primary);
|
|
||||||
--color-link-hover: var(--color-black);
|
|
||||||
|
|
||||||
// Table
|
|
||||||
--color-table-highlight: var(--color-white-alt);
|
|
||||||
|
|
||||||
// Tag
|
|
||||||
--color-tag: var(--color-gray-5);
|
|
||||||
--color-tag-bg: var(--color-button-alt-bg);
|
|
||||||
--color-tag-hover: var(--color-button-alt-text);
|
|
||||||
--color-tag-bg-hover: var(--color-button-alt-bg-hover);
|
|
||||||
}
|
|
|
@ -60,8 +60,12 @@ ol {
|
||||||
position: relative;
|
position: relative;
|
||||||
list-style-position: outside;
|
list-style-position: outside;
|
||||||
margin: var(--spacing-xs) 0;
|
margin: var(--spacing-xs) 0;
|
||||||
margin-left: var(--spacing-xl);
|
margin-left: var(--spacing-s);
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
margin-left: var(--spacing-xl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,10 +271,10 @@ textarea {
|
||||||
margin-top: var(--spacing-s);
|
margin-top: var(--spacing-s);
|
||||||
|
|
||||||
&:not(:last-child) {
|
&:not(:last-child) {
|
||||||
margin-bottom: var(--spacing-s);
|
margin-bottom: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button--link ~ .button--link {
|
.button--link + .button--link {
|
||||||
margin-left: var(--spacing-s);
|
margin-left: var(--spacing-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -499,3 +503,16 @@ textarea {
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home__meme {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: var(--spacing-m);
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-bottom: var(--spacing-l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ $breakpoint-medium: 1150px;
|
||||||
$breakpoint-large: 1600px;
|
$breakpoint-large: 1600px;
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--border-radius: 5px;
|
--border-radius: 10px;
|
||||||
--height-input: 2.5rem;
|
--height-input: 2.5rem;
|
||||||
--height-button: 2.5rem;
|
--height-button: 2.5rem;
|
||||||
--height-checkbox: 24px;
|
--height-checkbox: 24px;
|
||||||
|
|
|
@ -1,30 +1,29 @@
|
||||||
[theme='dark'] {
|
[theme='dark'] {
|
||||||
// Color overrides
|
// Color overrides
|
||||||
--color-primary: #2bbb90;
|
--color-primary: #e50054;
|
||||||
--color-primary-alt: #3e675d;
|
--color-primary-alt: #66001880;
|
||||||
--color-primary-alt-2: #065f46;
|
--color-fire: #ff6635;
|
||||||
--color-primary-alt-3: #34e5b0;
|
--color-fire-outside: #ff9b20;
|
||||||
--color-secondary: #204166;
|
|
||||||
--color-secondary-alt: #dbeafe;
|
|
||||||
--color-secondary-alt-2: #bfdbfe;
|
|
||||||
--color-secondary-alt-3: #2c5c8c;
|
|
||||||
|
|
||||||
// Structure
|
// Structure
|
||||||
--color-background: var(--color-gray-9);
|
--color-background: #140e1b;
|
||||||
--color-background-overlay: #21252999;
|
--color-background-overlay: #0c0d0e95;
|
||||||
--color-border: #333338;
|
--color-border: #30243d;
|
||||||
--color-card-background: var(--color-gray-8);
|
--color-card-background: #181021;
|
||||||
--color-card-background-highlighted: var(--color-gray-7);
|
--color-card-background-highlighted: #241c30;
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
--color-text: var(--color-white);
|
--color-text: var(--color-gray-1);
|
||||||
--color-text-subtitle: var(--color-gray-4);
|
--color-text-subtitle: var(--color-gray-4);
|
||||||
--color-text-empty: var(--color-text-subtitle);
|
--color-text-empty: var(--color-text-subtitle);
|
||||||
--color-text-help: #bbbbbb;
|
--color-text-help: #bbbbbb;
|
||||||
--color-text-warning: #212529;
|
--color-text-warning: #212529;
|
||||||
--color-text-warning--background: var(--lbry-yellow-1);
|
--color-text-warning--background: var(--lbry-yellow-1);
|
||||||
--color-text-error: #f87171;
|
--color-text-error: var(--color-danger);
|
||||||
--color-error: #61373f;
|
--color-error: var(--color-danger-alt);
|
||||||
|
--color-blockquote: var(--color-gray-5);
|
||||||
|
--color-blockquote-bg: var(--color-card-background-highlighted);
|
||||||
|
--color-help-warning-text: var(--color-white-alt);
|
||||||
|
|
||||||
// Tags (words)
|
// Tags (words)
|
||||||
--color-tag-words: var(--color-text);
|
--color-tag-words: var(--color-text);
|
||||||
|
@ -33,87 +32,73 @@
|
||||||
--color-tag-words-bg-hover: var(--color-gray-4);
|
--color-tag-words-bg-hover: var(--color-gray-4);
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
--color-header-background: var(--color-gray-8);
|
--color-header-button: #38274c;
|
||||||
--color-header-button: var(--color-gray-6);
|
--color-header-background: #231830;
|
||||||
--color-header-button-hover: var(--color-gray-6);
|
|
||||||
--color-header-button-active: var(--color-gray-6);
|
|
||||||
|
|
||||||
// Button
|
// Button
|
||||||
--color-button-primary-bg: var(--color-primary-alt);
|
--color-button-primary-text: white;
|
||||||
--color-button-primary-bg-hover: var(--color-primary-alt-2);
|
|
||||||
--color-button-primary-text: var(--color-gray-2);
|
|
||||||
--color-button-primary-hover-text: var(--color-primary-alt);
|
--color-button-primary-hover-text: var(--color-primary-alt);
|
||||||
--color-button-secondary-bg: var(--color-secondary);
|
--color-button-secondary-bg: #2c1543;
|
||||||
--color-button-secondary-border: var(--color-secondary);
|
--color-button-secondary-border: #4f1c82;
|
||||||
--color-button-secondary-bg-hover: var(--color-secondary-alt-3);
|
--color-button-secondary-bg-hover: #3b1c5b;
|
||||||
--color-button-secondary-text: var(--color-gray-2);
|
--color-button-secondary-text: #efefef;
|
||||||
--color-button-alt-bg: var(--color-gray-7);
|
--color-button-alt-bg: var(--color-header-button);
|
||||||
--color-button-alt-bg-hover: var(--color-gray-6);
|
--color-button-alt-bg-hover: #2b2037;
|
||||||
--color-button-alt-text: var(--color-gray-1);
|
--color-button-toggle-text: var(--color-text);
|
||||||
--color-button-border: var(--color-gray-5);
|
--color-button-toggle-bg: var(--color-primary-alt);
|
||||||
--color-button-toggle-text: var(--color-gray-1);
|
--color-button-toggle-bg-hover: var(--color-primary-alt);
|
||||||
--color-link: var(--color-primary-alt-3);
|
--color-button-alt-text: #e2e9f0;
|
||||||
--color-link-hover: var(--color-text);
|
--color-button-border: #5b4475;
|
||||||
--color-link-focus-bg: var(--color-gray-7);
|
--color-link: var(--color-primary);
|
||||||
|
--color-link-hover: #d75673;
|
||||||
|
--color-link-active: #ec1d4c;
|
||||||
|
--color-link-focus-bg: #3d2d4e;
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
--color-input: var(--color-white);
|
--color-input: #f4f4f5;
|
||||||
--color-input-label: var(--color-gray-3);
|
--color-input-label: #a7a7a7;
|
||||||
--color-input-placeholder: var(--color-gray-1);
|
--color-input-placeholder: #f4f4f5;
|
||||||
--color-input-bg: var(--color-header-button);
|
--color-input-bg: var(--color-header-button);
|
||||||
--color-input-bg-copyable: var(--color-gray-6);
|
--color-input-bg-copyable: #4c3861;
|
||||||
--color-input-border: var(--color-border);
|
--color-input-border: var(--color-border);
|
||||||
--color-input-border-active: var(--color-secondary);
|
--color-input-border-active: var(--color-secondary);
|
||||||
--color-input-toggle: var(--color-primary-alt-3);
|
--color-input-toggle: var(--color-primary-alt-3);
|
||||||
--color-input-toggle-bg: var(--color-input-bg);
|
--color-input-toggle-bg: var(--color-input-bg);
|
||||||
--color-input-toggle-bg-hover: var(--color-secondary);
|
--color-input-toggle-bg-hover: var(--color-secondary);
|
||||||
--color-input-bg-selected: var(--color-primary-alt);
|
--color-input-bg-selected: var(--color-primary-alt);
|
||||||
--color-input-prefix-bg: var(--color-gray-5);
|
--color-input-prefix-bg: var(--color-input-bg-copyable);
|
||||||
--color-input-prefix-border: var(--color-gray-4);
|
--color-input-prefix-border: var(--color-gray-4);
|
||||||
--select-toggle-background: url("data:image/svg+xml,%3Csvg viewBox='0 0 96 96' xmlns='http://www.w3.org/2000/svg' fill='%23ffffff'%3E%3Cpath d='M17.172, 31.172c1.562, -1.562 4.095, -1.562 5.656, 0l25.172, 25.171l25.172, -25.171c1.562, -1.562 4.095, -1.562 5.656, 0c1.562, 1.562 1.562, 4.095 0, 5.656l-28, 28c-1.562, 1.562 -4.095, 1.562 -5.656, 0l-28, -28c-0.781, -0.781 -1.172, -1.805 -1.172, -2.828c0, -1.023 0.391, -2.047 1.172, -2.828Z'/%3E%3C/svg%3E%0A");
|
--select-toggle-background: url("data:image/svg+xml,%3Csvg viewBox='0 0 96 96' xmlns='http://www.w3.org/2000/svg' fill='%23ffffff'%3E%3Cpath d='M17.172, 31.172c1.562, -1.562 4.095, -1.562 5.656, 0l25.172, 25.171l25.172, -25.171c1.562, -1.562 4.095, -1.562 5.656, 0c1.562, 1.562 1.562, 4.095 0, 5.656l-28, 28c-1.562, 1.562 -4.095, 1.562 -5.656, 0l-28, -28c-0.781, -0.781 -1.172, -1.805 -1.172, -2.828c0, -1.023 0.391, -2.047 1.172, -2.828Z'/%3E%3C/svg%3E%0A");
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
--color-navigation-icon: var(--color-gray-4);
|
--color-navigation-icon: #76808a;
|
||||||
--color-navigation-link: var(--color-gray-4);
|
--color-navigation-link: #b9c3ce;
|
||||||
--color-navigation-active: var(--color-gray-7);
|
--color-navigation-active: #2b2037;
|
||||||
--color-navigation-active-text: var(--color-gray-3);
|
--color-navigation-active-text: #c6bcd2;
|
||||||
--color-navigation-hover: var(--color-gray-6);
|
--color-navigation-hover: #21182a;
|
||||||
--color-navigation-hover-text: var(--color-gray-3);
|
--color-navigation-hover-text: #c6bcd2;
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
--color-tag: var(--color-primary-alt-3);
|
--color-tag: #ff85b1;
|
||||||
--color-tag-bg: var(--color-gray-7);
|
--color-tag-bg: var(--color-navigation-hover);
|
||||||
--color-tag-hover: var(--color-white);
|
--color-tag-hover: var(--color-white);
|
||||||
--color-tag-bg-hover: var(--color-primary-alt);
|
--color-tag-bg-hover: var(--color-primary-alt-2);
|
||||||
|
--color-tag-mature-bg: var(--color-primary-alt-2);
|
||||||
|
|
||||||
// Menu
|
// Menu
|
||||||
--color-menu-background: var(--color-header-background);
|
--color-menu-background: var(--color-header-background);
|
||||||
--color-menu-background--active: var(--color-gray-7);
|
--color-menu-background--active: var(--color-primary-alt);
|
||||||
--color-menu-icon: var(--color-gray-4);
|
--color-menu-icon: #928b9b;
|
||||||
|
--color-menu-icon-active: #d6d6d6;
|
||||||
|
|
||||||
// Comments
|
// Comments
|
||||||
--color-comment-menu: var(--color-gray-5);
|
--color-comment-menu: #6a6a6a;
|
||||||
--color-comment-menu-hovering: var(--color-gray-2);
|
--color-comment-menu-hovering: #e0e0e0;
|
||||||
--color-comment-threadline: #434b54;
|
|
||||||
--color-comment-threadline-hover: var(--color-gray-4);
|
|
||||||
--color-comment-highlighted: #484734;
|
--color-comment-highlighted: #484734;
|
||||||
|
--color-comment-threadline: #24192f;
|
||||||
// Snack
|
--color-comment-threadline-hover: var(--color-gray-4);
|
||||||
--color-snack-bg: var(--color-secondary);
|
|
||||||
|
|
||||||
// Superchat
|
|
||||||
--color-superchat-text: var(--color-black);
|
|
||||||
--color-superchat-text__light: var(--color-text);
|
|
||||||
--color-superchat: #fcd34d;
|
|
||||||
--color-superchat__light: #ef4e1647;
|
|
||||||
--color-superchat-2: #fde68a;
|
|
||||||
--color-superchat-3: #fef3c7;
|
|
||||||
--color-superchat-3__light: #58066087;
|
|
||||||
--color-superchat-4: #fffbeb;
|
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
--color-focus: #93c5fd50;
|
|
||||||
--color-nag: var(--color-orange);
|
|
||||||
--color-tab-text: var(--color-white);
|
--color-tab-text: var(--color-white);
|
||||||
--color-tabs-background: var(--color-card-background);
|
--color-tabs-background: var(--color-card-background);
|
||||||
--color-tab-divider: var(--color-white);
|
--color-tab-divider: var(--color-white);
|
||||||
|
@ -121,14 +106,12 @@
|
||||||
--color-notice: #58563b;
|
--color-notice: #58563b;
|
||||||
--color-purchased: #ffd580;
|
--color-purchased: #ffd580;
|
||||||
--color-purchased-alt: var(--color-purchased);
|
--color-purchased-alt: var(--color-purchased);
|
||||||
--color-purchased-text: var(--color-gray-5);
|
--color-purchased-text: black;
|
||||||
--color-thumbnail-background: var(--color-gray-5);
|
--color-thumbnail-background: var(--color-gray-5);
|
||||||
--color-tooltip-bg: #2f3337;
|
--color-tooltip-bg: #2f3337;
|
||||||
--color-help-warning-bg: #d97706;
|
--color-focus: #e91e6329;
|
||||||
--color-help-warning-text: white;
|
--color-placeholder-background: #261a35;
|
||||||
--color-blockquote: var(--color-gray-5);
|
--color-spinner-light: white;
|
||||||
--color-placeholder-background: #4e5862;
|
|
||||||
--color-spinner-light: #5a6570;
|
|
||||||
--color-spinner-dark: #212529;
|
--color-spinner-dark: #212529;
|
||||||
--color-login-graphic-background: var(--color-background);
|
--color-login-graphic-background: var(--color-background);
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,155 @@
|
||||||
:root {
|
:root {
|
||||||
|
// Color overrides
|
||||||
|
--color-primary: #fa6165;
|
||||||
|
--color-primary-alt: #fef1f6;
|
||||||
|
--color-primary-alt-2: #fb7e82;
|
||||||
|
--color-primary-alt-3: #fbcbdd;
|
||||||
|
--color-secondary: #f9902a;
|
||||||
|
--color-secondary-alt: #fee8d2;
|
||||||
|
--color-secondary-alt-2: #fefcf6;
|
||||||
|
|
||||||
|
// Structure
|
||||||
|
--color-border: #ededed;
|
||||||
|
--color-background: #fafafa;
|
||||||
|
--color-background-overlay: #21252980;
|
||||||
|
--color-card-background: #ffffff;
|
||||||
|
--color-card-background-highlighted: #fff5f5;
|
||||||
|
|
||||||
|
// Text
|
||||||
|
--color-text-selection-bg: var(--color-primary-alt);
|
||||||
|
--color-text-selection: var(--color-primary);
|
||||||
|
--color-text-error: var(--color-danger);
|
||||||
|
--color-text-empty: #999999;
|
||||||
|
--color-text-help: #999999;
|
||||||
|
--color-text-subtitle: #767676;
|
||||||
|
--color-text-warning: #212529;
|
||||||
|
--color-help-warning-bg: #fef3c7;
|
||||||
|
--color-text-warning--background: var(--lbry-yellow-1);
|
||||||
|
--color-blockquote: var(--color-gray-3);
|
||||||
|
--color-blockquote-bg: var(--color-gray-1);
|
||||||
|
--color-tooltip-bg: #222;
|
||||||
|
--color-tooltip-text: #fafafa;
|
||||||
|
|
||||||
|
// Header
|
||||||
|
--color-header-button: var(--color-button-alt-bg);
|
||||||
|
--color-header-background: #ffffff;
|
||||||
|
|
||||||
|
// Button
|
||||||
|
--color-button-alt-bg: var(--color-gray-1);
|
||||||
|
--color-button-alt-bg-hover: var(--color-gray-2);
|
||||||
|
--color-button-alt-text: black;
|
||||||
|
--color-button-primary-bg: var(--color-primary);
|
||||||
|
--color-button-primary-bg-hover: var(--color-primary-alt-2);
|
||||||
|
--color-button-primary-text: var(--color-primary-alt);
|
||||||
|
--color-button-primary-hover-text: var(--color-white);
|
||||||
|
--color-button-secondary-bg: var(--color-primary-alt);
|
||||||
|
--color-button-secondary-border: var(--color-primary-alt-3);
|
||||||
|
--color-button-secondary-text: var(--color-primary);
|
||||||
|
--color-button-secondary-bg-hover: var(--color-primary-alt-3);
|
||||||
|
--color-button-toggle-text: var(--color-primary);
|
||||||
|
--color-button-toggle-bg: var(--color-primary-alt);
|
||||||
|
--color-button-toggle-bg-hover: var(--color-primary-alt);
|
||||||
|
--color-button-border: var(--color-gray-3);
|
||||||
|
--color-link-active: var(--color-primary);
|
||||||
|
--color-link-focus-bg: #f1f1f1;
|
||||||
|
--color-link: var(--color-primary);
|
||||||
|
|
||||||
|
// Input
|
||||||
|
--color-input-bg-selected: var(--color-primary-alt);
|
||||||
|
--color-input-color: #111111;
|
||||||
|
--color-input-label: var(--color-gray-5);
|
||||||
|
--color-input-placeholder: #212529;
|
||||||
|
--color-input-bg: var(--color-gray-1);
|
||||||
|
--color-input-border: var(--color-border);
|
||||||
|
--color-input-border-active: var(--color-secondary);
|
||||||
|
--color-input-toggle: var(--color-secondary);
|
||||||
|
--color-input-toggle-bg: var(--color-gray-1);
|
||||||
|
--color-input-toggle-bg-hover: var(--color-secondary-alt);
|
||||||
|
--color-input-prefix-bg: var(--color-gray-2);
|
||||||
|
--color-input-prefix-border: var(--color-gray-5);
|
||||||
|
--select-toggle-background: url("data:image/svg+xml,%3Csvg viewBox='0 0 96 96' xmlns='http://www.w3.org/2000/svg' fill='%23212529'%3E%3Cpath d='M17.172, 31.172c1.562, -1.562 4.095, -1.562 5.656, 0l25.172, 25.171l25.172, -25.171c1.562, -1.562 4.095, -1.562 5.656, 0c1.562, 1.562 1.562, 4.095 0, 5.656l-28, 28c-1.562, 1.562 -4.095, 1.562 -5.656, 0l-28, -28c-0.781, -0.781 -1.172, -1.805 -1.172, -2.828c0, -1.023 0.391, -2.047 1.172, -2.828Z'/%3E%3C/svg%3E%0A");
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
--color-navigation-icon: var(--color-gray-5);
|
||||||
|
--color-navigation-link: var(--color-gray-5);
|
||||||
|
--color-navigation-active: var(--color-primary-alt);
|
||||||
|
--color-navigation-active-text: var(--color-primary);
|
||||||
|
--color-navigation-hover: var(--color-gray-1);
|
||||||
|
--color-navigation-hover-text: #3f3f3f;
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
--color-tag: var(--color-primary-alt-2);
|
||||||
|
--color-tag-bg: #f9f6f7;
|
||||||
|
--color-tag-hover: var(--color-button-alt-text);
|
||||||
|
--color-tag-bg-hover: var(--color-button-alt-bg-hover);
|
||||||
|
|
||||||
|
// Menu
|
||||||
|
--color-menu-background: var(--color-header-background);
|
||||||
|
--color-menu-icon: var(--color-navigation-link);
|
||||||
|
--color-menu-icon-active: var(--color-navigation-link);
|
||||||
|
--color-menu-background--selected: var(--color-secondary-alt);
|
||||||
|
--color-menu-background--active: var(--color-primary-alt);
|
||||||
|
|
||||||
|
// Comments
|
||||||
|
--color-comment-menu: #e0e0e0;
|
||||||
|
--color-comment-menu-hovering: #6a6a6a;
|
||||||
|
--color-comment-highlighted: #fff2d9;
|
||||||
|
--color-comment-threadline: var(--color-gray-1);
|
||||||
|
--color-comment-threadline-hover: var(--color-gray-4);
|
||||||
|
|
||||||
|
// Superchat
|
||||||
|
--color-superchat-text: var(--color-black);
|
||||||
|
--color-superchat: #fcd34d;
|
||||||
|
--color-superchat__light: #fcd34d50;
|
||||||
|
--color-superchat-2: #fde68a;
|
||||||
|
--color-superchat-3: #fef3c7;
|
||||||
|
--color-superchat-3__light: #fef3c750;
|
||||||
|
--color-superchat-4: #fffbeb;
|
||||||
|
|
||||||
|
// Color
|
||||||
|
--color-focus: #8dbff0;
|
||||||
|
--color-nag: #fa8700;
|
||||||
|
--color-error: #fcafca;
|
||||||
|
--color-notice: #fef3ca;
|
||||||
|
--color-purchased: var(--color-cost);
|
||||||
|
--color-purchased-alt: #ffebc2;
|
||||||
|
--color-purchased-text: black;
|
||||||
|
--color-thumbnail-background: var(--color-gray-1);
|
||||||
|
--color-spinner-light: #ffffff;
|
||||||
|
--color-spinner-dark: #212529;
|
||||||
|
--color-placeholder-background: #f0f0f0;
|
||||||
|
--color-file-viewer-background: var(--color-card-background);
|
||||||
|
--color-tabs-background: var(--color-card-background);
|
||||||
|
--color-tab-divider: var(--color-primary);
|
||||||
|
--color-modal-background: var(--color-card-background);
|
||||||
|
|
||||||
|
// Icons
|
||||||
|
--color-follow-bg: #ffd4da;
|
||||||
|
--color-follow-icon: #e2495e;
|
||||||
|
--color-view-bg: var(--color-secondary-alt);
|
||||||
|
--color-view-icon: var(--color-secondary);
|
||||||
|
|
||||||
|
// Editor
|
||||||
|
--color-editor-cursor: var(--color-text);
|
||||||
|
--color-editor-quote: #707070;
|
||||||
|
--color-editor-tag: #ea9400;
|
||||||
|
--color-editor-attr: #04b0f4;
|
||||||
|
--color-editor-string: #ff7451;
|
||||||
|
--color-editor-inline-code-fg: var(--color-text);
|
||||||
|
--color-editor-inline-code-fg-preview: #2e3439;
|
||||||
|
--color-editor-inline-code-bg: rgba(157, 161, 165, 0.3);
|
||||||
|
--color-editor-inline-code-bg-preview: #d0e8ff;
|
||||||
|
--color-editor-selected: #add6ff;
|
||||||
|
--color-editor-link: var(--color-link);
|
||||||
|
--color-editor-url: var(--color-editor-string);
|
||||||
|
--color-editor-hr: var(--color-editor-tag);
|
||||||
|
--color-editor-hr-preview: #cccccc;
|
||||||
|
|
||||||
|
// Ads
|
||||||
|
--color-ads-background: #fae5ff;
|
||||||
|
--color-ads-link: var(--color-link);
|
||||||
|
|
||||||
|
// Scrollbar
|
||||||
|
--color-scrollbar-thumb-bg: rgba(0, 0, 0, 0.2);
|
||||||
|
--color-scrollbar-track-bg: transparent;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,9 +38,13 @@ export const getSearchQueryString = (query: string, options: any = {}) => {
|
||||||
const encodedQuery = encodeURIComponent(query);
|
const encodedQuery = encodeURIComponent(query);
|
||||||
const queryParams = [
|
const queryParams = [
|
||||||
options.exact && !isSurroundedByQuotes(encodedQuery) ? `s="${encodedQuery}"` : `s=${encodedQuery}`,
|
options.exact && !isSurroundedByQuotes(encodedQuery) ? `s="${encodedQuery}"` : `s=${encodedQuery}`,
|
||||||
|
`free_only=true`,
|
||||||
`size=${options.size || DEFAULT_SEARCH_SIZE}`,
|
`size=${options.size || DEFAULT_SEARCH_SIZE}`,
|
||||||
`from=${options.from || DEFAULT_SEARCH_RESULT_FROM}`,
|
`from=${options.from || DEFAULT_SEARCH_RESULT_FROM}`,
|
||||||
|
// `mediaType=${SEARCH_OPTIONS.MEDIA_VIDEO}`,
|
||||||
|
// `claimType=${SEARCH_OPTIONS.INCLUDE_FILES}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
const { isBackgroundSearch } = options;
|
const { isBackgroundSearch } = options;
|
||||||
const includeUserOptions = typeof isBackgroundSearch === 'undefined' ? false : !isBackgroundSearch;
|
const includeUserOptions = typeof isBackgroundSearch === 'undefined' ? false : !isBackgroundSearch;
|
||||||
|
|
||||||
|
|
|
@ -147,3 +147,9 @@ export const generateShareUrl = (
|
||||||
const url = `${domain}/${lbryWebUrl}` + (urlParamsString === '' ? '' : `?${urlParamsString}`);
|
const url = `${domain}/${lbryWebUrl}` + (urlParamsString === '' ? '' : `?${urlParamsString}`);
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const generateRssUrl = (domain, lbryUrl) => {
|
||||||
|
const { channelName, channelClaimId } = parseURI(lbryUrl);
|
||||||
|
const url = `${domain}/$/rss/@${channelName}/${channelClaimId}`;
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Button from 'component/button';
|
||||||
import { formatLbryUrlForWeb } from 'util/url';
|
import { formatLbryUrlForWeb } from 'util/url';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import { URL, SITE_NAME } from 'config';
|
import { URL, SITE_NAME } from 'config';
|
||||||
import * as ICONS from 'constants/icons';
|
import OdyseeLogoWithText from 'component/header/odysee_white.png';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
|
@ -37,7 +37,9 @@ function FileViewerEmbeddedEnded(props: Props) {
|
||||||
return (
|
return (
|
||||||
<div className="file-viewer__overlay">
|
<div className="file-viewer__overlay">
|
||||||
<div className="file-viewer__overlay-secondary">
|
<div className="file-viewer__overlay-secondary">
|
||||||
<Button className="file-viewer__overlay-logo" label="LBRY" icon={ICONS.LBRY} href={URL} />
|
<Button className="file-viewer__overlay-logo--videoend" href={URL}>
|
||||||
|
<img src={OdyseeLogoWithText} />
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="file-viewer__overlay-title">{prompt}</div>
|
<div className="file-viewer__overlay-title">{prompt}</div>
|
||||||
<div className="file-viewer__overlay-actions">
|
<div className="file-viewer__overlay-actions">
|
||||||
|
|
|
@ -1,101 +1,34 @@
|
||||||
import * as PAGES from 'constants/pages';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
import I18nMessage from 'component/i18nMessage';
|
||||||
const sections = [
|
|
||||||
{
|
|
||||||
name: 'Community',
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
label: 'Twitter',
|
|
||||||
link: 'https://twitter.com/lbrycom',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Reddit',
|
|
||||||
link: 'https://reddit.com/r/lbry',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Chat (Discord)',
|
|
||||||
link: 'https://chat.lbry.org/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Telegram',
|
|
||||||
link: 'https://t.me/lbryofficial',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Facebook',
|
|
||||||
link: 'https://www.facebook.com/lbryio',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Resources',
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
label: 'FAQ',
|
|
||||||
link: 'https://lbry.com/faq',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Support --[used in footer; general help/support]--',
|
|
||||||
link: 'https://lbry.com/faq/support',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'YouTube Partner Program',
|
|
||||||
link: 'https://lbry.com/youtube',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'lbry.com',
|
|
||||||
link: 'https://lbry.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'lbry.tech',
|
|
||||||
link: 'https://lbry.tech',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'GitHub',
|
|
||||||
link: 'https://github.com/lbryio',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Policies',
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
label: 'Terms of Service',
|
|
||||||
link: 'https://www.lbry.com/termsofservice',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Privacy Policy',
|
|
||||||
link: 'https://lbry.com/privacypolicy',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '2257',
|
|
||||||
navigate: `/$/${PAGES.CODE_2257}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer className="footer">
|
<footer className="footer">
|
||||||
<ul className="navigation__tertiary footer__links ul--no-style">
|
<span className="footer__section-title">
|
||||||
{sections.map(({ name, links }) => {
|
<I18nMessage tokens={{ lbry_link: <Button button="link" label={'LBRY'} href="https://lbry.com" /> }}>
|
||||||
return (
|
POWERED BY %lbry_link%
|
||||||
<li key={name} className="footer__section">
|
</I18nMessage>
|
||||||
<ul className="ul--no-style">
|
</span>
|
||||||
<div className="footer__section-title">{__(name)}</div>
|
<ul className="navigation__tertiary footer__links">
|
||||||
{links.map(({ label, link, navigate }) => {
|
<li className="footer__link">
|
||||||
return (
|
<Button label={__('About --[link title in Sidebar or Footer]--')} href="https://lbry.com/about" />
|
||||||
<li key={label}>
|
</li>
|
||||||
<Button className="footer__link" label={__(label)} href={link} navigate={navigate} />
|
<li className="footer__link">
|
||||||
</li>
|
<Button label={__('Community Guidelines')} href="https://odysee.com/@OdyseeHelp:b/Community-Guidelines:c" />
|
||||||
);
|
</li>
|
||||||
})}
|
<li className="footer__link">
|
||||||
</ul>
|
<Button label={__('FAQ')} href="https://odysee.com/@OdyseeHelp:b" />
|
||||||
</li>
|
</li>
|
||||||
);
|
<li className="footer__link">
|
||||||
})}
|
<Button label={__('Support --[used in footer; general help/support]--')} href="https://lbry.com/support" />
|
||||||
|
</li>
|
||||||
|
<li className="footer__link">
|
||||||
|
<Button label={__('Terms')} href="https://lbry.com/termsofservice" />
|
||||||
|
</li>
|
||||||
|
<li className="footer__link">
|
||||||
|
<Button label={__('Privacy Policy')} href="https://lbry.com/privacy" />
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,7 +9,7 @@ const pool = mysql.createPool({
|
||||||
});
|
});
|
||||||
|
|
||||||
function queryPool(sql, params) {
|
function queryPool(sql, params) {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
pool.query(sql, params, (error, rows) => {
|
pool.query(sql, params, (error, rows) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log('error', error); // eslint-disable-line
|
console.log('error', error); // eslint-disable-line
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const { getHtml } = require('./html');
|
const { getHtml } = require('./html');
|
||||||
|
const { getRss } = require('./rss');
|
||||||
const { generateStreamUrl } = require('../../ui/util/web');
|
const { generateStreamUrl } = require('../../ui/util/web');
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
const Router = require('@koa/router');
|
const Router = require('@koa/router');
|
||||||
|
@ -42,6 +43,12 @@ router.get(`/$/stream/:claimName/:claimId`, async (ctx) => {
|
||||||
ctx.redirect(streamUrl);
|
ctx.redirect(streamUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get(`/$/rss/:claimName/:claimId`, async (ctx) => {
|
||||||
|
const xml = await getRss(ctx);
|
||||||
|
ctx.set('Content-Type', 'application/rss+xml');
|
||||||
|
ctx.body = xml;
|
||||||
|
});
|
||||||
|
|
||||||
router.get('*', async (ctx) => {
|
router.get('*', async (ctx) => {
|
||||||
const html = await getHtml(ctx);
|
const html = await getHtml(ctx);
|
||||||
ctx.body = html;
|
ctx.body = html;
|
||||||
|
|
96
web/src/rss.js
Normal file
96
web/src/rss.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
const { URL, SITE_NAME, LBRY_WEB_API } = require('../../config.js');
|
||||||
|
const { Lbry } = require('lbry-redux');
|
||||||
|
const Feed = require('feed').Feed;
|
||||||
|
|
||||||
|
const SDK_API_PATH = `${LBRY_WEB_API}/api/v1`;
|
||||||
|
const proxyURL = `${SDK_API_PATH}/proxy`;
|
||||||
|
Lbry.setDaemonConnectionString(proxyURL);
|
||||||
|
|
||||||
|
async function doClaimSearch(options) {
|
||||||
|
let results;
|
||||||
|
try {
|
||||||
|
results = await Lbry.claim_search(options);
|
||||||
|
} catch {}
|
||||||
|
return results ? results.items : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getChannelClaim(claimId) {
|
||||||
|
const options = {
|
||||||
|
claim_ids: [claimId],
|
||||||
|
page_size: 1,
|
||||||
|
no_totals: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const claims = await doClaimSearch(options);
|
||||||
|
return claims ? claims[0] : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getClaimsFromChannel(claimId, count) {
|
||||||
|
const options = {
|
||||||
|
channel_ids: [claimId],
|
||||||
|
page_size: count,
|
||||||
|
has_source: true,
|
||||||
|
claim_type: 'stream',
|
||||||
|
order_by: ['creation_timestamp'],
|
||||||
|
no_totals: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
return await doClaimSearch(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFeed(channelClaim) {
|
||||||
|
const replaceLineFeeds = (str) => str.replace(/(?:\r\n|\r|\n)/g, '<br>');
|
||||||
|
|
||||||
|
const value = channelClaim.value;
|
||||||
|
const title = value ? value.title : channelClaim.name;
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
title: title + ' on ' + SITE_NAME,
|
||||||
|
description: value ? replaceLineFeeds(value.description) : '',
|
||||||
|
link: `${URL}/${channelClaim.name}:${channelClaim.claim_id}`,
|
||||||
|
favicon: URL + '/public/favicon.png',
|
||||||
|
generator: SITE_NAME + ' RSS Feed',
|
||||||
|
image: value ? value.thumbnail.url : '',
|
||||||
|
author: {
|
||||||
|
name: channelClaim.name,
|
||||||
|
link: URL + '/' + channelClaim.name + ':' + channelClaim.claim_id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const feed = new Feed(options);
|
||||||
|
|
||||||
|
const latestClaims = await getClaimsFromChannel(channelClaim.claim_id, 50);
|
||||||
|
|
||||||
|
latestClaims.forEach((c) => {
|
||||||
|
const meta = c.meta;
|
||||||
|
const value = c.value;
|
||||||
|
|
||||||
|
feed.addItem({
|
||||||
|
guid: c.claim_id,
|
||||||
|
id: c.claim_id,
|
||||||
|
title: value ? value.title : c.name,
|
||||||
|
description: value ? replaceLineFeeds(value.description) : '',
|
||||||
|
image: value ? value.thumbnail.url : '',
|
||||||
|
link: URL + '/' + c.name + ':' + c.claim_id,
|
||||||
|
date: new Date(meta ? meta.creation_timestamp * 1000 : null),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return feed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getRss(ctx) {
|
||||||
|
if (!ctx.params.claimName || !ctx.params.claimId) {
|
||||||
|
return 'Invalid URL';
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelClaim = await getChannelClaim(ctx.params.claimId);
|
||||||
|
if (typeof channelClaim === 'string' || !channelClaim) {
|
||||||
|
return channelClaim;
|
||||||
|
}
|
||||||
|
|
||||||
|
const feed = await getFeed(channelClaim);
|
||||||
|
return feed.rss2();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getRss };
|
82
yarn.lock
82
yarn.lock
|
@ -1266,6 +1266,25 @@
|
||||||
tough-cookie "^2.2.2"
|
tough-cookie "^2.2.2"
|
||||||
tough-cookie-web-storage-store "^1.0.0"
|
tough-cookie-web-storage-store "^1.0.0"
|
||||||
|
|
||||||
|
"@hapi/boom@9.x.x":
|
||||||
|
version "9.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.2.tgz#48bd41d67437164a2d636e3b5bc954f8c8dc5e38"
|
||||||
|
integrity sha512-uJEJtiNHzKw80JpngDGBCGAmWjBtzxDCz17A9NO2zCi8LLBlb5Frpq4pXwyN+2JQMod4pKz5BALwyneCgDg89Q==
|
||||||
|
dependencies:
|
||||||
|
"@hapi/hoek" "9.x.x"
|
||||||
|
|
||||||
|
"@hapi/cryptiles@^5.1.0":
|
||||||
|
version "5.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-5.1.0.tgz#655de4cbbc052c947f696148c83b187fc2be8f43"
|
||||||
|
integrity sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA==
|
||||||
|
dependencies:
|
||||||
|
"@hapi/boom" "9.x.x"
|
||||||
|
|
||||||
|
"@hapi/hoek@9.x.x":
|
||||||
|
version "9.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131"
|
||||||
|
integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==
|
||||||
|
|
||||||
"@hot-loader/react-dom@^16.13":
|
"@hot-loader/react-dom@^16.13":
|
||||||
version "16.13.0"
|
version "16.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.13.0.tgz#de245b42358110baf80aaf47a0592153d4047997"
|
resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.13.0.tgz#de245b42358110baf80aaf47a0592153d4047997"
|
||||||
|
@ -2981,6 +3000,11 @@ camelcase@^5.0.0, camelcase@^5.2.0, camelcase@^5.3.1:
|
||||||
version "5.3.1"
|
version "5.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
||||||
|
|
||||||
|
can-autoplay@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/can-autoplay/-/can-autoplay-3.0.0.tgz#fc2de8f1d41b36f6d860d9336b66841d30f8b62d"
|
||||||
|
integrity sha512-qQXGGYPWgF8nPjEt305o3TJ/BkN15l6/wG+VU4N93YYXD3OtYkBBx+l5un7ihIk2UU1OLytAVJjW7ZR39j/CAQ==
|
||||||
|
|
||||||
caniuse-api@^3.0.0:
|
caniuse-api@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"
|
resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"
|
||||||
|
@ -5124,7 +5148,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
|
||||||
assign-symbols "^1.0.0"
|
assign-symbols "^1.0.0"
|
||||||
is-extendable "^1.0.1"
|
is-extendable "^1.0.1"
|
||||||
|
|
||||||
extend@^3.0.0, extend@~3.0.1:
|
extend@>=3.0.2, extend@^3.0.0, extend@~3.0.1:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||||
|
|
||||||
|
@ -5220,6 +5244,13 @@ fd-slicer@~1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
pend "~1.2.0"
|
pend "~1.2.0"
|
||||||
|
|
||||||
|
feed@^4.2.2:
|
||||||
|
version "4.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e"
|
||||||
|
integrity sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==
|
||||||
|
dependencies:
|
||||||
|
xml-js "^1.6.11"
|
||||||
|
|
||||||
figgy-pudding@^3.5.1:
|
figgy-pudding@^3.5.1:
|
||||||
version "3.5.2"
|
version "3.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
|
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
|
||||||
|
@ -7414,6 +7445,11 @@ lodash-es@^4.17.14, lodash-es@^4.2.1:
|
||||||
version "4.17.15"
|
version "4.17.15"
|
||||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
|
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
|
||||||
|
|
||||||
|
lodash._reinterpolate@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||||
|
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
|
||||||
|
|
||||||
lodash.camelcase@^4.3.0:
|
lodash.camelcase@^4.3.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||||
|
@ -7463,6 +7499,21 @@ lodash.snakecase@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d"
|
resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d"
|
||||||
|
|
||||||
|
lodash.template@>=4.5.0:
|
||||||
|
version "4.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
|
||||||
|
integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==
|
||||||
|
dependencies:
|
||||||
|
lodash._reinterpolate "^3.0.0"
|
||||||
|
lodash.templatesettings "^4.0.0"
|
||||||
|
|
||||||
|
lodash.templatesettings@^4.0.0:
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33"
|
||||||
|
integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==
|
||||||
|
dependencies:
|
||||||
|
lodash._reinterpolate "^3.0.0"
|
||||||
|
|
||||||
lodash.toarray@^4.4.0:
|
lodash.toarray@^4.4.0:
|
||||||
version "4.4.0"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
|
resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
|
||||||
|
@ -7475,7 +7526,7 @@ lodash.unset@^4.5.2:
|
||||||
version "4.5.2"
|
version "4.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.unset/-/lodash.unset-4.5.2.tgz#370d1d3e85b72a7e1b0cdf2d272121306f23e4ed"
|
resolved "https://registry.yarnpkg.com/lodash.unset/-/lodash.unset-4.5.2.tgz#370d1d3e85b72a7e1b0cdf2d272121306f23e4ed"
|
||||||
|
|
||||||
lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.6.1:
|
lodash@>=4.17.19, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.6.1:
|
||||||
version "4.17.21"
|
version "4.17.21"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
|
@ -12057,6 +12108,14 @@ vfile@^2.0.0:
|
||||||
videojs-font "3.2.0"
|
videojs-font "3.2.0"
|
||||||
videojs-vtt.js "^0.15.2"
|
videojs-vtt.js "^0.15.2"
|
||||||
|
|
||||||
|
videojs-contrib-ads@^6.6.5, videojs-contrib-ads@^6.9.0:
|
||||||
|
version "6.9.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/videojs-contrib-ads/-/videojs-contrib-ads-6.9.0.tgz#c792d6fda77254b277545cc3222352fc653b5833"
|
||||||
|
integrity sha512-nzKz+jhCGMTYffSNVYrmp9p70s05v6jUMOY3Z7DpVk3iFrWK4Zi/BIkokDWrMoHpKjdmCdKzfJVBT+CrUj6Spw==
|
||||||
|
dependencies:
|
||||||
|
global "^4.3.2"
|
||||||
|
video.js "^6 || ^7"
|
||||||
|
|
||||||
videojs-contrib-quality-levels@^2.0.9:
|
videojs-contrib-quality-levels@^2.0.9:
|
||||||
version "2.0.9"
|
version "2.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-2.0.9.tgz#b5d533d5092a6fc7d29eae1b43e4597d89bd527b"
|
resolved "https://registry.yarnpkg.com/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-2.0.9.tgz#b5d533d5092a6fc7d29eae1b43e4597d89bd527b"
|
||||||
|
@ -12077,6 +12136,18 @@ videojs-font@3.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/videojs-font/-/videojs-font-3.2.0.tgz#212c9d3f4e4ec3fa7345167d64316add35e92232"
|
resolved "https://registry.yarnpkg.com/videojs-font/-/videojs-font-3.2.0.tgz#212c9d3f4e4ec3fa7345167d64316add35e92232"
|
||||||
integrity sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA==
|
integrity sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA==
|
||||||
|
|
||||||
|
videojs-ima@^1.11.0:
|
||||||
|
version "1.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/videojs-ima/-/videojs-ima-1.11.0.tgz#26ad385e388c3da72372298d7d755b001d05a91d"
|
||||||
|
integrity sha512-ZRoWuGyJ75zamwZgpr0i/gZ6q7Evda/Q6R46gpW88WN7u0ORU7apw/lM1MSG4c3YDXW8LDENgzMAvMZUdifWhg==
|
||||||
|
dependencies:
|
||||||
|
"@hapi/cryptiles" "^5.1.0"
|
||||||
|
can-autoplay "^3.0.0"
|
||||||
|
extend ">=3.0.2"
|
||||||
|
lodash ">=4.17.19"
|
||||||
|
lodash.template ">=4.5.0"
|
||||||
|
videojs-contrib-ads "^6.6.5"
|
||||||
|
|
||||||
videojs-logo@^2.1.4:
|
videojs-logo@^2.1.4:
|
||||||
version "2.1.4"
|
version "2.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/videojs-logo/-/videojs-logo-2.1.4.tgz#56675b3f95949910bad3c217835ea57aa6fdf212"
|
resolved "https://registry.yarnpkg.com/videojs-logo/-/videojs-logo-2.1.4.tgz#56675b3f95949910bad3c217835ea57aa6fdf212"
|
||||||
|
@ -12512,6 +12583,13 @@ xdg-basedir@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
|
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
|
||||||
|
|
||||||
|
xml-js@^1.6.11:
|
||||||
|
version "1.6.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9"
|
||||||
|
integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==
|
||||||
|
dependencies:
|
||||||
|
sax "^1.2.4"
|
||||||
|
|
||||||
xmlbuilder@^10.0.0:
|
xmlbuilder@^10.0.0:
|
||||||
version "10.1.1"
|
version "10.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-10.1.1.tgz#8cae6688cc9b38d850b7c8d3c0a4161dcaf475b0"
|
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-10.1.1.tgz#8cae6688cc9b38d850b7c8d3c0a4161dcaf475b0"
|
||||||
|
|
Loading…
Reference in a new issue