Merge branch 'master' into issue/1119

This commit is contained in:
Igor Gassmann 2018-03-20 16:06:19 -04:00 committed by GitHub
commit 39072b07b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 147 additions and 26 deletions

View file

@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
* Do not kill an existing daemon, instead check if one exists ([#973](https://github.com/lbryio/lbry-app/pull/973))
* Enable play button immediately after user clicks download ([#987](https://github.com/lbryio/lbry-app/pull/987))
* Significantly improved search performance ([#1032](https://github.com/lbryio/lbry-app/pull/1032))
* Allow editing of claims when bid is greater than current balance ([1105](https://github.com/lbryio/lbry-app/pull/1105))
### Fixed
* Fixed sort by date of published content ([#986](https://github.com/lbryio/lbry-app/issues/986))
@ -35,6 +36,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
* App will no longer reset when minimizing to tray ([#1042](https://github.com/lbryio/lbry-app/pull/1042))
* Error when clicking LBRY URLs when app is closed on macOS ([#1119](https://github.com/lbryio/lbry-app/issues/1119))
* LBRY URLs not working on Linux ([#1120](https://github.com/lbryio/lbry-app/issues/1120))
* Fix Windows notifications not showing ([1145](https://github.com/lbryio/lbry-app/pull/1145))
### Deprecated
*

View file

@ -5,6 +5,9 @@
"keywords": [
"lbry"
],
"build": {
"appId": "io.lbry.LBRY"
},
"license": "MIT",
"homepage": "https://lbry.io/",
"bugs": {

View file

@ -50,6 +50,7 @@ const installExtensions = async () => {
app.setAsDefaultProtocolClient('lbry');
app.setName('LBRY');
app.setAppUserModelId('io.lbry.LBRY');
app.on('ready', async () => {
const processList = await findProcess('name', 'lbrynet-daemon');

View file

@ -2,6 +2,7 @@
import mixpanel from 'mixpanel-browser';
import Lbryio from 'lbryio';
import isDev from 'electron-is-dev';
import type { Subscription } from 'redux/reducers/subscriptions';
if (isDev) {
mixpanel.init('691723e855cabb9d27a7a79002216967');
@ -13,7 +14,9 @@ type Analytics = {
track: (string, ?Object) => void,
setUser: Object => void,
toggle: (boolean, ?boolean) => void,
apiLog: (string, string, string) => void,
apiLogView: (string, string, string) => void,
apiLogSubscribe: Subscription => void,
apiLogUnsubscribe: Subscription => void,
};
let analyticsEnabled: boolean = false;
@ -44,7 +47,7 @@ const analytics: Analytics = {
}
analyticsEnabled = enabled;
},
apiLog: (uri: string, outpoint: string, claimId: string): void => {
apiLogView: (uri: string, outpoint: string, claimId: string): void => {
if (analyticsEnabled) {
Lbryio.call('file', 'view', {
uri,
@ -53,6 +56,20 @@ const analytics: Analytics = {
}).catch(() => {});
}
},
apiLogSubscribe: (subscription: Subscription): void => {
if (analyticsEnabled) {
Lbryio.call('subscription', 'new', {
subscription,
}).catch(() => {});
}
},
apiLogUnsubscribe: (subscription: Subscription): void => {
if (analyticsEnabled) {
Lbryio.call('subscription', 'delete', {
subscription,
}).catch(() => {});
}
},
};
export default analytics;

View file

@ -57,6 +57,16 @@ class ChannelSection extends React.PureComponent {
return;
}
if (newChannelBid === 0) {
this.refs.newChannelName.showError(__('Bid value must be greater than 0.'));
return;
}
if (newChannelBid === balance) {
this.refs.newChannelName.showError(__('Please decrease your bid to account for transaction fees.'));
return;
}
this.setState({
creatingChannel: true,

View file

@ -61,15 +61,6 @@ class PublishForm extends React.PureComponent {
}
handleSubmit() {
const { balance } = this.props;
const { bid } = this.state;
if (bid > balance) {
this.handlePublishError({ message: 'insufficient funds' });
return;
}
this.setState({
submitting: true,
});

View file

@ -1,5 +1,6 @@
import { connect } from 'react-redux';
import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions';
import { doOpenModal } from 'redux/actions/app';
import { selectSubscriptions } from 'redux/selectors/subscriptions';
import SubscribeButton from './view';
@ -11,4 +12,5 @@ const select = (state, props) => ({
export default connect(select, {
doChannelSubscribe,
doChannelUnsubscribe,
doOpenModal,
})(SubscribeButton);

View file

@ -1,7 +1,15 @@
import React from 'react';
import Link from 'component/link';
import * as modals from 'constants/modal_types';
export default ({ channelName, uri, subscriptions, doChannelSubscribe, doChannelUnsubscribe }) => {
export default ({
channelName,
uri,
subscriptions,
doChannelSubscribe,
doChannelUnsubscribe,
doOpenModal,
}) => {
const isSubscribed =
subscriptions.map(subscription => subscription.channelName).indexOf(channelName) !== -1;
@ -15,12 +23,15 @@ export default ({ channelName, uri, subscriptions, doChannelSubscribe, doChannel
iconRight={isSubscribed ? '' : 'at'}
button={isSubscribed ? 'alt' : 'primary'}
label={subscriptionLabel}
onClick={() =>
onClick={() => {
if (!subscriptions.length) {
doOpenModal(modals.FIRST_SUBSCRIPTION);
}
subscriptionHandler({
channelName,
uri,
})
}
});
}}
/>
</div>
) : null;

View file

@ -16,3 +16,4 @@ export const TRANSACTION_FAILED = 'transaction_failed';
export const REWARD_APPROVAL_REQUIRED = 'reward_approval_required';
export const AFFIRM_PURCHASE = 'affirm_purchase';
export const CONFIRM_CLAIM_REVOKE = 'confirmClaimRevoke';
export const FIRST_SUBSCRIPTION = 'firstSubscription';

View file

@ -0,0 +1,11 @@
import { connect } from 'react-redux';
import { doCloseModal } from 'redux/actions/app';
import { doNavigate } from 'redux/actions/navigation';
import ModalFirstSubscription from './view';
const perform = dispatch => () => ({
closeModal: () => dispatch(doCloseModal()),
navigate: path => dispatch(doNavigate(path)),
});
export default connect(null, perform)(ModalFirstSubscription);

View file

@ -0,0 +1,43 @@
import React from 'react';
import { Modal } from 'modal/modal';
import Link from 'component/link';
const ModalFirstSubscription = props => {
const { closeModal, navigate } = props;
return (
<Modal type="custom" isOpen contentLabel="Subscriptions 101">
<section>
<h3 className="modal__header">{__('Subscriptions 101')}</h3>
<p>{__('You just subscribed to your first channel. Awesome!')}</p>
<p>{__('A few quick things to know:')}</p>
<p>
{__('1) You can use the')}{' '}
<Link
label={__('Subscriptions Page')}
onClick={() => {
navigate('/subscriptions');
closeModal();
}}
/>{' '}
{__('to view content across all of your subscribed channels.')}
</p>
<p>
{__(
'2) This app will automatically download new free content from channels you are subscribed to.'
)}
</p>
<p>
{__(
'3) If we have your email address, we may send you notifications and rewards related to new content.'
)}
</p>
<div className="modal__buttons">
<Link button="primary" onClick={closeModal} label={__('Got it')} />
</div>
</section>
</Modal>
);
};
export default ModalFirstSubscription;

View file

@ -14,8 +14,9 @@ import ModalTransactionFailed from 'modal/modalTransactionFailed';
import ModalFileTimeout from 'modal/modalFileTimeout';
import ModalAffirmPurchase from 'modal/modalAffirmPurchase';
import ModalRevokeClaim from 'modal/modalRevokeClaim';
import ModalEmailCollection from '../modalEmailCollection';
import ModalPhoneCollection from '../modalPhoneCollection';
import ModalEmailCollection from 'modal/modalEmailCollection';
import ModalPhoneCollection from 'modal/modalPhoneCollection';
import ModalFirstSubscription from 'modal/modalFirstSubscription';
import * as modals from 'constants/modal_types';
class ModalRouter extends React.PureComponent {
@ -135,6 +136,8 @@ class ModalRouter extends React.PureComponent {
return <ModalPhoneCollection {...modalProps} />;
case modals.EMAIL_COLLECTION:
return <ModalEmailCollection {...modalProps} />;
case modals.FIRST_SUBSCRIPTION:
return <ModalFirstSubscription {...modalProps} />;
default:
return null;
}

View file

@ -4,9 +4,11 @@ import { selectFetchingRewards, selectUnclaimedRewards } from 'redux/selectors/r
import { selectUser } from 'redux/selectors/user';
import { doAuthNavigate, doNavigate } from 'redux/actions/navigation';
import { doRewardList } from 'redux/actions/rewards';
import { selectDaemonSettings } from 'redux/selectors/settings';
import RewardsPage from './view';
const select = (state, props) => ({
daemonSettings: selectDaemonSettings(state),
fetching: selectFetchingRewards(state),
rewards: selectUnclaimedRewards(state),
user: selectUser(state),

View file

@ -29,9 +29,9 @@ class RewardsPage extends React.PureComponent {
// }
renderPageHeader() {
const { doAuth, navigate, user } = this.props;
const { doAuth, navigate, user, daemonSettings } = this.props;
if (user && !user.is_reward_approved) {
if (user && !user.is_reward_approved && daemonSettings.share_usage_data) {
if (!user.primary_email || !user.has_verified_email || !user.is_identity_verified) {
return (
<section className="card">
@ -78,9 +78,21 @@ class RewardsPage extends React.PureComponent {
}
renderUnclaimedRewards() {
const { fetching, rewards, user } = this.props;
const { fetching, rewards, user, daemonSettings, navigate } = this.props;
if (fetching) {
if (!daemonSettings.share_usage_data) {
return (
<div className="card__content empty">
<p>
{__(
'Rewards are currently disabled for your account. Turn on diagnostic data sharing, in'
)}{' '}
<Link onClick={() => navigate('/settings')} label="Settings" />
{__(', in order to re-enable them.')}
</p>
</div>
);
} else if (fetching) {
return (
<div className="card__content">
<BusyMessage message={__('Fetching rewards')} />

View file

@ -309,7 +309,10 @@ class SettingsPage extends React.PureComponent {
onChange={this.onShareDataChange.bind(this)}
defaultChecked={daemonSettings.share_usage_data}
label={__(
'Help make LBRY better by contributing analytics and diagnostic data and about my usage'
'Help make LBRY better by contributing analytics and diagnostic data about my usage.'
)}
helper={__(
'You will be ineligible to earn rewards while diagnostics are not being shared.'
)}
/>
</div>

View file

@ -228,7 +228,7 @@ export function doDownloadFile(uri, streamInfo) {
return dispatch => {
dispatch(doStartDownload(uri, streamInfo.outpoint));
analytics.apiLog(uri, streamInfo.output, streamInfo.claim_id);
analytics.apiLogView(uri, streamInfo.output, streamInfo.claim_id);
dispatch(doClaimEligiblePurchaseRewards());
};

View file

@ -6,21 +6,30 @@ import Lbry from 'lbry';
import { doPurchaseUri } from 'redux/actions/content';
import { doNavigate } from 'redux/actions/navigation';
import { buildURI } from 'lbryURI';
import analytics from 'analytics';
const CHECK_SUBSCRIPTIONS_INTERVAL = 60 * 60 * 1000;
export const doChannelSubscribe = (subscription: Subscription) => (dispatch: Dispatch) =>
export const doChannelSubscribe = (subscription: Subscription) => (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.CHANNEL_SUBSCRIBE,
data: subscription,
});
export const doChannelUnsubscribe = (subscription: Subscription) => (dispatch: Dispatch) =>
analytics.apiLogSubscribe(subscription);
dispatch(doCheckSubscription(subscription, true));
};
export const doChannelUnsubscribe = (subscription: Subscription) => (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.CHANNEL_UNSUBSCRIBE,
data: subscription,
});
analytics.apiLogUnsubscribe(subscription);
};
export const doCheckSubscriptions = () => (
dispatch: Dispatch,
getState: () => SubscriptionState

View file

@ -43,7 +43,7 @@ reducers[ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED] = (state, action) => {
const byId = Object.assign({}, state.byId);
const pendingById = Object.assign({}, state.pendingById);
claims.filter(claim => claim.category && claim.category.match(/claim/)).forEach(claim => {
claims.filter(claim => claim.category && (claim.category.match(/claim/) || claim.category.match(/update/))).forEach(claim => {
byId[claim.claim_id] = claim;
const pending = Object.values(pendingById).find(