diff --git a/ui/component/markdownLink/view.jsx b/ui/component/markdownLink/view.jsx
index 8267c823c..83e730171 100644
--- a/ui/component/markdownLink/view.jsx
+++ b/ui/component/markdownLink/view.jsx
@@ -67,10 +67,7 @@ function MarkdownLink(props: Props) {
const possibleLbryUrl = linkPathPlusHash ? `lbry://${linkPathPlusHash.replace(/:/g, '#')}` : undefined;
const lbryLinkIsValid = possibleLbryUrl && isURIValid(possibleLbryUrl);
- const isMarkdownLinkWithLabel =
- children && Array.isArray(children) && React.Children.count(children) === 1 && children.toString() !== href;
-
- if (lbryLinkIsValid && !isMarkdownLinkWithLabel) {
+ if (lbryLinkIsValid) {
lbryUrlFromLink = possibleLbryUrl;
}
}
diff --git a/ui/component/notificationBubble/index.js b/ui/component/notificationBubble/index.js
index 9bc30d408..ed621be81 100644
--- a/ui/component/notificationBubble/index.js
+++ b/ui/component/notificationBubble/index.js
@@ -1,10 +1,10 @@
import { connect } from 'react-redux';
-import { selectUnseenNotificationCount } from 'redux/selectors/notifications';
+import { selectUnreadNotificationCount } from 'redux/selectors/notifications';
import { selectUser } from 'redux/selectors/user';
import NotificationBubble from './view';
-const select = (state) => ({
- unseenCount: selectUnseenNotificationCount(state),
+const select = state => ({
+ unreadCount: selectUnreadNotificationCount(state),
user: selectUser(state),
});
diff --git a/ui/component/notificationBubble/view.jsx b/ui/component/notificationBubble/view.jsx
index b8ac0edb6..63f646c5a 100644
--- a/ui/component/notificationBubble/view.jsx
+++ b/ui/component/notificationBubble/view.jsx
@@ -3,16 +3,16 @@ import React from 'react';
import classnames from 'classnames';
type Props = {
- unseenCount: number,
+ unreadCount: number,
inline: boolean,
user: ?User,
};
export default function NotificationHeaderButton(props: Props) {
- const { unseenCount, inline = false, user } = props;
+ const { unreadCount, inline = false, user } = props;
const notificationsEnabled = user && user.experimental_ui;
- if (unseenCount === 0 || !notificationsEnabled) {
+ if (unreadCount === 0 || !notificationsEnabled) {
return null;
}
@@ -24,10 +24,10 @@ export default function NotificationHeaderButton(props: Props) {
>
9,
+ 'notification__bubble--small': unreadCount > 9,
})}
>
- {unseenCount > 20 ? '20+' : unseenCount}
+ {unreadCount > 20 ? '20+' : unreadCount}
);
diff --git a/ui/component/postViewer/view.jsx b/ui/component/postViewer/view.jsx
index 06175c592..22573a201 100644
--- a/ui/component/postViewer/view.jsx
+++ b/ui/component/postViewer/view.jsx
@@ -6,6 +6,7 @@ import FileActions from 'component/fileActions';
import FileRenderInitiator from 'component/fileRenderInitiator';
import FileRenderInline from 'component/fileRenderInline';
import FileViewCount from 'component/fileViewCount';
+import { formatCredits } from 'lbry-redux';
import CreditAmount from 'component/common/credit-amount';
import DateTime from 'component/dateTime';
@@ -22,6 +23,7 @@ function PostViewer(props: Props) {
}
const amount = parseFloat(claim.amount) + parseFloat(claim.meta.support_amount);
+ const formattedAmount = formatCredits(amount, 2, true);
return (
@@ -31,7 +33,7 @@ function PostViewer(props: Props) {
-
+
diff --git a/ui/component/publishForm/view.jsx b/ui/component/publishForm/view.jsx
index 167298c84..27f7649d9 100644
--- a/ui/component/publishForm/view.jsx
+++ b/ui/component/publishForm/view.jsx
@@ -25,7 +25,6 @@ import SelectThumbnail from 'component/selectThumbnail';
import Card from 'component/common/card';
import I18nMessage from 'component/i18nMessage';
import * as PUBLISH_MODES from 'constants/publish_types';
-import { useHistory } from 'react-router';
// @if TARGET='app'
import fs from 'fs';
@@ -90,19 +89,13 @@ type Props = {
};
function PublishForm(props: Props) {
- // Detect upload type from query in URL
- const urlParams = new URLSearchParams(location.search);
- const uploadType = urlParams.get('type');
- const history = useHistory();
-
- // Component state
- const [mode, setMode] = React.useState(uploadType || PUBLISH_MODES.FILE);
+ const [mode, setMode] = React.useState(PUBLISH_MODES.FILE);
const [autoSwitchMode, setAutoSwitchMode] = React.useState(true);
- // Used to check if the url name has changed:
+ // Used to checl if the url name has changed:
// A new file needs to be provided
const [prevName, setPrevName] = React.useState(false);
- // Used to check if the file has been modified by user
+ // Used to checl if the file has been modified by user
const [fileEdited, setFileEdited] = React.useState(false);
const [prevFileText, setPrevFileText] = React.useState('');
@@ -252,42 +245,6 @@ function PublishForm(props: Props) {
}
}, [activeChannelName, incognito, updatePublishForm]);
- useEffect(() => {
- const _uploadType = uploadType && uploadType.toLowerCase();
-
- // Default to standard file publish if none specified
- if (!_uploadType) {
- setMode(PUBLISH_MODES.FILE);
- return;
- }
-
- // File publish
- if (_uploadType === PUBLISH_MODES.FILE.toLowerCase()) {
- setMode(PUBLISH_MODES.FILE);
- return;
- }
- // Post publish
- if (_uploadType === PUBLISH_MODES.POST.toLowerCase()) {
- setMode(PUBLISH_MODES.POST);
- return;
- }
- // LiveStream publish
- if (_uploadType === PUBLISH_MODES.LIVESTREAM.toLowerCase()) {
- setMode(PUBLISH_MODES.LIVESTREAM);
- return;
- }
-
- // Default to standard file publish
- setMode(PUBLISH_MODES.FILE);
- }, [uploadType]);
-
- useEffect(() => {
- if (!uploadType) return;
- const newParams = new URLSearchParams();
- newParams.set('type', mode.toLowerCase());
- history.push({search: newParams.toString()});
- }, [mode, uploadType]);
-
// @if TARGET='web'
function createWebFile() {
if (fileText) {
diff --git a/ui/component/repostCreate/index.js b/ui/component/repostCreate/index.js
index 07b5c5426..4b142e8b9 100644
--- a/ui/component/repostCreate/index.js
+++ b/ui/component/repostCreate/index.js
@@ -17,7 +17,7 @@ import {
selectFetchingMyChannels,
} from 'lbry-redux';
import { doToast } from 'redux/actions/notifications';
-import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
+import { selectActiveChannelClaim } from 'redux/selectors/app';
import RepostCreate from './view';
const select = (state, props) => ({
@@ -37,7 +37,6 @@ const select = (state, props) => ({
isResolvingEnteredRepost: props.repostUri && makeSelectIsUriResolving(`lbry://${props.repostUri}`)(state),
activeChannelClaim: selectActiveChannelClaim(state),
fetchingMyChannels: selectFetchingMyChannels(state),
- incognito: selectIncognito(state),
});
export default connect(select, {
diff --git a/ui/component/repostCreate/view.jsx b/ui/component/repostCreate/view.jsx
index dfda5ae04..f61394440 100644
--- a/ui/component/repostCreate/view.jsx
+++ b/ui/component/repostCreate/view.jsx
@@ -42,7 +42,6 @@ type Props = {
isResolvingEnteredRepost: boolean,
activeChannelClaim: ?ChannelClaim,
fetchingMyChannels: boolean,
- incognito: boolean,
};
function RepostCreate(props: Props) {
@@ -68,7 +67,6 @@ function RepostCreate(props: Props) {
isResolvingEnteredRepost,
activeChannelClaim,
fetchingMyChannels,
- incognito,
} = props;
const defaultName = name || (claim && claim.name) || '';
@@ -283,7 +281,7 @@ function RepostCreate(props: Props) {
doRepost({
name: enteredRepostName,
bid: creditsToString(repostBid),
- channel_id: activeChannelClaim && !incognito ? activeChannelClaim.claim_id : undefined,
+ channel_id: activeChannelClaim ? activeChannelClaim.claim_id : undefined,
claim_id: repostClaimId,
}).then((repostClaim: StreamClaim) => {
doCheckPendingClaims();
diff --git a/ui/component/router/view.jsx b/ui/component/router/view.jsx
index 056127b7a..ac3d58ee6 100644
--- a/ui/component/router/view.jsx
+++ b/ui/component/router/view.jsx
@@ -1,59 +1,63 @@
// @flow
-import * as PAGES from 'constants/pages';
-import React, { useEffect } from 'react';
+import React, { useEffect, Suspense, lazy } from 'react';
import { Route, Redirect, Switch, withRouter } from 'react-router-dom';
-import SettingsPage from 'page/settings';
-import SettingsNotificationsPage from 'page/settingsNotifications';
-import SettingsAdvancedPage from 'page/settingsAdvanced';
-import HelpPage from 'page/help';
+
// @if TARGET='app'
import BackupPage from 'page/backup';
// @endif
// @if TARGET='web'
import Code2257Page from 'web/page/code2257';
// @endif
-import ReportPage from 'page/report';
-import ShowPage from 'page/show';
-import PublishPage from 'page/publish';
-import DiscoverPage from 'page/discover';
-import HomePage from 'page/home';
-import InvitedPage from 'page/invited';
-import RewardsPage from 'page/rewards';
-import FileListPublished from 'page/fileListPublished';
-import InvitePage from 'page/invite';
-import SearchPage from 'page/search';
-import LibraryPage from 'page/library';
-import WalletPage from 'page/wallet';
-import TagsFollowingPage from 'page/tagsFollowing';
-import ChannelsFollowingPage from 'page/channelsFollowing';
-import ChannelsFollowingDiscoverPage from 'page/channelsFollowingDiscover';
-import TagsFollowingManagePage from 'page/tagsFollowingManage';
-import ListBlockedPage from 'page/listBlocked';
-import FourOhFourPage from 'page/fourOhFour';
-import SignInPage from 'page/signIn';
-import SignUpPage from 'page/signUp';
-import PasswordResetPage from 'page/passwordReset';
-import PasswordSetPage from 'page/passwordSet';
-import SignInVerifyPage from 'page/signInVerify';
-import ChannelsPage from 'page/channels';
-import LiveStreamSetupPage from 'page/livestreamSetup';
-import EmbedWrapperPage from 'page/embedWrapper';
-import TopPage from 'page/top';
-import Welcome from 'page/welcome';
-import CreatorDashboard from 'page/creatorDashboard';
-import RewardsVerifyPage from 'page/rewardsVerify';
-import CheckoutPage from 'page/checkoutPage';
-import ChannelNew from 'page/channelNew';
-import RepostNew from 'page/repost';
-import BuyPage from 'page/buy';
-import NotificationsPage from 'page/notifications';
-import SignInWalletPasswordPage from 'page/signInWalletPassword';
-import YoutubeSyncPage from 'page/youtubeSync';
+// constants
+import * as PAGES from 'constants/pages';
import { LINKED_COMMENT_QUERY_PARAM } from 'constants/comment';
import { parseURI, isURIValid } from 'lbry-redux';
import { SITE_TITLE, WELCOME_VERSION } from 'config';
+// Code Splitting
+const BuyPage = lazy(() => import('page/buy'));
+const ChannelsPage = lazy(() => import('page/channels'));
+const ChannelsFollowingPage = lazy(() => import('page/channelsFollowing'));
+const ChannelsFollowingDiscoverPage = lazy(() => import('page/channelsFollowingDiscover'));
+const CreatorDashboard = lazy(() => import('page/creatorDashboard'));
+const CheckoutPage = lazy(() => import('page/checkoutPage'));
+const ChannelNew = lazy(() => import('page/channelNew'));
+const DiscoverPage = lazy(() => import('page/discover'));
+const EmbedWrapperPage = lazy(() => import('page/embedWrapper'));
+const FileListPublished = lazy(() => import('page/fileListPublished'));
+const FourOhFourPage = lazy(() => import('page/fourOhFour'));
+const HelpPage = lazy(() => import('page/help'));
+const HomePage = lazy(() => import('page/home'));
+const InvitedPage = lazy(() => import('page/invited'));
+const InvitePage = lazy(() => import('page/invite'));
+const LiveStreamPage = lazy(() => import('page/livestream'));
+const LibraryPage = lazy(() => import('page/library'));
+const ListBlockedPage = lazy(() => import('page/listBlocked'));
+const NotificationsPage = lazy(() => import('page/notifications'));
+const PasswordResetPage = lazy(() => import('page/passwordReset'));
+const PasswordSetPage = lazy(() => import('page/passwordSet'));
+const PublishPage = lazy(() => import('page/publish'));
+const RewardsPage = lazy(() => import('page/rewards'));
+const RewardsVerifyPage = lazy(() => import('page/rewardsVerify'));
+const ReportPage = lazy(() => import('page/report'));
+const RepostNew = lazy(() => import('page/repost'));
+const SignInPage = lazy(() => import('page/signIn'));
+const SignUpPage = lazy(() => import('page/signUp'));
+const SignInVerifyPage = lazy(() => import('page/signInVerify'));
+const SearchPage = lazy(() => import('page/search'));
+const SettingsPage = lazy(() => import('page/settings'));
+const SettingsNotificationsPage = lazy(() => import('page/settingsNotifications'));
+const SettingsAdvancedPage = lazy(() => import('page/settingsAdvanced'));
+const SignInWalletPasswordPage = lazy(() => import('page/signInWalletPassword'));
+const ShowPage = lazy(() => import('page/show'));
+const TagsFollowingPage = lazy(() => import('page/tagsFollowing'));
+const TagsFollowingManagePage = lazy(() => import('page/tagsFollowingManage'));
+const TopPage = lazy(() => import('page/top'));
+const WalletPage = lazy(() => import('page/wallet'));
+const Welcome = lazy(() => import('page/welcome'));
+const YoutubeSyncPage = lazy(() => import('page/youtubeSync'));
+
// Tell the browser we are handling scroll restoration
if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual';
@@ -92,7 +96,12 @@ type PrivateRouteProps = Props & {
};
function PrivateRoute(props: PrivateRouteProps) {
- const { component: Component, isAuthenticated, ...rest } = props;
+ const {
+ component: Component,
+ isAuthenticated,
+ ...rest
+ } = props;
+
return (
- {/* @if TARGET='app' */}
- {welcomeVersion < WELCOME_VERSION && }
- {/* @endif */}
-
-
-
-
-
-
-
-
-
- {/* $FlowFixMe */}
- {dynamicRoutes.map((dynamicRouteProps: RowDataItem) => (
- }
+ Loading... }>
+
+ {/* @if TARGET='app' */}
+ {welcomeVersion < WELCOME_VERSION && }
+ {/* @endif */}
+
- ))}
+
+
+
+
+
-
-
-
-
-
-
+
+
+ {/* $FlowFixMe */}
+ {dynamicRoutes.map((dynamicRouteProps: RowDataItem) => (
+ }
+ />
+ ))}
-
- {/* @if TARGET='app' */}
-
- {/* @endif */}
- {/* @if TARGET='web' */}
-
- {/* @endif */}
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ {/* @if TARGET='app' */}
+
+ {/* @endif */}
+ {/* @if TARGET='web' */}
+
+ {/* @endif */}
+
+
+
+
+
+
+
-
-
- {/* Below need to go at the end to make sure we don't match any of our pages first */}
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Below need to go at the end to make sure we don't match any of our pages first */}
+
+
+
+
+
);
}
diff --git a/ui/component/sideNavigation/index.js b/ui/component/sideNavigation/index.js
index 6eda01389..820fc33b4 100644
--- a/ui/component/sideNavigation/index.js
+++ b/ui/component/sideNavigation/index.js
@@ -5,17 +5,17 @@ import { selectFollowedTags } from 'redux/selectors/tags';
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
import { selectHomepageData, selectLanguage } from 'redux/selectors/settings';
import { doSignOut } from 'redux/actions/app';
-import { selectUnseenNotificationCount } from 'redux/selectors/notifications';
+import { selectUnreadNotificationCount } from 'redux/selectors/notifications';
import SideNavigation from './view';
-const select = (state) => ({
+const select = state => ({
subscriptions: selectSubscriptions(state),
followedTags: selectFollowedTags(state),
language: selectLanguage(state), // trigger redraw on language change
email: selectUserVerifiedEmail(state),
purchaseSuccess: selectPurchaseUriSuccess(state),
- unseenCount: selectUnseenNotificationCount(state),
+ unreadCount: selectUnreadNotificationCount(state),
user: selectUser(state),
homepageData: selectHomepageData(state),
});
diff --git a/ui/component/sideNavigation/view.jsx b/ui/component/sideNavigation/view.jsx
index cd9993448..3b849a355 100644
--- a/ui/component/sideNavigation/view.jsx
+++ b/ui/component/sideNavigation/view.jsx
@@ -38,10 +38,10 @@ type Props = {
uploadCount: number,
doSignOut: () => void,
sidebarOpen: boolean,
- setSidebarOpen: (boolean) => void,
+ setSidebarOpen: boolean => void,
isMediumScreen: boolean,
isOnFilePage: boolean,
- unseenCount: number,
+ unreadCount: number,
purchaseSuccess: boolean,
doClearPurchasedUriSuccess: () => void,
user: ?User,
@@ -69,7 +69,7 @@ function SideNavigation(props: Props) {
setSidebarOpen,
isMediumScreen,
isOnFilePage,
- unseenCount,
+ unreadCount,
homepageData,
user,
} = props;
@@ -232,7 +232,7 @@ function SideNavigation(props: Props) {
const isAbsolute = isOnFilePage || isMediumScreen;
const microNavigation = !sidebarOpen || isMediumScreen;
const subLinks = email
- ? MOBILE_LINKS.filter((link) => {
+ ? MOBILE_LINKS.filter(link => {
if (!notificationsEnabled && link.icon === ICONS.NOTIFICATION) {
return false;
}
@@ -320,7 +320,7 @@ function SideNavigation(props: Props) {
>
- {SIDE_LINKS.map((linkProps) => {
+ {SIDE_LINKS.map(linkProps => {
// $FlowFixMe
const { hideForUnauth, ...passedProps } = linkProps;
return !email && linkProps.hideForUnauth && IS_WEB ? null : (
@@ -334,7 +334,7 @@ function SideNavigation(props: Props) {
icon={pulseLibrary && linkProps.icon === ICONS.LIBRARY ? ICONS.PURCHASED : linkProps.icon}
className={classnames('navigation-link', {
'navigation-link--pulse': linkProps.icon === ICONS.LIBRARY && pulseLibrary,
- 'navigation-link--highlighted': linkProps.icon === ICONS.NOTIFICATION && unseenCount > 0,
+ 'navigation-link--highlighted': linkProps.icon === ICONS.NOTIFICATION && unreadCount > 0,
})}
activeClass="navigation-link--active"
/>
@@ -386,7 +386,7 @@ function SideNavigation(props: Props) {
>
- {SIDE_LINKS.map((linkProps) => {
+ {SIDE_LINKS.map(linkProps => {
// $FlowFixMe
const { hideForUnauth, link, route, ...passedProps } = linkProps;
return !email && linkProps.hideForUnauth && IS_WEB ? null : (
@@ -399,7 +399,7 @@ function SideNavigation(props: Props) {
icon={pulseLibrary && linkProps.icon === ICONS.LIBRARY ? ICONS.PURCHASED : linkProps.icon}
className={classnames('navigation-link', {
'navigation-link--pulse': linkProps.icon === ICONS.LIBRARY && pulseLibrary,
- 'navigation-link--highlighted': linkProps.icon === ICONS.NOTIFICATION && unseenCount > 0,
+ 'navigation-link--highlighted': linkProps.icon === ICONS.NOTIFICATION && unreadCount > 0,
})}
activeClass="navigation-link--active"
/>
@@ -409,7 +409,7 @@ function SideNavigation(props: Props) {
})}
- {subLinks.map((linkProps) => {
+ {subLinks.map(linkProps => {
const { hideForUnauth, ...passedProps } = linkProps;
return !email && hideForUnauth && IS_WEB ? null : (
diff --git a/ui/component/splash/view.jsx b/ui/component/splash/view.jsx
index c48251a27..73a7b08ad 100644
--- a/ui/component/splash/view.jsx
+++ b/ui/component/splash/view.jsx
@@ -30,7 +30,7 @@ type Props = {
animationHidden: boolean,
toggleSplashAnimation: () => void,
clearWalletServers: () => void,
- doShowSnackBar: (string) => void,
+ doShowSnackBar: string => void,
};
type State = {
@@ -109,30 +109,30 @@ export default class SplashScreen extends React.PureComponent {
const { modal, notifyUnlockWallet, clearWalletServers, doShowSnackBar } = this.props;
const { launchedModal } = this.state;
- Lbry.status().then((status) => {
+ Lbry.status().then(status => {
const sdkStatus = status;
const { wallet } = status;
- Lbry.wallet_status().then((walletStatus) => {
+ Lbry.wallet_status().then(status => {
if (sdkStatus.is_running && wallet && wallet.available_servers) {
- if (walletStatus.is_locked) {
+ if (status.is_locked) {
// Clear the error timeout, it might sit on this step for a while until someone enters their password
if (this.timeout) {
clearTimeout(this.timeout);
}
// Make sure there isn't another active modal (like INCOMPATIBLE_DAEMON)
- this.updateStatusCallback(sdkStatus, walletStatus, true);
+ this.updateStatusCallback(sdkStatus, status, true);
if (launchedModal === false && !modal) {
this.setState({ launchedModal: true }, () => notifyUnlockWallet());
}
} else {
- this.updateStatusCallback(sdkStatus, walletStatus);
+ this.updateStatusCallback(sdkStatus, status);
}
- } else if (!sdkStatus.is_running && walletStatus.is_syncing) {
+ } else if (!sdkStatus.is_running && status.is_syncing) {
// Clear the timeout if wallet is still syncing
if (this.timeout) {
clearTimeout(this.timeout);
}
- this.updateStatusCallback(sdkStatus, walletStatus);
+ this.updateStatusCallback(sdkStatus, status);
} else if (this.state.waitingForWallet > MAX_WALLET_WAIT && launchedModal === false && !modal) {
clearWalletServers();
doShowSnackBar(
@@ -141,15 +141,20 @@ export default class SplashScreen extends React.PureComponent {
)
);
this.setState({ waitingForWallet: 0 });
- this.updateStatusCallback(sdkStatus, walletStatus);
+ this.updateStatusCallback(sdkStatus, status);
} else {
- this.updateStatusCallback(sdkStatus, walletStatus);
+ this.updateStatusCallback(sdkStatus, status);
}
});
});
}
updateStatusCallback(status: StatusResponse, walletStatus: WalletStatusResponse, waitingForUnlock: boolean = false) {
+ if (status.connection_status.code !== 'connected') {
+ this.setState({ error: true });
+ return;
+ }
+
const { wallet, startup_status: startupStatus } = status;
// If the wallet is locked, stop doing anything and make the user input their password
diff --git a/ui/component/syncFatalError/view.jsx b/ui/component/syncFatalError/view.jsx
index ad8f83d1f..c5777ffe0 100644
--- a/ui/component/syncFatalError/view.jsx
+++ b/ui/component/syncFatalError/view.jsx
@@ -3,10 +3,8 @@ import * as ICONS from 'constants/icons';
import React from 'react';
import Button from 'component/button';
import Yrbl from 'component/yrbl';
-import { SITE_HELP_EMAIL } from 'config';
-// @if TARGET='web'
import { STATUS_DEGRADED, STATUS_FAILING, STATUS_DOWN } from 'web/effects/use-degraded-performance';
-// @endif
+import { SITE_HELP_EMAIL } from 'config';
type Props = {
lbryTvApiStatus: string,
@@ -15,13 +13,9 @@ type Props = {
export default function SyncFatalError(props: Props) {
const { lbryTvApiStatus } = props;
- let downTime = false;
-
- // @if TARGET='web'
- downTime =
+ const downTime =
IS_WEB &&
(lbryTvApiStatus === STATUS_DEGRADED || lbryTvApiStatus === STATUS_FAILING || lbryTvApiStatus === STATUS_DOWN);
- // @endif
return (
diff --git a/ui/component/viewers/videoViewer/internal/videojs.jsx b/ui/component/viewers/videoViewer/internal/videojs.jsx
index c35e19089..a9d6664e3 100644
--- a/ui/component/viewers/videoViewer/internal/videojs.jsx
+++ b/ui/component/viewers/videoViewer/internal/videojs.jsx
@@ -31,7 +31,6 @@ export type Player = {
loadingSpinner: any,
getChild: (string) => any,
playbackRate: (?number) => number,
- readyState: () => number,
userActive: (?boolean) => boolean,
overlay: (any) => void,
mobileUi: (any) => void,
@@ -453,7 +452,7 @@ export default React.memo
(function VideoJs(props: Props) {
return vjs;
}
- // This lifecycle hook is only called once (on mount), or when `isAudio` changes.
+ // This lifecycle hook is only called once (on mount)
useEffect(() => {
const vjsElement = createVideoPlayerDOM(containerRef.current);
const vjsPlayer = initializeVideoPlayer(vjsElement);
@@ -477,7 +476,7 @@ export default React.memo(function VideoJs(props: Props) {
player.dispose();
}
};
- }, [isAudio]);
+ }, []);
// Update video player and reload when source URL changes
useEffect(() => {
diff --git a/ui/component/viewers/videoViewer/view.jsx b/ui/component/viewers/videoViewer/view.jsx
index 75a2ffda0..b446f0c73 100644
--- a/ui/component/viewers/videoViewer/view.jsx
+++ b/ui/component/viewers/videoViewer/view.jsx
@@ -19,8 +19,8 @@ const PLAY_TIMEOUT_LIMIT = 2000;
type Props = {
position: number,
- changeVolume: (number) => void,
- changeMute: (boolean) => void,
+ changeVolume: number => void,
+ changeMute: boolean => void,
source: string,
contentType: string,
thumbnail: string,
@@ -36,9 +36,9 @@ type Props = {
doAnalyticsBuffer: (string, any) => void,
claimRewards: () => void,
savePosition: (string, number) => void,
- clearPosition: (string) => void,
+ clearPosition: string => void,
toggleVideoTheaterMode: () => void,
- setVideoPlaybackRate: (number) => void,
+ setVideoPlaybackRate: number => void,
};
/*
@@ -76,7 +76,6 @@ function VideoViewer(props: Props) {
const [isPlaying, setIsPlaying] = useState(false);
const [showAutoplayCountdown, setShowAutoplayCountdown] = useState(false);
const [isEndededEmbed, setIsEndededEmbed] = useState(false);
- const vjsCallbackDataRef: any = React.useRef();
/* isLoading was designed to show loading screen on first play press, rather than completely black screen, but
breaks because some browsers (e.g. Firefox) block autoplay but leave the player.play Promise pending */
@@ -94,16 +93,8 @@ function VideoViewer(props: Props) {
}
}, [uri, previousUri]);
- // Update vjsCallbackDataRef (ensures videojs callbacks are not using stale values):
- useEffect(() => {
- vjsCallbackDataRef.current = {
- embedded: embedded,
- videoPlaybackRate: videoPlaybackRate,
- };
- }, [embedded, videoPlaybackRate]);
-
function doTrackingBuffered(e: Event, data: any) {
- fetch(source, { method: 'HEAD' }).then((response) => {
+ fetch(source, { method: 'HEAD' }).then(response => {
data.playerPoweredBy = response.headers.get('x-powered-by');
doAnalyticsBuffer(uri, data);
});
@@ -146,12 +137,6 @@ function VideoViewer(props: Props) {
}
}
- function restorePlaybackRate(player) {
- if (!vjsCallbackDataRef.current.embedded) {
- player.playbackRate(vjsCallbackDataRef.current.videoPlaybackRate);
- }
- }
-
const onPlayerReady = useCallback(
(player: Player) => {
if (!embedded) {
@@ -165,14 +150,12 @@ function VideoViewer(props: Props) {
// https://blog.videojs.com/autoplay-best-practices-with-video-js/#Programmatic-Autoplay-and-Success-Failure-Detection
if (shouldPlay) {
const playPromise = player.play();
- const timeoutPromise = new Promise((resolve, reject) =>
- setTimeout(() => reject(PLAY_TIMEOUT_ERROR), PLAY_TIMEOUT_LIMIT)
- );
+ const timeoutPromise = new Promise((resolve, reject) => setTimeout(() => reject(PLAY_TIMEOUT_ERROR), PLAY_TIMEOUT_LIMIT));
- Promise.race([playPromise, timeoutPromise]).catch((error) => {
+ Promise.race([playPromise, timeoutPromise]).catch(error => {
if (PLAY_TIMEOUT_ERROR) {
const retryPlayPromise = player.play();
- Promise.race([retryPlayPromise, timeoutPromise]).catch((error) => {
+ Promise.race([retryPlayPromise, timeoutPromise]).catch(error => {
setIsLoading(false);
setIsPlaying(false);
});
@@ -184,13 +167,6 @@ function VideoViewer(props: Props) {
}
setIsLoading(shouldPlay); // if we are here outside of an embed, we're playing
-
- // PR: #5535
- // Move the restoration to a later `loadedmetadata` phase to counter the
- // delay from the header-fetch. This is a temp change until the next
- // re-factoring.
- player.on('loadedmetadata', () => restorePlaybackRate(player));
-
player.on('tracking:buffered', doTrackingBuffered);
player.on('tracking:firstplay', doTrackingFirstPlay);
player.on('ended', onEnded);
@@ -199,8 +175,9 @@ function VideoViewer(props: Props) {
setIsPlaying(false);
handlePosition(player);
});
- player.on('error', () => {
+ player.on('error', function() {
const error = player.error();
+
if (error) {
analytics.sentryError('Video.js error', error);
}
@@ -212,12 +189,7 @@ function VideoViewer(props: Props) {
}
});
player.on('ratechange', () => {
- const HAVE_NOTHING = 0; // https://docs.videojs.com/player#readyState
- if (player && player.readyState() !== HAVE_NOTHING) {
- // The playbackRate occasionally resets to 1, typically when loading a fresh video or when 'src' changes.
- // Videojs says it's a browser quirk (https://github.com/videojs/video.js/issues/2516).
- // [x] Don't update 'videoPlaybackRate' in this scenario.
- // [ ] Ideally, the controlBar should be hidden to prevent users from changing the rate while loading.
+ if (player) {
setVideoPlaybackRate(player.playbackRate());
}
});
diff --git a/ui/page/fileListPublished/index.js b/ui/page/fileListPublished/index.js
index 3154874a8..bf09f976b 100644
--- a/ui/page/fileListPublished/index.js
+++ b/ui/page/fileListPublished/index.js
@@ -30,10 +30,9 @@ const select = (state, props) => {
};
};
-const perform = (dispatch) => ({
+const perform = dispatch => ({
checkPendingPublishes: () => dispatch(doCheckPendingClaims()),
- fetchClaimListMine: (page, pageSize, resolve, filterBy) =>
- dispatch(doFetchClaimListMine(page, pageSize, resolve, filterBy)),
+ fetchClaimListMine: (page, pageSize) => dispatch(doFetchClaimListMine(page, pageSize)),
clearPublish: () => dispatch(doClearPublish()),
});
diff --git a/ui/page/fileListPublished/view.jsx b/ui/page/fileListPublished/view.jsx
index c87ea8634..ca7b802c6 100644
--- a/ui/page/fileListPublished/view.jsx
+++ b/ui/page/fileListPublished/view.jsx
@@ -10,21 +10,16 @@ import { PAGE_PARAM, PAGE_SIZE_PARAM } from 'constants/claim';
import WebUploadList from 'component/webUploadList';
import Spinner from 'component/spinner';
import Yrbl from 'component/yrbl';
-import classnames from 'classnames';
-
-const FILTER_ALL = 'stream,repost';
-const FILTER_UPLOADS = 'stream';
-const FILTER_REPOSTS = 'repost';
type Props = {
uploadCount: number,
checkPendingPublishes: () => void,
clearPublish: () => void,
- fetchClaimListMine: (number, number, boolean, Array) => void,
+ fetchClaimListMine: (number, number) => void,
fetching: boolean,
urls: Array,
urlTotal: number,
- history: { replace: (string) => void, push: (string) => void },
+ history: { replace: string => void, push: string => void },
page: number,
pageSize: number,
};
@@ -42,7 +37,6 @@ function FileListPublished(props: Props) {
pageSize,
} = props;
- const [filterBy, setFilterBy] = React.useState(FILTER_ALL);
const params = {};
params[PAGE_PARAM] = Number(page);
@@ -57,9 +51,9 @@ function FileListPublished(props: Props) {
useEffect(() => {
if (paramsString && fetchClaimListMine) {
const params = JSON.parse(paramsString);
- fetchClaimListMine(params.page, params.page_size, true, filterBy.split(','));
+ fetchClaimListMine(params.page, params.page_size);
}
- }, [uploadCount, paramsString, filterBy, fetchClaimListMine]);
+ }, [uploadCount, paramsString, fetchClaimListMine]);
return (
@@ -68,35 +62,7 @@ function FileListPublished(props: Props) {
{!!(urls && urls.length) && (
<>
- setFilterBy(FILTER_ALL)}
- className={classnames(`button-toggle`, {
- 'button-toggle--active': filterBy === FILTER_ALL,
- })}
- />
- setFilterBy(FILTER_UPLOADS)}
- className={classnames(`button-toggle`, {
- 'button-toggle--active': filterBy === FILTER_UPLOADS,
- })}
- />
- setFilterBy(FILTER_REPOSTS)}
- className={classnames(`button-toggle`, {
- 'button-toggle--active': filterBy === FILTER_REPOSTS,
- })}
- />
-
- }
+ header={{__('Uploads')} }
headerAltControls={
{fetching && }
@@ -105,7 +71,7 @@ function FileListPublished(props: Props) {
button="alt"
label={__('Refresh')}
icon={ICONS.REFRESH}
- onClick={() => fetchClaimListMine(params.page, params.page_size, true, filterBy.split(','))}
+ onClick={() => fetchClaimListMine(params.page, params.page_size)}
/>
)}
}
+ loading={fetching}
persistedStorageKey="claim-list-published"
uris={urls}
/>
diff --git a/ui/page/livestream/index.js b/ui/page/livestream/index.js
index 0f8751458..d5c4f515f 100644
--- a/ui/page/livestream/index.js
+++ b/ui/page/livestream/index.js
@@ -1,18 +1,13 @@
import { connect } from 'react-redux';
-import { doResolveUri } from 'lbry-redux';
-import { doSetPlayingUri } from 'redux/actions/content';
-import { doUserSetReferrer } from 'redux/actions/user';
-import { selectUserVerifiedEmail } from 'redux/selectors/user';
-import { selectHasUnclaimedRefereeReward } from 'redux/selectors/rewards';
-import LivestreamPage from './view';
+import { selectMyChannelClaims, selectFetchingMyChannels } from 'lbry-redux';
+import { selectActiveChannelClaim } from 'redux/selectors/app';
+import { doSetActiveChannel } from 'redux/actions/app';
+import CreatorDashboardPage from './view';
const select = (state) => ({
- hasUnclaimedRefereeReward: selectHasUnclaimedRefereeReward(state),
- isAuthenticated: selectUserVerifiedEmail(state),
+ channels: selectMyChannelClaims(state),
+ fetchingChannels: selectFetchingMyChannels(state),
+ activeChannelClaim: selectActiveChannelClaim(state),
});
-export default connect(select, {
- doSetPlayingUri,
- doResolveUri,
- doUserSetReferrer,
-})(LivestreamPage);
+export default connect(select, { doSetActiveChannel })(CreatorDashboardPage);
diff --git a/ui/page/livestream/view.jsx b/ui/page/livestream/view.jsx
index 83f271598..251e5aaa7 100644
--- a/ui/page/livestream/view.jsx
+++ b/ui/page/livestream/view.jsx
@@ -1,114 +1,240 @@
// @flow
-// import { LIVE_STREAM_TAG, BITWAVE_API } from 'constants/livestream';
+import * as PAGES from 'constants/pages';
import React from 'react';
import Page from 'component/page';
-import LivestreamLayout from 'component/livestreamLayout';
-import analytics from 'analytics';
+import Spinner from 'component/spinner';
+import Button from 'component/button';
+import ChannelSelector from 'component/channelSelector';
+import Yrbl from 'component/yrbl';
+import { Lbry } from 'lbry-redux';
+import { toHex } from '../../util/hex';
+import ClaimPreview from '../../component/claimPreview';
+import { FormField } from '../../component/common/form';
+import * as PUBLISH_MODES from '../../constants/publish_types';
type Props = {
- uri: string,
- claim: StreamClaim,
- doSetPlayingUri: ({ uri: ?string }) => void,
- isAuthenticated: boolean,
- doUserSetReferrer: (string) => void,
+ channels: Array,
+ fetchingChannels: boolean,
+ activeChannelClaim: ?ChannelClaim,
};
-export default function LivestreamPage(props: Props) {
- const { uri, claim, doSetPlayingUri, isAuthenticated, doUserSetReferrer } = props;
- const [activeViewers, setActiveViewers] = React.useState(0);
+export default function CreatorDashboardPage(props: Props) {
+ const { channels, fetchingChannels, activeChannelClaim } = props;
+
+ const [sigData, setSigData] = React.useState({ signature: undefined, signing_ts: undefined });
+
+ const hasChannels = channels && channels.length > 0;
+ const activeChannelClaimStr = JSON.stringify(activeChannelClaim);
+ const streamKey = createStreamKey();
React.useEffect(() => {
- // function checkIsLive() {
- // fetch(`${BITWAVE_API}/`)
- // .then((res) => res.json())
- // .then((res) => {
- // if (!res || !res.data) {
- // setIsLive(false);
- // return;
- // }
- // setActiveViewers(res.data.viewCount);
- // if (res.data.live) {
- // setDisplayCountdown(false);
- // setIsLive(true);
- // setIsFetching(false);
- // return;
- // }
- // // Not live, but see if we can display the countdown;
- // const scheduledTime = res.data.scheduled;
- // if (scheduledTime) {
- // const scheduledDate = new Date(scheduledTime);
- // const lastLiveTimestamp = res.data.timestamp;
- // let isLivestreamOver = false;
- // if (lastLiveTimestamp) {
- // const timestampDate = new Date(lastLiveTimestamp);
- // isLivestreamOver = timestampDate.getTime() > scheduledDate.getTime();
- // }
- // if (isLivestreamOver) {
- // setDisplayCountdown(false);
- // setIsLive(false);
- // } else {
- // const datePlusTenMinuteBuffer = scheduledDate.setMinutes(10, 0, 0);
- // const isInFuture = Date.now() < datePlusTenMinuteBuffer;
- // if (isInFuture) {
- // setDisplayCountdown(true);
- // setIsLive(false);
- // } else {
- // setDisplayCountdown(false);
- // setIsLive(false);
- // }
- // }
- // setIsFetching(false);
- // } else {
- // // Offline and no countdown happening
- // setIsLive(false);
- // setDisplayCountdown(false);
- // setActiveViewers(0);
- // setIsFetching(false);
- // }
- // });
- // }
- // let interval;
- // checkIsLive();
- // if (uri) {
- // interval = setInterval(checkIsLive, 10000);
- // }
- // return () => {
- // if (interval) {
- // clearInterval(interval);
- // }
- // };
- }, [uri]);
+ if (activeChannelClaimStr) {
+ const channelClaim = JSON.parse(activeChannelClaimStr);
- const stringifiedClaim = JSON.stringify(claim);
- React.useEffect(() => {
- if (uri && stringifiedClaim) {
- const jsonClaim = JSON.parse(stringifiedClaim);
-
- if (jsonClaim) {
- const { txid, nout, claim_id: claimId } = jsonClaim;
- const outpoint = `${txid}:${nout}`;
-
- analytics.apiLogView(uri, outpoint, claimId);
- }
-
- if (!isAuthenticated) {
- const uri = jsonClaim.signing_channel && jsonClaim.signing_channel.permanent_url;
- if (uri) {
- doUserSetReferrer(uri.replace('lbry://', ''));
- }
+ // ensure we have a channel
+ if (channelClaim.claim_id) {
+ Lbry.channel_sign({
+ channel_id: channelClaim.claim_id,
+ hexdata: toHex(channelClaim.name),
+ })
+ .then((data) => {
+ console.log(data);
+ setSigData(data);
+ })
+ .catch((error) => {
+ setSigData({ signature: null, signing_ts: null });
+ console.error(error);
+ });
}
}
- }, [uri, stringifiedClaim, isAuthenticated]);
+ }, [ activeChannelClaimStr, setSigData ]);
+
+ function createStreamKey() {
+ if (!activeChannelClaim || !sigData.signature || !sigData.signing_ts) return null;
+ return `${activeChannelClaim.claim_id}?d=${toHex(activeChannelClaim.name)}&s=${sigData.signature}&t=${sigData.signing_ts}`;
+ }
+
+ /******/
+
+ const LIVE_STREAM_TAG = 'odysee-livestream';
+
+ const [isFetching, setIsFetching] = React.useState(true);
+ const [isLive, setIsLive] = React.useState(false);
+ const [livestreamClaim, setLivestreamClaim] = React.useState(false);
+ const [livestreamClaims, setLivestreamClaims] = React.useState([]);
React.useEffect(() => {
- // Set playing uri to null so the popout player doesnt start playing the dummy claim if a user navigates back
- // This can be removed when we start using the app video player, not a bitwave iframe
- doSetPlayingUri({ uri: null });
- }, [doSetPlayingUri]);
+ if (!activeChannelClaimStr) return;
+
+ const channelClaim = JSON.parse(activeChannelClaimStr);
+
+ Lbry.claim_search({
+ channel_ids: [channelClaim.claim_id],
+ any_tags: [LIVE_STREAM_TAG],
+ claim_type: ['stream'],
+ })
+ .then((res) => {
+ if (res && res.items && res.items.length > 0) {
+ const claim = res.items[res.items.length - 1];
+ setLivestreamClaim(claim);
+ setLivestreamClaims(res.items.reverse());
+ } else {
+ setIsFetching(false);
+ }
+ })
+ .catch(() => {
+ setIsFetching(false);
+ });
+ }, [activeChannelClaimStr]);
return (
-
-
+
+ {fetchingChannels && (
+
+
+
+ )}
+
+ {!fetchingChannels && !hasChannels && (
+
+
+
+ }
+ />
+ )}
+
+ {!fetchingChannels && activeChannelClaim && (
+
+ {/* Channel Selector */}
+
+
+ {/* Display StreamKey */}
+ { streamKey
+ ? (
+ {/* Stream Server Address */}
+
+
+ {/* Stream Key */}
+
+
)
+ : (
+
+
+ {JSON.stringify(activeChannelClaim)}
+
+ { sigData &&
+
+ {JSON.stringify(sigData)}
+
+ }
+
+ )
+ }
+
+ {/* Stream Claim(s) */}
+ { livestreamClaim && livestreamClaims ? (
+
+
Your LiveStream Claims
+
+ {livestreamClaims.map(claim => (
+
+ ))}
+
+ {/*Your LiveStream Claims
+ */}
+
+ ) : (
+
+
You must first publish a livestream claim before your stream will be visible!
+
+ {/* Relies on https://github.com/lbryio/lbry-desktop/pull/5669 */}
+
+
+
+ )}
+
+ {/* Debug Stuff */}
+ { streamKey && false && (
+
+
Debug Info
+
+ {/* Channel ID */}
+
+
+ {/* Signature */}
+
+
+ {/* Signature TS */}
+
+
+ {/* Hex Data */}
+
+
+ {/* Channel Public Key */}
+
+
+ )}
+
+ )}
);
}
diff --git a/ui/page/livestreamSetup/index.js b/ui/page/livestreamSetup/index.js
deleted file mode 100644
index 68bf859c1..000000000
--- a/ui/page/livestreamSetup/index.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { connect } from 'react-redux';
-import { selectMyChannelClaims, selectFetchingMyChannels } from 'lbry-redux';
-import { selectActiveChannelClaim } from 'redux/selectors/app';
-import LivestreamSetupPage from './view';
-
-const select = (state) => ({
- channels: selectMyChannelClaims(state),
- fetchingChannels: selectFetchingMyChannels(state),
- activeChannelClaim: selectActiveChannelClaim(state),
-});
-
-export default connect(select)(LivestreamSetupPage);
diff --git a/ui/page/livestreamSetup/view.jsx b/ui/page/livestreamSetup/view.jsx
deleted file mode 100644
index 792238dfc..000000000
--- a/ui/page/livestreamSetup/view.jsx
+++ /dev/null
@@ -1,221 +0,0 @@
-// @flow
-import * as PAGES from 'constants/pages';
-import * as PUBLISH_MODES from 'constants/publish_types';
-import React from 'react';
-import Page from 'component/page';
-import Spinner from 'component/spinner';
-import Button from 'component/button';
-import ChannelSelector from 'component/channelSelector';
-import Yrbl from 'component/yrbl';
-import { Lbry } from 'lbry-redux';
-import { toHex } from 'util/hex';
-import ClaimPreview from 'component/claimPreview';
-import { FormField } from 'component/common/form';
-import CopyableText from 'component/copyableText';
-import Card from 'component/common/card';
-import ClaimList from 'component/claimList';
-import livestream from '../livestream';
-
-type Props = {
- channels: Array,
- fetchingChannels: boolean,
- activeChannelClaim: ?ChannelClaim,
-};
-
-export default function LivestreamSetupPage(props: Props) {
- const { channels, fetchingChannels, activeChannelClaim } = props;
-
- const [sigData, setSigData] = React.useState({ signature: undefined, signing_ts: undefined });
-
- const hasChannels = channels && channels.length > 0;
- const activeChannelClaimStr = JSON.stringify(activeChannelClaim);
- const streamKey = createStreamKey();
-
- React.useEffect(() => {
- if (activeChannelClaimStr) {
- const channelClaim = JSON.parse(activeChannelClaimStr);
-
- // ensure we have a channel
- if (channelClaim.claim_id) {
- Lbry.channel_sign({
- channel_id: channelClaim.claim_id,
- hexdata: toHex(channelClaim.name),
- })
- .then((data) => {
- console.log(data);
- setSigData(data);
- })
- .catch((error) => {
- setSigData({ signature: null, signing_ts: null });
- console.error(error);
- });
- }
- }
- }, [activeChannelClaimStr, setSigData]);
-
- function createStreamKey() {
- if (!activeChannelClaim || !sigData.signature || !sigData.signing_ts) return null;
- return `${activeChannelClaim.claim_id}?d=${toHex(activeChannelClaim.name)}&s=${sigData.signature}&t=${
- sigData.signing_ts
- }`;
- }
-
- /******/
-
- const LIVE_STREAM_TAG = 'odysee-livestream';
-
- const [isFetching, setIsFetching] = React.useState(true);
- const [isLive, setIsLive] = React.useState(false);
- const [livestreamClaims, setLivestreamClaims] = React.useState([]);
-
- React.useEffect(() => {
- if (!activeChannelClaimStr) return;
-
- const channelClaim = JSON.parse(activeChannelClaimStr);
-
- Lbry.claim_search({
- channel_ids: [channelClaim.claim_id],
- any_tags: [LIVE_STREAM_TAG],
- claim_type: ['stream'],
- })
- .then((res) => {
- if (res && res.items && res.items.length > 0) {
- const claim = res.items[res.items.length - 1];
- setLivestreamClaims(res.items.reverse());
- } else {
- setIsFetching(false);
- }
- })
- .catch(() => {
- setIsFetching(false);
- });
- }, [activeChannelClaimStr]);
-
- return (
-
- {fetchingChannels && (
-
-
-
- )}
-
- {!fetchingChannels && !hasChannels && (
-
-
-
- }
- />
- )}
-
-
- {!fetchingChannels && activeChannelClaim && (
- <>
-
-
- {streamKey && livestreamClaims.length > 0 && (
-
-
-
- >
- }
- />
- )}
-
- {livestreamClaims.length > 0 ? (
- claim.permanent_url)}
- />
- ) : (
-
-
-
- }
- />
- )}
-
- {/* Debug Stuff */}
- {streamKey && false && (
-
-
Debug Info
-
- {/* Channel ID */}
-
-
- {/* Signature */}
-
-
- {/* Signature TS */}
-
-
- {/* Hex Data */}
-
-
- {/* Channel Public Key */}
-
-
- )}
- >
- )}
-
-
- );
-}
diff --git a/ui/page/livestreamStream/index.js b/ui/page/livestreamStream/index.js
new file mode 100644
index 000000000..0f8751458
--- /dev/null
+++ b/ui/page/livestreamStream/index.js
@@ -0,0 +1,18 @@
+import { connect } from 'react-redux';
+import { doResolveUri } from 'lbry-redux';
+import { doSetPlayingUri } from 'redux/actions/content';
+import { doUserSetReferrer } from 'redux/actions/user';
+import { selectUserVerifiedEmail } from 'redux/selectors/user';
+import { selectHasUnclaimedRefereeReward } from 'redux/selectors/rewards';
+import LivestreamPage from './view';
+
+const select = (state) => ({
+ hasUnclaimedRefereeReward: selectHasUnclaimedRefereeReward(state),
+ isAuthenticated: selectUserVerifiedEmail(state),
+});
+
+export default connect(select, {
+ doSetPlayingUri,
+ doResolveUri,
+ doUserSetReferrer,
+})(LivestreamPage);
diff --git a/ui/page/livestreamStream/view.jsx b/ui/page/livestreamStream/view.jsx
new file mode 100644
index 000000000..83f271598
--- /dev/null
+++ b/ui/page/livestreamStream/view.jsx
@@ -0,0 +1,114 @@
+// @flow
+// import { LIVE_STREAM_TAG, BITWAVE_API } from 'constants/livestream';
+import React from 'react';
+import Page from 'component/page';
+import LivestreamLayout from 'component/livestreamLayout';
+import analytics from 'analytics';
+
+type Props = {
+ uri: string,
+ claim: StreamClaim,
+ doSetPlayingUri: ({ uri: ?string }) => void,
+ isAuthenticated: boolean,
+ doUserSetReferrer: (string) => void,
+};
+
+export default function LivestreamPage(props: Props) {
+ const { uri, claim, doSetPlayingUri, isAuthenticated, doUserSetReferrer } = props;
+ const [activeViewers, setActiveViewers] = React.useState(0);
+
+ React.useEffect(() => {
+ // function checkIsLive() {
+ // fetch(`${BITWAVE_API}/`)
+ // .then((res) => res.json())
+ // .then((res) => {
+ // if (!res || !res.data) {
+ // setIsLive(false);
+ // return;
+ // }
+ // setActiveViewers(res.data.viewCount);
+ // if (res.data.live) {
+ // setDisplayCountdown(false);
+ // setIsLive(true);
+ // setIsFetching(false);
+ // return;
+ // }
+ // // Not live, but see if we can display the countdown;
+ // const scheduledTime = res.data.scheduled;
+ // if (scheduledTime) {
+ // const scheduledDate = new Date(scheduledTime);
+ // const lastLiveTimestamp = res.data.timestamp;
+ // let isLivestreamOver = false;
+ // if (lastLiveTimestamp) {
+ // const timestampDate = new Date(lastLiveTimestamp);
+ // isLivestreamOver = timestampDate.getTime() > scheduledDate.getTime();
+ // }
+ // if (isLivestreamOver) {
+ // setDisplayCountdown(false);
+ // setIsLive(false);
+ // } else {
+ // const datePlusTenMinuteBuffer = scheduledDate.setMinutes(10, 0, 0);
+ // const isInFuture = Date.now() < datePlusTenMinuteBuffer;
+ // if (isInFuture) {
+ // setDisplayCountdown(true);
+ // setIsLive(false);
+ // } else {
+ // setDisplayCountdown(false);
+ // setIsLive(false);
+ // }
+ // }
+ // setIsFetching(false);
+ // } else {
+ // // Offline and no countdown happening
+ // setIsLive(false);
+ // setDisplayCountdown(false);
+ // setActiveViewers(0);
+ // setIsFetching(false);
+ // }
+ // });
+ // }
+ // let interval;
+ // checkIsLive();
+ // if (uri) {
+ // interval = setInterval(checkIsLive, 10000);
+ // }
+ // return () => {
+ // if (interval) {
+ // clearInterval(interval);
+ // }
+ // };
+ }, [uri]);
+
+ const stringifiedClaim = JSON.stringify(claim);
+ React.useEffect(() => {
+ if (uri && stringifiedClaim) {
+ const jsonClaim = JSON.parse(stringifiedClaim);
+
+ if (jsonClaim) {
+ const { txid, nout, claim_id: claimId } = jsonClaim;
+ const outpoint = `${txid}:${nout}`;
+
+ analytics.apiLogView(uri, outpoint, claimId);
+ }
+
+ if (!isAuthenticated) {
+ const uri = jsonClaim.signing_channel && jsonClaim.signing_channel.permanent_url;
+ if (uri) {
+ doUserSetReferrer(uri.replace('lbry://', ''));
+ }
+ }
+ }
+ }, [uri, stringifiedClaim, isAuthenticated]);
+
+ React.useEffect(() => {
+ // Set playing uri to null so the popout player doesnt start playing the dummy claim if a user navigates back
+ // This can be removed when we start using the app video player, not a bitwave iframe
+ doSetPlayingUri({ uri: null });
+ }, [doSetPlayingUri]);
+
+ return (
+
+
+
+ );
+}
diff --git a/ui/page/show/view.jsx b/ui/page/show/view.jsx
index 0e53c6ba5..faf9f3970 100644
--- a/ui/page/show/view.jsx
+++ b/ui/page/show/view.jsx
@@ -5,7 +5,7 @@ import { Redirect } from 'react-router-dom';
import Spinner from 'component/spinner';
import ChannelPage from 'page/channel';
import FilePage from 'page/file';
-import LivestreamPage from 'page/livestream';
+import LivestreamPage from 'page/livestreamStream';
import Page from 'component/page';
import Button from 'component/button';
import Card from 'component/common/card';
diff --git a/ui/redux/actions/comments.js b/ui/redux/actions/comments.js
index e5737c8c2..6683b2b2f 100644
--- a/ui/redux/actions/comments.js
+++ b/ui/redux/actions/comments.js
@@ -196,13 +196,7 @@ export function doCommentReact(commentId: string, type: string) {
};
}
-export function doCommentCreate(
- comment: string = '',
- claim_id: string = '',
- parent_id?: string,
- uri: string,
- livestream?: boolean = false
-) {
+export function doCommentCreate(comment: string = '', claim_id: string = '', parent_id?: string, uri: string) {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const activeChannelClaim = selectActiveChannelClaim(state);
@@ -234,7 +228,6 @@ export function doCommentCreate(
type: ACTIONS.COMMENT_CREATE_COMPLETED,
data: {
uri,
- livestream,
comment: result,
claimId: claim_id,
},
diff --git a/ui/redux/actions/websocket.js b/ui/redux/actions/websocket.js
index c33c442a3..3651a97e1 100644
--- a/ui/redux/actions/websocket.js
+++ b/ui/redux/actions/websocket.js
@@ -2,8 +2,6 @@ import * as ACTIONS from 'constants/action_types';
import { getAuthToken } from 'util/saved-passwords';
import { doNotificationList } from 'redux/actions/notifications';
-const COMMENT_WS_URL = `wss://comments.lbry.com/api/v2/live-chat/subscribe?subscription_id=`;
-
let sockets = {};
let retryCount = 0;
@@ -28,13 +26,13 @@ export const doSocketConnect = (url, cb) => {
sockets[url].onerror = (e) => {
console.error('websocket onerror', e); // eslint-disable-line
- retryCount += 1;
- connectToSocket();
+ // onerror and onclose will both fire, so nothing is needed here
};
- sockets[url].onclose = () => {
- console.log('\n Disconnected from WS\n\n'); // eslint-disable-line
- sockets[url] = null;
+ sockets[url].onclose = (e) => {
+ console.error('websocket onclose', e); // eslint-disable-line
+ retryCount += 1;
+ connectToSocket();
};
}, timeToWait);
}
@@ -42,17 +40,6 @@ export const doSocketConnect = (url, cb) => {
connectToSocket();
};
-export const doSocketDisconnect = (url) => (dispatch) => {
- if (sockets[url] !== undefined && sockets[url] !== null) {
- sockets[url].close();
- sockets[url] = null;
-
- dispatch({
- type: ACTIONS.WS_DISCONNECT,
- });
- }
-};
-
export const doNotificationSocketConnect = () => (dispatch) => {
const authToken = getAuthToken();
if (!authToken) {
@@ -68,21 +55,20 @@ export const doNotificationSocketConnect = () => (dispatch) => {
});
};
-export const doCommentSocketConnect = (uri, claimId) => (dispatch) => {
- const url = `${COMMENT_WS_URL}${claimId}`;
+export const doCommentSocketConnect = (claimId) => (dispatch) => {
+ const url = `wss://comments.lbry.com/api/v2/live-chat/subscribe?subscription_id=${claimId}`;
doSocketConnect(url, (response) => {
if (response.type === 'delta') {
const newComment = response.data.comment;
dispatch({
type: ACTIONS.COMMENT_RECEIVED,
- data: { comment: newComment, claimId, uri },
+ data: { comment: newComment, claimId },
});
}
});
};
-export const doCommentSocketDisconnect = (claimId) => (dispatch) => {
- const url = `${COMMENT_WS_URL}${claimId}`;
- dispatch(doSocketDisconnect(url));
-};
+export const doSocketDisconnect = () => ({
+ type: ACTIONS.WS_DISCONNECT,
+});
diff --git a/ui/redux/reducers/comments.js b/ui/redux/reducers/comments.js
index 9307a698f..ed199cb60 100644
--- a/ui/redux/reducers/comments.js
+++ b/ui/redux/reducers/comments.js
@@ -7,9 +7,6 @@ const defaultState: CommentsState = {
byId: {}, // ClaimID -> list of comments
repliesByParentId: {}, // ParentCommentID -> list of reply comments
topLevelCommentsById: {}, // ClaimID -> list of top level comments
- // TODO:
- // Remove commentsByUri
- // It is not needed and doesn't provide anything but confusion
commentsByUri: {}, // URI -> claimId
isLoading: false,
isCommenting: false,
@@ -38,12 +35,7 @@ export default handleActions(
}),
[ACTIONS.COMMENT_CREATE_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
- const {
- comment,
- claimId,
- uri,
- livestream,
- }: { comment: Comment, claimId: string, uri: string, livestream: boolean } = action.data;
+ const { comment, claimId, uri }: { comment: Comment, claimId: string, uri: string } = action.data;
const commentById = Object.assign({}, state.commentById);
const byId = Object.assign({}, state.byId);
const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById); // was byId {ClaimId -> [commentIds...]}
@@ -52,31 +44,27 @@ export default handleActions(
const comments = byId[claimId] || [];
const newCommentIds = comments.slice();
- // If it was created during a livestream, let the websocket handler perform the state update
- if (!livestream) {
- // add the comment by its ID
- commentById[comment.comment_id] = comment;
+ // add the comment by its ID
+ commentById[comment.comment_id] = comment;
- // push the comment_id to the top of ID list
- newCommentIds.unshift(comment.comment_id);
- byId[claimId] = newCommentIds;
+ // push the comment_id to the top of ID list
+ newCommentIds.unshift(comment.comment_id);
+ byId[claimId] = newCommentIds;
- if (comment['parent_id']) {
- if (!repliesByParentId[comment.parent_id]) {
- repliesByParentId[comment.parent_id] = [comment.comment_id];
- } else {
- repliesByParentId[comment.parent_id].unshift(comment.comment_id);
- }
+ if (comment['parent_id']) {
+ if (!repliesByParentId[comment.parent_id]) {
+ repliesByParentId[comment.parent_id] = [comment.comment_id];
} else {
- if (!topLevelCommentsById[claimId]) {
- commentsByUri[uri] = claimId;
- topLevelCommentsById[claimId] = [comment.comment_id];
- } else {
- topLevelCommentsById[claimId].unshift(comment.comment_id);
- }
+ repliesByParentId[comment.parent_id].unshift(comment.comment_id);
+ }
+ } else {
+ if (!topLevelCommentsById[claimId]) {
+ commentsByUri[uri] = claimId;
+ topLevelCommentsById[claimId] = [comment.comment_id];
+ } else {
+ topLevelCommentsById[claimId].unshift(comment.comment_id);
}
}
-
return {
...state,
topLevelCommentsById,
@@ -217,42 +205,6 @@ export default handleActions(
...state,
isLoading: false,
}),
-
- [ACTIONS.COMMENT_RECEIVED]: (state: CommentsState, action: any) => {
- const { uri, claimId, comment } = action.data;
- const commentsByUri = Object.assign({}, state.commentsByUri);
- const commentsByClaimId = Object.assign({}, state.byId);
- const allCommentsById = Object.assign({}, state.commentById);
- const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById);
- const commentsForId = topLevelCommentsById[claimId];
-
- allCommentsById[comment.comment_id] = comment;
- commentsByUri[uri] = claimId;
-
- if (commentsForId) {
- const newCommentsForId = commentsForId.slice();
- const commentExists = newCommentsForId.includes(comment.comment_id);
- if (!commentExists) {
- newCommentsForId.unshift(comment.comment_id);
- }
-
- topLevelCommentsById[claimId] = newCommentsForId;
- } else {
- topLevelCommentsById[claimId] = [comment.comment_id];
- }
-
- // We don't care to keep existing lower level comments since this is just for livestreams
- commentsByClaimId[claimId] = topLevelCommentsById[claimId];
-
- return {
- ...state,
- byId: commentsByClaimId,
- commentById: allCommentsById,
- commentsByUri,
- topLevelCommentsById,
- };
- },
-
[ACTIONS.COMMENT_ABANDON_STARTED]: (state: CommentsState, action: any) => ({
...state,
isLoading: true,
diff --git a/ui/scss/component/_livestream.scss b/ui/scss/component/_livestream.scss
index 9cca8fcb0..fb218a88d 100644
--- a/ui/scss/component/_livestream.scss
+++ b/ui/scss/component/_livestream.scss
@@ -196,7 +196,3 @@
display: inline-block;
}
}
-
-.livestream__publish-intro {
- margin-top: var(--spacing-l);
-}
diff --git a/web/effects/use-degraded-performance.js b/web/effects/use-degraded-performance.js
index bf00f0487..99e8a8d14 100644
--- a/web/effects/use-degraded-performance.js
+++ b/web/effects/use-degraded-performance.js
@@ -11,7 +11,7 @@ export const STATUS_DEGRADED = 'degraded';
export const STATUS_FAILING = 'failing';
export const STATUS_DOWN = 'down';
-const getParams = (user) => {
+const getParams = user => {
const headers = {};
const token = getAuthToken();
if (token && user.has_verified_email) {
@@ -31,8 +31,8 @@ export function useDegradedPerformance(onDegradedPerformanceCallback, user) {
const STATUS_ENDPOINT = `${SDK_API_PATH}/status`.replace('v1', 'v2');
fetchWithTimeout(STATUS_TIMEOUT_LIMIT, fetch(STATUS_ENDPOINT, getParams(user)))
- .then((response) => response.json())
- .then((status) => {
+ .then(response => response.json())
+ .then(status => {
if (status.general_state !== STATUS_OK) {
onDegradedPerformanceCallback(STATUS_FAILING);
}
diff --git a/yarn.lock b/yarn.lock
index e18ab3ca7..47072ac4a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6950,9 +6950,9 @@ lazy-val@^1.0.4:
yargs "^13.2.2"
zstd-codec "^0.1.1"
-lbry-redux@lbryio/lbry-redux#d75e7725feb1584937c405ddfda91f0f30ee8749:
+lbry-redux@lbryio/lbry-redux#bf728f8716385749370de9a1edee50b267d61fa4:
version "0.0.1"
- resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/d75e7725feb1584937c405ddfda91f0f30ee8749"
+ resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/bf728f8716385749370de9a1edee50b267d61fa4"
dependencies:
proxy-polyfill "0.1.6"
reselect "^3.0.0"