Merge branch 'master' into issue/1119
This commit is contained in:
commit
39072b07b5
18 changed files with 147 additions and 26 deletions
|
@ -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))
|
* 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))
|
* 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))
|
* 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
|
||||||
* Fixed sort by date of published content ([#986](https://github.com/lbryio/lbry-app/issues/986))
|
* 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))
|
* 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))
|
* 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))
|
* 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
|
### Deprecated
|
||||||
*
|
*
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"lbry"
|
"lbry"
|
||||||
],
|
],
|
||||||
|
"build": {
|
||||||
|
"appId": "io.lbry.LBRY"
|
||||||
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://lbry.io/",
|
"homepage": "https://lbry.io/",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
@ -50,6 +50,7 @@ const installExtensions = async () => {
|
||||||
|
|
||||||
app.setAsDefaultProtocolClient('lbry');
|
app.setAsDefaultProtocolClient('lbry');
|
||||||
app.setName('LBRY');
|
app.setName('LBRY');
|
||||||
|
app.setAppUserModelId('io.lbry.LBRY');
|
||||||
|
|
||||||
app.on('ready', async () => {
|
app.on('ready', async () => {
|
||||||
const processList = await findProcess('name', 'lbrynet-daemon');
|
const processList = await findProcess('name', 'lbrynet-daemon');
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import mixpanel from 'mixpanel-browser';
|
import mixpanel from 'mixpanel-browser';
|
||||||
import Lbryio from 'lbryio';
|
import Lbryio from 'lbryio';
|
||||||
import isDev from 'electron-is-dev';
|
import isDev from 'electron-is-dev';
|
||||||
|
import type { Subscription } from 'redux/reducers/subscriptions';
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
mixpanel.init('691723e855cabb9d27a7a79002216967');
|
mixpanel.init('691723e855cabb9d27a7a79002216967');
|
||||||
|
@ -13,7 +14,9 @@ type Analytics = {
|
||||||
track: (string, ?Object) => void,
|
track: (string, ?Object) => void,
|
||||||
setUser: Object => void,
|
setUser: Object => void,
|
||||||
toggle: (boolean, ?boolean) => 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;
|
let analyticsEnabled: boolean = false;
|
||||||
|
@ -44,7 +47,7 @@ const analytics: Analytics = {
|
||||||
}
|
}
|
||||||
analyticsEnabled = enabled;
|
analyticsEnabled = enabled;
|
||||||
},
|
},
|
||||||
apiLog: (uri: string, outpoint: string, claimId: string): void => {
|
apiLogView: (uri: string, outpoint: string, claimId: string): void => {
|
||||||
if (analyticsEnabled) {
|
if (analyticsEnabled) {
|
||||||
Lbryio.call('file', 'view', {
|
Lbryio.call('file', 'view', {
|
||||||
uri,
|
uri,
|
||||||
|
@ -53,6 +56,20 @@ const analytics: Analytics = {
|
||||||
}).catch(() => {});
|
}).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;
|
export default analytics;
|
||||||
|
|
|
@ -57,6 +57,16 @@ class ChannelSection extends React.PureComponent {
|
||||||
|
|
||||||
return;
|
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({
|
this.setState({
|
||||||
creatingChannel: true,
|
creatingChannel: true,
|
||||||
|
|
|
@ -61,15 +61,6 @@ class PublishForm extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit() {
|
handleSubmit() {
|
||||||
const { balance } = this.props;
|
|
||||||
const { bid } = this.state;
|
|
||||||
|
|
||||||
if (bid > balance) {
|
|
||||||
this.handlePublishError({ message: 'insufficient funds' });
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
submitting: true,
|
submitting: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions';
|
import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions';
|
||||||
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||||
|
|
||||||
import SubscribeButton from './view';
|
import SubscribeButton from './view';
|
||||||
|
@ -11,4 +12,5 @@ const select = (state, props) => ({
|
||||||
export default connect(select, {
|
export default connect(select, {
|
||||||
doChannelSubscribe,
|
doChannelSubscribe,
|
||||||
doChannelUnsubscribe,
|
doChannelUnsubscribe,
|
||||||
|
doOpenModal,
|
||||||
})(SubscribeButton);
|
})(SubscribeButton);
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'component/link';
|
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 =
|
const isSubscribed =
|
||||||
subscriptions.map(subscription => subscription.channelName).indexOf(channelName) !== -1;
|
subscriptions.map(subscription => subscription.channelName).indexOf(channelName) !== -1;
|
||||||
|
|
||||||
|
@ -15,12 +23,15 @@ export default ({ channelName, uri, subscriptions, doChannelSubscribe, doChannel
|
||||||
iconRight={isSubscribed ? '' : 'at'}
|
iconRight={isSubscribed ? '' : 'at'}
|
||||||
button={isSubscribed ? 'alt' : 'primary'}
|
button={isSubscribed ? 'alt' : 'primary'}
|
||||||
label={subscriptionLabel}
|
label={subscriptionLabel}
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
|
if (!subscriptions.length) {
|
||||||
|
doOpenModal(modals.FIRST_SUBSCRIPTION);
|
||||||
|
}
|
||||||
subscriptionHandler({
|
subscriptionHandler({
|
||||||
channelName,
|
channelName,
|
||||||
uri,
|
uri,
|
||||||
})
|
});
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
|
@ -16,3 +16,4 @@ export const TRANSACTION_FAILED = 'transaction_failed';
|
||||||
export const REWARD_APPROVAL_REQUIRED = 'reward_approval_required';
|
export const REWARD_APPROVAL_REQUIRED = 'reward_approval_required';
|
||||||
export const AFFIRM_PURCHASE = 'affirm_purchase';
|
export const AFFIRM_PURCHASE = 'affirm_purchase';
|
||||||
export const CONFIRM_CLAIM_REVOKE = 'confirmClaimRevoke';
|
export const CONFIRM_CLAIM_REVOKE = 'confirmClaimRevoke';
|
||||||
|
export const FIRST_SUBSCRIPTION = 'firstSubscription';
|
||||||
|
|
11
src/renderer/modal/modalFirstSubscription/index.js
Normal file
11
src/renderer/modal/modalFirstSubscription/index.js
Normal 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);
|
43
src/renderer/modal/modalFirstSubscription/view.jsx
Normal file
43
src/renderer/modal/modalFirstSubscription/view.jsx
Normal 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;
|
|
@ -14,8 +14,9 @@ import ModalTransactionFailed from 'modal/modalTransactionFailed';
|
||||||
import ModalFileTimeout from 'modal/modalFileTimeout';
|
import ModalFileTimeout from 'modal/modalFileTimeout';
|
||||||
import ModalAffirmPurchase from 'modal/modalAffirmPurchase';
|
import ModalAffirmPurchase from 'modal/modalAffirmPurchase';
|
||||||
import ModalRevokeClaim from 'modal/modalRevokeClaim';
|
import ModalRevokeClaim from 'modal/modalRevokeClaim';
|
||||||
import ModalEmailCollection from '../modalEmailCollection';
|
import ModalEmailCollection from 'modal/modalEmailCollection';
|
||||||
import ModalPhoneCollection from '../modalPhoneCollection';
|
import ModalPhoneCollection from 'modal/modalPhoneCollection';
|
||||||
|
import ModalFirstSubscription from 'modal/modalFirstSubscription';
|
||||||
import * as modals from 'constants/modal_types';
|
import * as modals from 'constants/modal_types';
|
||||||
|
|
||||||
class ModalRouter extends React.PureComponent {
|
class ModalRouter extends React.PureComponent {
|
||||||
|
@ -135,6 +136,8 @@ class ModalRouter extends React.PureComponent {
|
||||||
return <ModalPhoneCollection {...modalProps} />;
|
return <ModalPhoneCollection {...modalProps} />;
|
||||||
case modals.EMAIL_COLLECTION:
|
case modals.EMAIL_COLLECTION:
|
||||||
return <ModalEmailCollection {...modalProps} />;
|
return <ModalEmailCollection {...modalProps} />;
|
||||||
|
case modals.FIRST_SUBSCRIPTION:
|
||||||
|
return <ModalFirstSubscription {...modalProps} />;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,11 @@ import { selectFetchingRewards, selectUnclaimedRewards } from 'redux/selectors/r
|
||||||
import { selectUser } from 'redux/selectors/user';
|
import { selectUser } from 'redux/selectors/user';
|
||||||
import { doAuthNavigate, doNavigate } from 'redux/actions/navigation';
|
import { doAuthNavigate, doNavigate } from 'redux/actions/navigation';
|
||||||
import { doRewardList } from 'redux/actions/rewards';
|
import { doRewardList } from 'redux/actions/rewards';
|
||||||
|
import { selectDaemonSettings } from 'redux/selectors/settings';
|
||||||
import RewardsPage from './view';
|
import RewardsPage from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
|
daemonSettings: selectDaemonSettings(state),
|
||||||
fetching: selectFetchingRewards(state),
|
fetching: selectFetchingRewards(state),
|
||||||
rewards: selectUnclaimedRewards(state),
|
rewards: selectUnclaimedRewards(state),
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
|
|
|
@ -29,9 +29,9 @@ class RewardsPage extends React.PureComponent {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
renderPageHeader() {
|
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) {
|
if (!user.primary_email || !user.has_verified_email || !user.is_identity_verified) {
|
||||||
return (
|
return (
|
||||||
<section className="card">
|
<section className="card">
|
||||||
|
@ -78,9 +78,21 @@ class RewardsPage extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderUnclaimedRewards() {
|
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 (
|
return (
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
<BusyMessage message={__('Fetching rewards')} />
|
<BusyMessage message={__('Fetching rewards')} />
|
||||||
|
|
|
@ -309,7 +309,10 @@ class SettingsPage extends React.PureComponent {
|
||||||
onChange={this.onShareDataChange.bind(this)}
|
onChange={this.onShareDataChange.bind(this)}
|
||||||
defaultChecked={daemonSettings.share_usage_data}
|
defaultChecked={daemonSettings.share_usage_data}
|
||||||
label={__(
|
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>
|
</div>
|
||||||
|
|
|
@ -228,7 +228,7 @@ export function doDownloadFile(uri, streamInfo) {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
dispatch(doStartDownload(uri, streamInfo.outpoint));
|
dispatch(doStartDownload(uri, streamInfo.outpoint));
|
||||||
|
|
||||||
analytics.apiLog(uri, streamInfo.output, streamInfo.claim_id);
|
analytics.apiLogView(uri, streamInfo.output, streamInfo.claim_id);
|
||||||
|
|
||||||
dispatch(doClaimEligiblePurchaseRewards());
|
dispatch(doClaimEligiblePurchaseRewards());
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,21 +6,30 @@ import Lbry from 'lbry';
|
||||||
import { doPurchaseUri } from 'redux/actions/content';
|
import { doPurchaseUri } from 'redux/actions/content';
|
||||||
import { doNavigate } from 'redux/actions/navigation';
|
import { doNavigate } from 'redux/actions/navigation';
|
||||||
import { buildURI } from 'lbryURI';
|
import { buildURI } from 'lbryURI';
|
||||||
|
import analytics from 'analytics';
|
||||||
|
|
||||||
const CHECK_SUBSCRIPTIONS_INTERVAL = 60 * 60 * 1000;
|
const CHECK_SUBSCRIPTIONS_INTERVAL = 60 * 60 * 1000;
|
||||||
|
|
||||||
export const doChannelSubscribe = (subscription: Subscription) => (dispatch: Dispatch) =>
|
export const doChannelSubscribe = (subscription: Subscription) => (dispatch: Dispatch) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.CHANNEL_SUBSCRIBE,
|
type: ACTIONS.CHANNEL_SUBSCRIBE,
|
||||||
data: subscription,
|
data: subscription,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const doChannelUnsubscribe = (subscription: Subscription) => (dispatch: Dispatch) =>
|
analytics.apiLogSubscribe(subscription);
|
||||||
|
|
||||||
|
dispatch(doCheckSubscription(subscription, true));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const doChannelUnsubscribe = (subscription: Subscription) => (dispatch: Dispatch) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.CHANNEL_UNSUBSCRIBE,
|
type: ACTIONS.CHANNEL_UNSUBSCRIBE,
|
||||||
data: subscription,
|
data: subscription,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
analytics.apiLogUnsubscribe(subscription);
|
||||||
|
};
|
||||||
|
|
||||||
export const doCheckSubscriptions = () => (
|
export const doCheckSubscriptions = () => (
|
||||||
dispatch: Dispatch,
|
dispatch: Dispatch,
|
||||||
getState: () => SubscriptionState
|
getState: () => SubscriptionState
|
||||||
|
|
|
@ -43,7 +43,7 @@ reducers[ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED] = (state, action) => {
|
||||||
const byId = Object.assign({}, state.byId);
|
const byId = Object.assign({}, state.byId);
|
||||||
const pendingById = Object.assign({}, state.pendingById);
|
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;
|
byId[claim.claim_id] = claim;
|
||||||
|
|
||||||
const pending = Object.values(pendingById).find(
|
const pending = Object.values(pendingById).find(
|
||||||
|
|
Loading…
Reference in a new issue