Skip to content

Commit

Permalink
feature: use internal-apis for subscriptions and add page loader
Browse files Browse the repository at this point in the history
  • Loading branch information
Sean Yesmunt committed May 7, 2018
1 parent f6afb2e commit 1fe95ce
Show file tree
Hide file tree
Showing 15 changed files with 415 additions and 195 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Expand Up @@ -34,6 +34,7 @@
"singleQuote": true
}],
"func-names": ["warn", "as-needed"],
"jsx-a11y/label-has-for": 0
"jsx-a11y/label-has-for": 0,
"import/prefer-default-export": 0
}
}
5 changes: 4 additions & 1 deletion src/renderer/component/fileList/view.jsx
Expand Up @@ -53,7 +53,6 @@ class FileList extends React.PureComponent<Props, State> {
if (fileInfo1.pending) {
return -1;
}

const height1 = this.props.claimsById[fileInfo1.claim_id]
? this.props.claimsById[fileInfo1.claim_id].height
: 0;
Expand Down Expand Up @@ -145,6 +144,10 @@ class FileList extends React.PureComponent<Props, State> {
const { sortBy } = this.state;
const content = [];

if (!fileInfos) {
return null;
}

this.sortFunctions[sortBy](fileInfos).forEach(fileInfo => {
const {
channel_name: channelName,
Expand Down
103 changes: 84 additions & 19 deletions src/renderer/component/page/view.jsx
@@ -1,33 +1,98 @@
// @flow
import * as React from 'react';
import classnames from 'classnames';
import Spinner from 'component/common/spinner';
import { isShowingChildren } from 'util/dom';

// time in ms to wait to show loading spinner
const LOADER_TIMEOUT = 1500;

type Props = {
children: React.Node,
children: React.Node | Array<React.Node>,
pageTitle: ?string,
noPadding: ?boolean,
extraPadding: ?boolean,
notContained: ?boolean, // No max-width, but keep the padding
loading: ?boolean,
};

const Page = (props: Props) => {
const { pageTitle, children, noPadding, extraPadding, notContained } = props;
return (
<main
className={classnames('main', {
'main--contained': !notContained && !noPadding && !extraPadding,
'main--no-padding': noPadding,
'main--extra-padding': extraPadding,
})}
>
{pageTitle && (
<div className="page__header">
{pageTitle && <h1 className="page__title">{pageTitle}</h1>}
</div>
)}
{children}
</main>
);
type State = {
showLoader: ?boolean,
};

class Page extends React.PureComponent<Props, State> {
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
const { children } = nextProps;
const { showLoader } = prevState;

// If we aren't showing the loader, don't bother updating
if (!showLoader) {
return null;
}

if (isShowingChildren(children)) {
return {
showLoader: false,
};
}
return null;
}

constructor() {
super();

this.state = {
showLoader: false,
};

this.loaderTimeout = null;
}

componentDidMount() {
const { children } = this.props;

if (!isShowingChildren(children))
this.loaderTimeout = setTimeout(() => {
this.setState({ showLoader: true });
}, LOADER_TIMEOUT);
}

componentWillUnmount() {
this.loaderTimeout = null;
}

loaderTimeout: ?number;

render() {
const { pageTitle, children, noPadding, extraPadding, notContained, loading } = this.props;
const { showLoader } = this.state;

// We don't want to show the loading spinner right away if it will only flash on the
// screen for a short time, wait until we know it will be loading for a bit before showing it
const shouldShowLoader = !isShowingChildren(children) && showLoader;

return (
<main
className={classnames('main', {
'main--contained': !notContained && !noPadding && !extraPadding,
'main--no-padding': noPadding,
'main--extra-padding': extraPadding,
})}
>
{pageTitle && (
<div className="page__header">
{pageTitle && <h1 className="page__title">{pageTitle}</h1>}
</div>
)}
{!loading && children}
{shouldShowLoader && (
<div className="page__empty">
<Spinner />
</div>
)}
</main>
);
}
}

export default Page;
3 changes: 3 additions & 0 deletions src/renderer/constants/action_types.js
Expand Up @@ -166,6 +166,9 @@ export const SET_SUBSCRIPTION_NOTIFICATIONS = 'SET_SUBSCRIPTION_NOTIFICATIONS';
export const CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED';
export const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED';
export const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE';
export const FETCH_MY_SUBSCRIPTIONS_START = 'FETCH_MY_SUBSCRIPTIONS_START';
export const FETCH_MY_SUBSCRIPTIONS_FAIL = 'FETCH_MY_SUBSCRIPTIONS_FAIL';
export const FETCH_MY_SUBSCRIPTIONS_SUCCESS = 'FETCH_MY_SUBSCRIPTIONS_SUCCESS';

// Video controls
export const SET_VIDEO_PAUSE = 'SET_VIDEO_PAUSE';
Expand Down
7 changes: 4 additions & 3 deletions src/renderer/page/channel/view.jsx
Expand Up @@ -16,6 +16,7 @@ type Props = {
claim: {
name: string,
claim_id: string,
permanent_url: string,
},
claimsInChannel: Array<{}>,
fetchClaims: (string, number) => void,
Expand Down Expand Up @@ -58,8 +59,8 @@ class ChannelPage extends React.PureComponent<Props> {
}

render() {
const { fetching, claimsInChannel, claim, uri, page, totalPages } = this.props;
const { name } = claim;
const { fetching, claimsInChannel, claim, page, totalPages } = this.props;
const { name, permanent_url: permanentUrl } = claim;

let contentList;
if (fetching) {
Expand All @@ -78,7 +79,7 @@ class ChannelPage extends React.PureComponent<Props> {
<section className="card__channel-info card__channel-info--large">
<h1>{name}</h1>
<div className="card__actions card__actions--no-margin">
<SubscribeButton uri={uri} channelName={name} />
<SubscribeButton uri={permanentUrl} channelName={name} />
</div>
</section>
<section>{contentList}</section>
Expand Down
20 changes: 9 additions & 11 deletions src/renderer/page/subscriptions/index.js
@@ -1,27 +1,25 @@
import React from 'react';
import { connect } from 'react-redux';
import {
selectSubscriptionsFromClaims,
selectSubscriptionClaims,
selectSubscriptions,
selectHasFetchedSubscriptions,
selectSubscriptionsBeingFetched,
selectIsFetchingSubscriptions,
selectNotifications,
} from 'redux/selectors/subscriptions';
import { doFetchClaimsByChannel } from 'redux/actions/content';
import {
setHasFetchedSubscriptions,
setSubscriptionNotifications,
} from 'redux/actions/subscriptions';
import { setSubscriptionNotifications, doFetchMySubscriptions } from 'redux/actions/subscriptions';
import SubscriptionsPage from './view';

const select = state => ({
hasFetchedSubscriptions: state.subscriptions.hasFetchedSubscriptions,
savedSubscriptions: selectSubscriptions(state),
subscriptions: selectSubscriptionsFromClaims(state),
isFetchingSubscriptions: selectIsFetchingSubscriptions(state),
subscriptionsBeingFetched: selectSubscriptionsBeingFetched(state),
subscriptions: selectSubscriptions(state),
subscriptionClaims: selectSubscriptionClaims(state),
notifications: selectNotifications(state),
});

export default connect(select, {
doFetchClaimsByChannel,
setHasFetchedSubscriptions,
setSubscriptionNotifications,
doFetchMySubscriptions,
})(SubscriptionsPage);
105 changes: 35 additions & 70 deletions src/renderer/page/subscriptions/view.jsx
@@ -1,39 +1,28 @@
// @flow
import React from 'react';
import Page from 'component/page';
import CategoryList from 'component/common/category-list';
import type { Subscription } from 'redux/reducers/subscriptions';
import * as NOTIFICATION_TYPES from 'constants/notification_types';
import Button from 'component/button';

type SavedSubscriptions = Array<Subscription>;
import FileList from 'component/fileList';

type Props = {
doFetchClaimsByChannel: (string, number) => any,
savedSubscriptions: SavedSubscriptions,
doFetchClaimsByChannel: (string, number) => void,
doFetchMySubscriptions: () => void,
setSubscriptionNotifications: ({}) => void,
// TODO build out claim types
subscriptions: Array<any>,
setHasFetchedSubscriptions: () => void,
hasFetchedSubscriptions: boolean,
subscriptions: Array<Subscription>,
isFetchingSubscriptions: boolean,
subscriptionClaims: Array<{ uri: string, claims: Array<{}> }>,
subscriptionsBeingFetched: {},
notifications: {},
};

export default class extends React.PureComponent<Props> {
// setHasFetchedSubscriptions is a terrible hack
// it allows the subscriptions to load correctly when refresing on the subscriptions page
// currently the page is rendered before the state is rehyrdated
// that causes this component to be rendered with zero savedSubscriptions
// we need to wait until persist/REHYDRATE has fired before rendering the page
componentDidMount() {
const {
savedSubscriptions,
setHasFetchedSubscriptions,
notifications,
setSubscriptionNotifications,
} = this.props;
if (savedSubscriptions.length) {
this.fetchSubscriptions(savedSubscriptions);
setHasFetchedSubscriptions();
}
const { notifications, setSubscriptionNotifications, doFetchMySubscriptions } = this.props;
doFetchMySubscriptions();

const newNotifications = {};
Object.keys(notifications).forEach(cur => {
if (notifications[cur].type === NOTIFICATION_TYPES.DOWNLOADING) {
Expand All @@ -43,69 +32,45 @@ export default class extends React.PureComponent<Props> {
setSubscriptionNotifications(newNotifications);
}

componentWillReceiveProps(props: Props) {
const { savedSubscriptions, hasFetchedSubscriptions, setHasFetchedSubscriptions } = props;
componentDidUpdate() {
const {
subscriptions,
subscriptionClaims,
doFetchClaimsByChannel,
subscriptionsBeingFetched,
} = this.props;

if (!hasFetchedSubscriptions && savedSubscriptions.length) {
this.fetchSubscriptions(savedSubscriptions);
setHasFetchedSubscriptions();
}
}
const subscriptionClaimMap = {};
subscriptionClaims.forEach(claim => {
subscriptionClaimMap[claim.uri] = 1;
});

fetchSubscriptions(savedSubscriptions: SavedSubscriptions) {
const { doFetchClaimsByChannel } = this.props;
if (savedSubscriptions.length) {
// can this use batchActions?
savedSubscriptions.forEach(sub => {
subscriptions.forEach(sub => {
if (!subscriptionClaimMap[sub.uri] && !subscriptionsBeingFetched[sub.uri]) {
doFetchClaimsByChannel(sub.uri, 1);
});
}
}
});
}

render() {
const { subscriptions, savedSubscriptions } = this.props;

// TODO: if you are subscribed to an empty channel, this will always be true (but it should not be)
const someClaimsNotLoaded = Boolean(
subscriptions.find(subscription => !subscription.claims.length)
);
const { subscriptions, subscriptionClaims, isFetchingSubscriptions } = this.props;

const fetchingSubscriptions =
!!savedSubscriptions.length &&
(subscriptions.length !== savedSubscriptions.length || someClaimsNotLoaded);
let claimList = [];
subscriptionClaims.forEach(claimData => {
claimList = claimList.concat(claimData.claims);
});

return (
<Page noPadding isLoading={fetchingSubscriptions}>
{!savedSubscriptions.length && (
<Page notContained loading={isFetchingSubscriptions}>
{!subscriptions.length && (
<div className="page__empty">
{__("It looks like you aren't subscribed to any channels yet.")}
<div className="card__actions card__actions--center">
<Button button="primary" navigate="/discover" label={__('Explore new content')} />
</div>
</div>
)}
{!!savedSubscriptions.length && (
<div>
{!!subscriptions.length &&
subscriptions.map(subscription => {
if (!subscription.claims.length) {
// will need to update when you can subscribe to empty channels
// for now this prevents issues with FeaturedCategory being rendered
// before the names (claim uris) are populated
return '';
}

return (
<CategoryList
key={subscription.channelName}
categoryLink={subscription.uri}
category={subscription.channelName}
names={subscription.claims}
/>
);
})}
</div>
)}
{!!claimList.length && <FileList hideFilter sortByHeight fileInfos={claimList} />}
</Page>
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/renderer/redux/actions/content.js
Expand Up @@ -2,7 +2,6 @@ import * as MODALS from 'constants/modal_types';
import * as NOTIFICATION_TYPES from 'constants/notification_types';
import { ipcRenderer } from 'electron';
import Lbryio from 'lbryio';
import { doNotify } from 'lbry-redux';
import { doAlertError } from 'redux/actions/app';
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
import { doNavigate } from 'redux/actions/navigation';
Expand All @@ -27,6 +26,7 @@ import {
selectDownloadingByOutpoint,
selectTotalDownloadProgress,
selectBalance,
doNotify,
} from 'lbry-redux';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import setBadge from 'util/setBadge';
Expand Down Expand Up @@ -138,6 +138,7 @@ export function doUpdateLoadStatus(uri, outpoint) {
: acc,
0
);

const notif = new window.Notification(notifications[uri].subscription.channelName, {
body: `Posted ${fileInfo.metadata.title}${
count > 1 && count < 10 ? ` and ${count - 1} other new items` : ''
Expand Down

0 comments on commit 1fe95ce

Please sign in to comment.