Merge pull request #56 from lbryio/onboarding

onboarding updates
This commit is contained in:
Sean Yesmunt 2019-09-17 10:33:15 -04:00 committed by GitHub
commit d87e594fe9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1801 additions and 1115 deletions

View file

@ -25,6 +25,8 @@
"import/no-commonjs": "warn",
"import/no-amd": "warn",
"import/prefer-default-export": "ignore",
"func-names": ["warn", "as-needed"]
"flowtype/generic-spacing": 0,
"func-names": ["warn", "as-needed"],
"no-plusplus": 0
}
}

569
dist/bundle.es.js vendored
View file

@ -213,6 +213,13 @@ Lbryio.call = (resource, action, params = {}, method = 'get') => {
auth_token: token,
...params
};
Object.keys(fullParams).forEach(key => {
const value = fullParams[key];
if (typeof value === 'object') {
fullParams[key] = JSON.stringify(value);
}
});
const qs = querystring.stringify(fullParams);
let url = `${Lbryio.CONNECTION_STRING}${resource}/${action}?${qs}`;
let options = {
@ -371,7 +378,7 @@ Lbryio.setOverride = (methodName, newMethod) => {
const rewards = {};
rewards.TYPE_NEW_DEVELOPER = 'new_developer';
rewards.TYPE_NEW_USER = 'new_user';
rewards.TYPE_CONFIRM_EMAIL = 'verified_email';
rewards.TYPE_CONFIRM_EMAIL = 'email_provided';
rewards.TYPE_FIRST_CHANNEL = 'new_channel';
rewards.TYPE_FIRST_STREAM = 'first_stream';
rewards.TYPE_MANY_DOWNLOADS = 'many_downloads';
@ -628,6 +635,273 @@ var subscriptions = handleActions({
})
}, defaultState);
function swapKeyAndValue(dict) {
const ret = {}; // eslint-disable-next-line no-restricted-syntax
for (const key in dict) {
if (dict.hasOwnProperty(key)) {
ret[dict[key]] = key;
}
}
return ret;
}
const selectState = state => state.subscriptions || {}; // Returns the list of channel uris a user is subscribed to
const selectSubscriptions = reselect.createSelector(selectState, state => state.subscriptions); // Fetching list of users subscriptions
const selectIsFetchingSubscriptions = reselect.createSelector(selectState, state => state.loading); // The current view mode on the subscriptions page
const selectViewMode = reselect.createSelector(selectState, state => state.viewMode); // Suggested subscriptions from internal apis
const selectSuggested = reselect.createSelector(selectState, state => state.suggested);
const selectIsFetchingSuggested = reselect.createSelector(selectState, state => state.loadingSuggested);
const selectSuggestedChannels = reselect.createSelector(selectSubscriptions, selectSuggested, (userSubscriptions, suggested) => {
if (!suggested) {
return null;
} // Swap the key/value because we will use the uri for everything, this just makes it easier
// suggested is returned from the api with the form:
// {
// featured: { "Channel label": uri, ... },
// top_subscribed: { "@channel": uri, ... }
// top_bid: { "@channel": uri, ... }
// }
// To properly compare the suggested subscriptions from our current subscribed channels
// We only care about the uri, not the label
// We also only care about top_subscribed and featured
// top_bid could just be porn or a channel with no content
const topSubscribedSuggestions = swapKeyAndValue(suggested[SUGGESTED_TOP_SUBSCRIBED]);
const featuredSuggestions = swapKeyAndValue(suggested[SUGGESTED_FEATURED]); // Make sure there are no duplicates
// If a uri isn't already in the suggested object, add it
const suggestedChannels = { ...topSubscribedSuggestions
};
Object.keys(featuredSuggestions).forEach(uri => {
if (!suggestedChannels[uri]) {
const channelLabel = featuredSuggestions[uri];
suggestedChannels[uri] = channelLabel;
}
});
userSubscriptions.forEach(({
uri
}) => {
// Note to passer bys:
// Maybe we should just remove the `lbry://` prefix from subscription uris
// Most places don't store them like that
const subscribedUri = uri.slice('lbry://'.length);
if (suggestedChannels[subscribedUri]) {
delete suggestedChannels[subscribedUri];
}
});
return Object.keys(suggestedChannels).map(uri => ({
uri,
label: suggestedChannels[uri]
})).slice(0, 5);
});
const selectFirstRunCompleted = reselect.createSelector(selectState, state => state.firstRunCompleted);
const selectShowSuggestedSubs = reselect.createSelector(selectState, state => state.showSuggestedSubs); // Fetching any claims that are a part of a users subscriptions
const selectSubscriptionsBeingFetched = reselect.createSelector(selectSubscriptions, lbryRedux.selectAllFetchingChannelClaims, (subscriptions, fetchingChannelClaims) => {
const fetchingSubscriptionMap = {};
subscriptions.forEach(sub => {
const isFetching = fetchingChannelClaims && fetchingChannelClaims[sub.uri];
if (isFetching) {
fetchingSubscriptionMap[sub.uri] = true;
}
});
return fetchingSubscriptionMap;
});
const selectUnreadByChannel = reselect.createSelector(selectState, state => state.unread); // Returns the current total of unread subscriptions
const selectUnreadAmount = reselect.createSelector(selectUnreadByChannel, unreadByChannel => {
const unreadChannels = Object.keys(unreadByChannel);
let badges = 0;
if (!unreadChannels.length) {
return badges;
}
unreadChannels.forEach(channel => {
badges += unreadByChannel[channel].uris.length;
});
return badges;
}); // Returns the uris with channels as an array with the channel with the newest content first
// If you just want the `unread` state, use selectUnread
const selectUnreadSubscriptions = reselect.createSelector(selectUnreadAmount, selectUnreadByChannel, lbryRedux.selectClaimsByUri, (unreadAmount, unreadByChannel, claimsByUri) => {
// determine which channel has the newest content
const unreadList = [];
if (!unreadAmount) {
return unreadList;
}
const channelUriList = Object.keys(unreadByChannel); // There is only one channel with unread notifications
if (unreadAmount === 1) {
channelUriList.forEach(channel => {
const unreadChannel = {
channel,
uris: unreadByChannel[channel].uris
};
unreadList.push(unreadChannel);
});
return unreadList;
}
channelUriList.sort((channel1, channel2) => {
const latestUriFromChannel1 = unreadByChannel[channel1].uris[0];
const latestClaimFromChannel1 = claimsByUri[latestUriFromChannel1] || {};
const latestUriFromChannel2 = unreadByChannel[channel2].uris[0];
const latestClaimFromChannel2 = claimsByUri[latestUriFromChannel2] || {};
const latestHeightFromChannel1 = latestClaimFromChannel1.height || 0;
const latestHeightFromChannel2 = latestClaimFromChannel2.height || 0;
if (latestHeightFromChannel1 !== latestHeightFromChannel2) {
return latestHeightFromChannel2 - latestHeightFromChannel1;
}
return 0;
}).forEach(channel => {
const unreadSubscription = unreadByChannel[channel];
const unreadChannel = {
channel,
uris: unreadSubscription.uris
};
unreadList.push(unreadChannel);
});
return unreadList;
}); // Returns all unread subscriptions for a uri passed in
const makeSelectUnreadByChannel = uri => reselect.createSelector(selectUnreadByChannel, unread => unread[uri]); // Returns the first page of claims for every channel a user is subscribed to
const selectSubscriptionClaims = reselect.createSelector(lbryRedux.selectAllClaimsByChannel, lbryRedux.selectClaimsById, selectSubscriptions, selectUnreadByChannel, (channelIds, allClaims, savedSubscriptions, unreadByChannel) => {
// no claims loaded yet
if (!Object.keys(channelIds).length) {
return [];
}
let fetchedSubscriptions = [];
savedSubscriptions.forEach(subscription => {
let channelClaims = []; // if subscribed channel has content
if (channelIds[subscription.uri] && channelIds[subscription.uri]['1']) {
// This will need to be more robust, we will want to be able to load more than the first page
// Strip out any ids that will be shown as notifications
const pageOneChannelIds = channelIds[subscription.uri]['1']; // we have the channel ids and the corresponding claims
// loop over the list of ids and grab the claim
pageOneChannelIds.forEach(id => {
const grabbedClaim = allClaims[id];
if (unreadByChannel[subscription.uri] && unreadByChannel[subscription.uri].uris.some(uri => uri.includes(id))) {
grabbedClaim.isNew = true;
}
channelClaims = channelClaims.concat([grabbedClaim]);
});
}
fetchedSubscriptions = fetchedSubscriptions.concat(channelClaims);
});
return fetchedSubscriptions;
}); // Returns true if a user is subscribed to the channel associated with the uri passed in
// Accepts content or channel uris
const makeSelectIsSubscribed = uri => reselect.createSelector(selectSubscriptions, lbryRedux.makeSelectChannelForClaimUri(uri, true), (subscriptions, channelUri) => {
if (channelUri) {
return subscriptions.some(sub => sub.uri === channelUri);
} // If we couldn't get a channel uri from the claim uri, the uri passed in might be a channel already
const {
isChannel
} = lbryRedux.parseURI(uri);
if (isChannel) {
const uriWithPrefix = uri.startsWith('lbry://') ? uri : `lbry://${uri}`;
return subscriptions.some(sub => sub.uri === uriWithPrefix);
}
return false;
});
const makeSelectIsNew = uri => reselect.createSelector(makeSelectIsSubscribed(uri), lbryRedux.makeSelectChannelForClaimUri(uri), selectUnreadByChannel, (isSubscribed, channel, unreadByChannel) => {
if (!isSubscribed) {
return false;
}
const unreadForChannel = unreadByChannel[`lbry://${channel}`];
if (unreadForChannel) {
return unreadForChannel.uris.includes(uri);
}
return false; // If they are subscribed, check to see if this uri is in the list of unreads
});
const selectEnabledChannelNotifications = reselect.createSelector(selectState, state => state.enabledChannelNotifications);
const persistShape = {
version: '0',
shared: {}
};
function userStateSyncMiddleware() {
return ({
getState
}) => next => action => {
if (action.type === CHANNEL_SUBSCRIBE || action.type === CHANNEL_UNSUBSCRIBE || action.type === lbryRedux.ACTIONS.TOGGLE_TAG_FOLLOW) {
const newShape = { ...persistShape
};
const state = getState();
const subscriptions = selectSubscriptions(state).map(({
uri
}) => uri);
const tags = lbryRedux.selectFollowedTags(state);
newShape.shared.subscriptions = subscriptions;
newShape.shared.tags = tags;
const {
uri
} = action.data;
if (action.type === CHANNEL_SUBSCRIBE) {
const newSubscriptions = subscriptions.slice();
newSubscriptions.push(uri);
newShape.shared.subscriptions = newSubscriptions;
} else if (action.type === CHANNEL_UNSUBSCRIBE) {
let newSubscriptions = subscriptions.slice();
newSubscriptions = newSubscriptions.filter(subscribedUri => subscribedUri !== uri);
newShape.shared.subscriptions = newSubscriptions;
} else {
const toggledTag = action.data.name;
const followedTags = lbryRedux.selectFollowedTags(state).map(({
name
}) => name);
const isFollowing = lbryRedux.makeSelectIsFollowingTag(toggledTag)(state);
let newTags = followedTags.slice();
if (isFollowing) {
newTags = newTags.filter(followedTag => followedTag.name !== toggledTag);
} else {
newTags.push(toggledTag);
}
newShape.shared.tags = newTags;
}
Lbryio.call('user_settings', 'set', {
settings: newShape
});
}
return next(action);
};
}
function doGenerateAuthToken(installationId) {
return dispatch => {
dispatch({
@ -658,25 +932,25 @@ function doGenerateAuthToken(installationId) {
};
}
const selectState = state => state.rewards || {};
const selectState$1 = state => state.rewards || {};
const selectUnclaimedRewardsByType = reselect.createSelector(selectState, state => state.unclaimedRewardsByType);
const selectClaimedRewardsById = reselect.createSelector(selectState, state => state.claimedRewardsById);
const selectUnclaimedRewardsByType = reselect.createSelector(selectState$1, state => state.unclaimedRewardsByType);
const selectClaimedRewardsById = reselect.createSelector(selectState$1, state => state.claimedRewardsById);
const selectClaimedRewards = reselect.createSelector(selectClaimedRewardsById, byId => Object.values(byId) || []);
const selectClaimedRewardsByTransactionId = reselect.createSelector(selectClaimedRewards, rewards => rewards.reduce((mapParam, reward) => {
const map = mapParam;
map[reward.transaction_id] = reward;
return map;
}, {}));
const selectUnclaimedRewards = reselect.createSelector(selectState, state => state.unclaimedRewards);
const selectFetchingRewards = reselect.createSelector(selectState, state => !!state.fetching);
const selectUnclaimedRewards = reselect.createSelector(selectState$1, state => state.unclaimedRewards);
const selectFetchingRewards = reselect.createSelector(selectState$1, state => !!state.fetching);
const selectUnclaimedRewardValue = reselect.createSelector(selectUnclaimedRewards, rewards => rewards.reduce((sum, reward) => sum + reward.reward_amount, 0));
const selectClaimsPendingByType = reselect.createSelector(selectState, state => state.claimPendingByType);
const selectClaimsPendingByType = reselect.createSelector(selectState$1, state => state.claimPendingByType);
const selectIsClaimRewardPending = (state, props) => selectClaimsPendingByType(state, props)[props.reward_type];
const makeSelectIsRewardClaimPending = () => reselect.createSelector(selectIsClaimRewardPending, isClaiming => isClaiming);
const selectClaimErrorsByType = reselect.createSelector(selectState, state => state.claimErrorsByType);
const selectClaimErrorsByType = reselect.createSelector(selectState$1, state => state.claimErrorsByType);
const selectClaimRewardError = (state, props) => selectClaimErrorsByType(state, props)[props.reward_type];
@ -686,38 +960,39 @@ const selectRewardByType = (state, rewardType) => selectUnclaimedRewards(state).
const makeSelectRewardByType = () => reselect.createSelector(selectRewardByType, reward => reward);
const makeSelectRewardAmountByType = () => reselect.createSelector(selectRewardByType, reward => reward ? reward.reward_amount : 0);
const selectRewardContentClaimIds = reselect.createSelector(selectState, state => state.rewardedContentClaimIds);
const selectRewardContentClaimIds = reselect.createSelector(selectState$1, state => state.rewardedContentClaimIds);
const selectReferralReward = reselect.createSelector(selectUnclaimedRewards, unclaimedRewards => unclaimedRewards.filter(reward => reward.reward_type === rewards.TYPE_REFERRAL)[0]);
const selectState$1 = state => state.user || {};
const selectAuthenticationIsPending = reselect.createSelector(selectState$1, state => state.authenticationIsPending);
const selectUserIsPending = reselect.createSelector(selectState$1, state => state.userIsPending);
const selectUser = reselect.createSelector(selectState$1, state => state.user);
const selectState$2 = state => state.user || {};
const selectAuthenticationIsPending = reselect.createSelector(selectState$2, state => state.authenticationIsPending);
const selectUserIsPending = reselect.createSelector(selectState$2, state => state.userIsPending);
const selectUser = reselect.createSelector(selectState$2, state => state.user);
const selectUserEmail = reselect.createSelector(selectUser, user => user ? user.primary_email : null);
const selectUserPhone = reselect.createSelector(selectUser, user => user ? user.phone_number : null);
const selectUserCountryCode = reselect.createSelector(selectUser, user => user ? user.country_code : null);
const selectEmailToVerify = reselect.createSelector(selectState$1, selectUserEmail, (state, userEmail) => state.emailToVerify || userEmail);
const selectPhoneToVerify = reselect.createSelector(selectState$1, selectUserPhone, (state, userPhone) => state.phoneToVerify || userPhone);
const selectEmailToVerify = reselect.createSelector(selectState$2, selectUserEmail, (state, userEmail) => state.emailToVerify || userEmail);
const selectPhoneToVerify = reselect.createSelector(selectState$2, selectUserPhone, (state, userPhone) => state.phoneToVerify || userPhone);
const selectUserIsRewardApproved = reselect.createSelector(selectUser, user => user && user.is_reward_approved);
const selectEmailNewIsPending = reselect.createSelector(selectState$1, state => state.emailNewIsPending);
const selectEmailNewErrorMessage = reselect.createSelector(selectState$1, state => state.emailNewErrorMessage);
const selectPhoneNewErrorMessage = reselect.createSelector(selectState$1, state => state.phoneNewErrorMessage);
const selectEmailVerifyIsPending = reselect.createSelector(selectState$1, state => state.emailVerifyIsPending);
const selectEmailVerifyErrorMessage = reselect.createSelector(selectState$1, state => state.emailVerifyErrorMessage);
const selectPhoneNewIsPending = reselect.createSelector(selectState$1, state => state.phoneNewIsPending);
const selectPhoneVerifyIsPending = reselect.createSelector(selectState$1, state => state.phoneVerifyIsPending);
const selectPhoneVerifyErrorMessage = reselect.createSelector(selectState$1, state => state.phoneVerifyErrorMessage);
const selectIdentityVerifyIsPending = reselect.createSelector(selectState$1, state => state.identityVerifyIsPending);
const selectIdentityVerifyErrorMessage = reselect.createSelector(selectState$1, state => state.identityVerifyErrorMessage);
const selectEmailNewIsPending = reselect.createSelector(selectState$2, state => state.emailNewIsPending);
const selectEmailNewErrorMessage = reselect.createSelector(selectState$2, state => state.emailNewErrorMessage);
const selectPhoneNewErrorMessage = reselect.createSelector(selectState$2, state => state.phoneNewErrorMessage);
const selectEmailVerifyIsPending = reselect.createSelector(selectState$2, state => state.emailVerifyIsPending);
const selectEmailVerifyErrorMessage = reselect.createSelector(selectState$2, state => state.emailVerifyErrorMessage);
const selectPhoneNewIsPending = reselect.createSelector(selectState$2, state => state.phoneNewIsPending);
const selectPhoneVerifyIsPending = reselect.createSelector(selectState$2, state => state.phoneVerifyIsPending);
const selectPhoneVerifyErrorMessage = reselect.createSelector(selectState$2, state => state.phoneVerifyErrorMessage);
const selectIdentityVerifyIsPending = reselect.createSelector(selectState$2, state => state.identityVerifyIsPending);
const selectIdentityVerifyErrorMessage = reselect.createSelector(selectState$2, state => state.identityVerifyErrorMessage);
const selectUserVerifiedEmail = reselect.createSelector(selectUser, user => user && user.has_verified_email);
const selectUserIsVerificationCandidate = reselect.createSelector(selectUser, user => user && (!user.has_verified_email || !user.is_identity_verified));
const selectAccessToken = reselect.createSelector(selectState$1, state => state.accessToken);
const selectUserInviteStatusIsPending = reselect.createSelector(selectState$1, state => state.inviteStatusIsPending);
const selectUserInvitesRemaining = reselect.createSelector(selectState$1, state => state.invitesRemaining);
const selectUserInvitees = reselect.createSelector(selectState$1, state => state.invitees);
const selectAccessToken = reselect.createSelector(selectState$2, state => state.accessToken);
const selectUserInviteStatusIsPending = reselect.createSelector(selectState$2, state => state.inviteStatusIsPending);
const selectUserInvitesRemaining = reselect.createSelector(selectState$2, state => state.invitesRemaining);
const selectUserInvitees = reselect.createSelector(selectState$2, state => state.invitees);
const selectUserInviteStatusFailed = reselect.createSelector(selectUserInvitesRemaining, () => selectUserInvitesRemaining === null);
const selectUserInviteNewIsPending = reselect.createSelector(selectState$1, state => state.inviteNewIsPending);
const selectUserInviteNewErrorMessage = reselect.createSelector(selectState$1, state => state.inviteNewErrorMessage);
const selectUserInviteReferralLink = reselect.createSelector(selectState$1, state => state.referralLink);
const selectUserInviteNewIsPending = reselect.createSelector(selectState$2, state => state.inviteNewIsPending);
const selectUserInviteNewErrorMessage = reselect.createSelector(selectState$2, state => state.inviteNewErrorMessage);
const selectUserInviteReferralLink = reselect.createSelector(selectState$2, state => state.referralLink);
function doFetchInviteStatus() {
return dispatch => {
@ -1121,16 +1396,16 @@ function doClaimRewardType(rewardType, options = {}) {
const unclaimedRewards = selectUnclaimedRewards(state);
const reward = rewardType === rewards.TYPE_REWARD_CODE ? {
reward_type: rewards.TYPE_REWARD_CODE
} : unclaimedRewards.find(ur => ur.reward_type === rewardType);
} : unclaimedRewards.find(ur => ur.reward_type === rewardType); // Try to claim the email reward right away, even if we haven't called reward_list yet
if (rewardType !== rewards.TYPE_REWARD_CODE) {
if (rewardType !== rewards.TYPE_REWARD_CODE || rewardType !== rewards.TYPE_CONFIRM_EMAIL) {
if (!reward || reward.transaction_id) {
// already claimed or doesn't exist, do nothing
return;
}
}
if (!userIsRewardApproved && rewardType !== rewards.TYPE_CONFIRM_EMAIL) {
if (!userIsRewardApproved && rewardType !== rewards.TYPE_CONFIRM_EMAIL && rewardType !== rewards.TYPE_REWARD_CODE) {
if (!options || !options.failSilently && rewards.callbacks.rewardApprovalRequested) {
rewards.callbacks.rewardApprovalRequested();
}
@ -1163,6 +1438,10 @@ function doClaimRewardType(rewardType, options = {}) {
}
dispatch(doRewardList());
if (options.callback) {
options.callback();
}
};
const failure = error => {
@ -1180,6 +1459,10 @@ function doClaimRewardType(rewardType, options = {}) {
isError: true
}));
}
if (options.callback) {
options.callback(error);
}
};
rewards.claimReward(rewardType, params).then(success, failure);
@ -1244,217 +1527,6 @@ function doFetchRewardedContent() {
const PAGE_SIZE = 20;
function swapKeyAndValue(dict) {
const ret = {}; // eslint-disable-next-line no-restricted-syntax
for (const key in dict) {
if (dict.hasOwnProperty(key)) {
ret[dict[key]] = key;
}
}
return ret;
}
const selectState$2 = state => state.subscriptions || {}; // Returns the list of channel uris a user is subscribed to
const selectSubscriptions = reselect.createSelector(selectState$2, state => state.subscriptions); // Fetching list of users subscriptions
const selectIsFetchingSubscriptions = reselect.createSelector(selectState$2, state => state.loading); // The current view mode on the subscriptions page
const selectViewMode = reselect.createSelector(selectState$2, state => state.viewMode); // Suggested subscriptions from internal apis
const selectSuggested = reselect.createSelector(selectState$2, state => state.suggested);
const selectIsFetchingSuggested = reselect.createSelector(selectState$2, state => state.loadingSuggested);
const selectSuggestedChannels = reselect.createSelector(selectSubscriptions, selectSuggested, (userSubscriptions, suggested) => {
if (!suggested) {
return null;
} // Swap the key/value because we will use the uri for everything, this just makes it easier
// suggested is returned from the api with the form:
// {
// featured: { "Channel label": uri, ... },
// top_subscribed: { "@channel": uri, ... }
// top_bid: { "@channel": uri, ... }
// }
// To properly compare the suggested subscriptions from our current subscribed channels
// We only care about the uri, not the label
// We also only care about top_subscribed and featured
// top_bid could just be porn or a channel with no content
const topSubscribedSuggestions = swapKeyAndValue(suggested[SUGGESTED_TOP_SUBSCRIBED]);
const featuredSuggestions = swapKeyAndValue(suggested[SUGGESTED_FEATURED]); // Make sure there are no duplicates
// If a uri isn't already in the suggested object, add it
const suggestedChannels = { ...topSubscribedSuggestions
};
Object.keys(featuredSuggestions).forEach(uri => {
if (!suggestedChannels[uri]) {
const channelLabel = featuredSuggestions[uri];
suggestedChannels[uri] = channelLabel;
}
});
userSubscriptions.forEach(({
uri
}) => {
// Note to passer bys:
// Maybe we should just remove the `lbry://` prefix from subscription uris
// Most places don't store them like that
const subscribedUri = uri.slice('lbry://'.length);
if (suggestedChannels[subscribedUri]) {
delete suggestedChannels[subscribedUri];
}
});
return Object.keys(suggestedChannels).map(uri => ({
uri,
label: suggestedChannels[uri]
})).slice(0, 5);
});
const selectFirstRunCompleted = reselect.createSelector(selectState$2, state => state.firstRunCompleted);
const selectShowSuggestedSubs = reselect.createSelector(selectState$2, state => state.showSuggestedSubs); // Fetching any claims that are a part of a users subscriptions
const selectSubscriptionsBeingFetched = reselect.createSelector(selectSubscriptions, lbryRedux.selectAllFetchingChannelClaims, (subscriptions, fetchingChannelClaims) => {
const fetchingSubscriptionMap = {};
subscriptions.forEach(sub => {
const isFetching = fetchingChannelClaims && fetchingChannelClaims[sub.uri];
if (isFetching) {
fetchingSubscriptionMap[sub.uri] = true;
}
});
return fetchingSubscriptionMap;
});
const selectUnreadByChannel = reselect.createSelector(selectState$2, state => state.unread); // Returns the current total of unread subscriptions
const selectUnreadAmount = reselect.createSelector(selectUnreadByChannel, unreadByChannel => {
const unreadChannels = Object.keys(unreadByChannel);
let badges = 0;
if (!unreadChannels.length) {
return badges;
}
unreadChannels.forEach(channel => {
badges += unreadByChannel[channel].uris.length;
});
return badges;
}); // Returns the uris with channels as an array with the channel with the newest content first
// If you just want the `unread` state, use selectUnread
const selectUnreadSubscriptions = reselect.createSelector(selectUnreadAmount, selectUnreadByChannel, lbryRedux.selectClaimsByUri, (unreadAmount, unreadByChannel, claimsByUri) => {
// determine which channel has the newest content
const unreadList = [];
if (!unreadAmount) {
return unreadList;
}
const channelUriList = Object.keys(unreadByChannel); // There is only one channel with unread notifications
if (unreadAmount === 1) {
channelUriList.forEach(channel => {
const unreadChannel = {
channel,
uris: unreadByChannel[channel].uris
};
unreadList.push(unreadChannel);
});
return unreadList;
}
channelUriList.sort((channel1, channel2) => {
const latestUriFromChannel1 = unreadByChannel[channel1].uris[0];
const latestClaimFromChannel1 = claimsByUri[latestUriFromChannel1] || {};
const latestUriFromChannel2 = unreadByChannel[channel2].uris[0];
const latestClaimFromChannel2 = claimsByUri[latestUriFromChannel2] || {};
const latestHeightFromChannel1 = latestClaimFromChannel1.height || 0;
const latestHeightFromChannel2 = latestClaimFromChannel2.height || 0;
if (latestHeightFromChannel1 !== latestHeightFromChannel2) {
return latestHeightFromChannel2 - latestHeightFromChannel1;
}
return 0;
}).forEach(channel => {
const unreadSubscription = unreadByChannel[channel];
const unreadChannel = {
channel,
uris: unreadSubscription.uris
};
unreadList.push(unreadChannel);
});
return unreadList;
}); // Returns all unread subscriptions for a uri passed in
const makeSelectUnreadByChannel = uri => reselect.createSelector(selectUnreadByChannel, unread => unread[uri]); // Returns the first page of claims for every channel a user is subscribed to
const selectSubscriptionClaims = reselect.createSelector(lbryRedux.selectAllClaimsByChannel, lbryRedux.selectClaimsById, selectSubscriptions, selectUnreadByChannel, (channelIds, allClaims, savedSubscriptions, unreadByChannel) => {
// no claims loaded yet
if (!Object.keys(channelIds).length) {
return [];
}
let fetchedSubscriptions = [];
savedSubscriptions.forEach(subscription => {
let channelClaims = []; // if subscribed channel has content
if (channelIds[subscription.uri] && channelIds[subscription.uri]['1']) {
// This will need to be more robust, we will want to be able to load more than the first page
// Strip out any ids that will be shown as notifications
const pageOneChannelIds = channelIds[subscription.uri]['1']; // we have the channel ids and the corresponding claims
// loop over the list of ids and grab the claim
pageOneChannelIds.forEach(id => {
const grabbedClaim = allClaims[id];
if (unreadByChannel[subscription.uri] && unreadByChannel[subscription.uri].uris.some(uri => uri.includes(id))) {
grabbedClaim.isNew = true;
}
channelClaims = channelClaims.concat([grabbedClaim]);
});
}
fetchedSubscriptions = fetchedSubscriptions.concat(channelClaims);
});
return fetchedSubscriptions;
}); // Returns true if a user is subscribed to the channel associated with the uri passed in
// Accepts content or channel uris
const makeSelectIsSubscribed = uri => reselect.createSelector(selectSubscriptions, lbryRedux.makeSelectChannelForClaimUri(uri, true), (subscriptions, channelUri) => {
if (channelUri) {
return subscriptions.some(sub => sub.uri === channelUri);
} // If we couldn't get a channel uri from the claim uri, the uri passed in might be a channel already
const {
isChannel
} = lbryRedux.parseURI(uri);
if (isChannel) {
const uriWithPrefix = uri.startsWith('lbry://') ? uri : `lbry://${uri}`;
return subscriptions.some(sub => sub.uri === uriWithPrefix);
}
return false;
});
const makeSelectIsNew = uri => reselect.createSelector(makeSelectIsSubscribed(uri), lbryRedux.makeSelectChannelForClaimUri(uri), selectUnreadByChannel, (isSubscribed, channel, unreadByChannel) => {
if (!isSubscribed) {
return false;
}
const unreadForChannel = unreadByChannel[`lbry://${channel}`];
if (unreadForChannel) {
return unreadForChannel.uris.includes(uri);
}
return false; // If they are subscribed, check to see if this uri is in the list of unreads
});
const selectEnabledChannelNotifications = reselect.createSelector(selectState$2, state => state.enabledChannelNotifications);
//
const CHECK_SUBSCRIPTIONS_INTERVAL = 15 * 60 * 1000;
const doSetViewMode = viewMode => dispatch => dispatch({
@ -2409,7 +2481,8 @@ reducers$2[lbryRedux.ACTIONS.USER_FETCH_STARTED] = state => Object.assign({}, st
reducers$2[lbryRedux.ACTIONS.USER_FETCH_SUCCESS] = (state, action) => Object.assign({}, state, {
userIsPending: false,
user: action.data.user
user: action.data.user,
emailToVerify: action.data.user.has_verified_email ? null : state.emailToVerify
});
reducers$2[lbryRedux.ACTIONS.USER_FETCH_FAILURE] = state => Object.assign({}, state, {
@ -2971,9 +3044,11 @@ exports.selectUserIsPending = selectUserIsPending;
exports.selectUserIsRewardApproved = selectUserIsRewardApproved;
exports.selectUserIsVerificationCandidate = selectUserIsVerificationCandidate;
exports.selectUserPhone = selectUserPhone;
exports.selectUserVerifiedEmail = selectUserVerifiedEmail;
exports.selectViewMode = selectViewMode;
exports.setSubscriptionLatest = setSubscriptionLatest;
exports.statsReducer = statsReducer;
exports.subscriptionsReducer = subscriptions;
exports.syncReducer = syncReducer;
exports.userReducer = userReducer;
exports.userStateSyncMiddleware = userStateSyncMiddleware;

1655
dist/bundle.js vendored

File diff suppressed because it is too large Load diff

View file

@ -3,4 +3,3 @@
// eslint-disable-next-line no-use-before-define
export type Dispatch<T> = (action: T | Promise<T> | Array<T> | ThunkAction<T>) => any; // Need to refer to ThunkAction
export type GetState = () => any;
export type ThunkAction<T> = (dispatch: Dispatch<T>, getState: GetState) => any;

View file

@ -1,5 +1,5 @@
// @flow
import type { Dispatch as ReduxDispatch } from 'types/redux';
import type { Dispatch } from 'flow-typed/Redux';
import * as ACTIONS from 'constants/action_types';
import {
DOWNLOADED,
@ -12,7 +12,7 @@ import {
SUGGESTED_FEATURED,
} from 'constants/subscriptions';
export type Subscription = {
declare type Subscription = {
channelName: string, // @CryptoCandor,
uri: string, // lbry://@CryptoCandor#9152f3b054f692076a6882d1b58a30e8781cc8e6
latest?: string, // substratum#b0ab143243020e7831fd070d9f871e1fda948620
@ -21,26 +21,26 @@ export type Subscription = {
// Tracking for new content
// i.e. If a subscription has a DOWNLOADING type, we will trigger an OS notification
// to tell users there is new content from their subscriptions
export type SubscriptionNotificationType = DOWNLOADED | DOWNLOADING | NOTIFY_ONLY;
declare type SubscriptionNotificationType = DOWNLOADED | DOWNLOADING | NOTIFY_ONLY;
export type UnreadSubscription = {
declare type UnreadSubscription = {
type: SubscriptionNotificationType,
uris: Array<string>,
};
export type UnreadSubscriptions = {
declare type UnreadSubscriptions = {
[string]: UnreadSubscription,
};
export type ViewMode = VIEW_LATEST_FIRST | VIEW_ALL;
declare type ViewMode = VIEW_LATEST_FIRST | VIEW_ALL;
export type SuggestedType = SUGGESTED_TOP_BID | SUGGESTED_TOP_SUBSCRIBED | SUGGESTED_FEATURED;
declare type SuggestedType = SUGGESTED_TOP_BID | SUGGESTED_TOP_SUBSCRIBED | SUGGESTED_FEATURED;
export type SuggestedSubscriptions = {
declare type SuggestedSubscriptions = {
[SuggestedType]: string,
};
export type SubscriptionState = {
declare type SubscriptionState = {
enabledChannelNotifications: Array<string>,
subscriptions: Array<Subscription>,
unread: UnreadSubscriptions,
@ -55,27 +55,27 @@ export type SubscriptionState = {
//
// Action types
//
export type DoChannelSubscriptionEnableNotifications = {
declare type DoChannelSubscriptionEnableNotifications = {
type: ACTIONS.CHANNEL_SUBSCRIPTION_ENABLE_NOTIFICATIONS,
data: string,
};
export type DoChannelSubscriptionDisableNotifications = {
declare type DoChannelSubscriptionDisableNotifications = {
type: ACTIONS.CHANNEL_SUBSCRIPTION_DISABLE_NOTIFICATIONS,
data: string,
};
export type DoChannelSubscribe = {
declare type DoChannelSubscribe = {
type: ACTIONS.CHANNEL_SUBSCRIBE,
data: Subscription,
};
export type DoChannelUnsubscribe = {
declare type DoChannelUnsubscribe = {
type: ACTIONS.CHANNEL_UNSUBSCRIBE,
data: Subscription,
};
export type DoUpdateSubscriptionUnreads = {
declare type DoUpdateSubscriptionUnreads = {
type: ACTIONS.UPDATE_SUBSCRIPTION_UNREADS,
data: {
channel: string,
@ -84,7 +84,7 @@ export type DoUpdateSubscriptionUnreads = {
},
};
export type DoRemoveSubscriptionUnreads = {
declare type DoRemoveSubscriptionUnreads = {
type: ACTIONS.REMOVE_SUBSCRIPTION_UNREADS,
data: {
channel: string,
@ -92,7 +92,7 @@ export type DoRemoveSubscriptionUnreads = {
},
};
export type SetSubscriptionLatest = {
declare type SetSubscriptionLatest = {
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
data: {
subscription: Subscription,
@ -100,30 +100,30 @@ export type SetSubscriptionLatest = {
},
};
export type CheckSubscriptionStarted = {
declare type CheckSubscriptionStarted = {
type: ACTIONS.CHECK_SUBSCRIPTION_STARTED,
};
export type CheckSubscriptionCompleted = {
declare type CheckSubscriptionCompleted = {
type: ACTIONS.CHECK_SUBSCRIPTION_COMPLETED,
};
export type FetchedSubscriptionsSucess = {
declare type FetchedSubscriptionsSucess = {
type: ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS,
data: Array<Subscription>,
};
export type SetViewMode = {
declare type SetViewMode = {
type: ACTIONS.SET_VIEW_MODE,
data: ViewMode,
};
export type GetSuggestedSubscriptionsSuccess = {
declare type GetSuggestedSubscriptionsSuccess = {
type: ACTIONS.GET_SUGGESTED_SUBSCRIPTIONS_START,
data: SuggestedSubscriptions,
};
export type Action =
declare type SubscriptionAction =
| DoChannelSubscribe
| DoChannelUnsubscribe
| DoUpdateSubscriptionUnreads
@ -134,4 +134,4 @@ export type Action =
| SetViewMode
| Function;
export type Dispatch = ReduxDispatch<Action>;
declare type SubscriptionDispatch = Dispatch<SubscriptionAction>;

23
dist/flow-typed/User.js vendored Normal file
View file

@ -0,0 +1,23 @@
// @flow
declare type User = {
created_at: string,
family_name: ?string,
given_name: ?string,
groups: Array<string>,
has_verified_email: boolean,
id: number,
invite_reward_claimed: boolean,
invited_at: ?number,
invited_by_id: number,
invites_remaining: number,
is_email_enabled: boolean,
is_identity_verified: boolean,
is_reward_approved: boolean,
language: string,
manual_approval_user_id: ?number,
primary_email: string,
reward_status_change_trigger: string,
updated_at: string,
youtube_channels: ?Array<string>,
};

5
flow-typed/Redux.js vendored Normal file
View file

@ -0,0 +1,5 @@
// @flow
// eslint-disable-next-line no-use-before-define
export type Dispatch<T> = (action: T | Promise<T> | Array<T> | ThunkAction<T>) => any; // Need to refer to ThunkAction
export type GetState = () => any;

137
flow-typed/Subscription.js vendored Normal file
View file

@ -0,0 +1,137 @@
// @flow
import type { Dispatch } from 'flow-typed/Redux';
import * as ACTIONS from 'constants/action_types';
import {
DOWNLOADED,
DOWNLOADING,
NOTIFY_ONLY,
VIEW_ALL,
VIEW_LATEST_FIRST,
SUGGESTED_TOP_BID,
SUGGESTED_TOP_SUBSCRIBED,
SUGGESTED_FEATURED,
} from 'constants/subscriptions';
declare type Subscription = {
channelName: string, // @CryptoCandor,
uri: string, // lbry://@CryptoCandor#9152f3b054f692076a6882d1b58a30e8781cc8e6
latest?: string, // substratum#b0ab143243020e7831fd070d9f871e1fda948620
};
// Tracking for new content
// i.e. If a subscription has a DOWNLOADING type, we will trigger an OS notification
// to tell users there is new content from their subscriptions
declare type SubscriptionNotificationType = DOWNLOADED | DOWNLOADING | NOTIFY_ONLY;
declare type UnreadSubscription = {
type: SubscriptionNotificationType,
uris: Array<string>,
};
declare type UnreadSubscriptions = {
[string]: UnreadSubscription,
};
declare type ViewMode = VIEW_LATEST_FIRST | VIEW_ALL;
declare type SuggestedType = SUGGESTED_TOP_BID | SUGGESTED_TOP_SUBSCRIBED | SUGGESTED_FEATURED;
declare type SuggestedSubscriptions = {
[SuggestedType]: string,
};
declare type SubscriptionState = {
enabledChannelNotifications: Array<string>,
subscriptions: Array<Subscription>,
unread: UnreadSubscriptions,
loading: boolean,
viewMode: ViewMode,
suggested: SuggestedSubscriptions,
loadingSuggested: boolean,
firstRunCompleted: boolean,
showSuggestedSubs: boolean,
};
//
// Action types
//
declare type DoChannelSubscriptionEnableNotifications = {
type: ACTIONS.CHANNEL_SUBSCRIPTION_ENABLE_NOTIFICATIONS,
data: string,
};
declare type DoChannelSubscriptionDisableNotifications = {
type: ACTIONS.CHANNEL_SUBSCRIPTION_DISABLE_NOTIFICATIONS,
data: string,
};
declare type DoChannelSubscribe = {
type: ACTIONS.CHANNEL_SUBSCRIBE,
data: Subscription,
};
declare type DoChannelUnsubscribe = {
type: ACTIONS.CHANNEL_UNSUBSCRIBE,
data: Subscription,
};
declare type DoUpdateSubscriptionUnreads = {
type: ACTIONS.UPDATE_SUBSCRIPTION_UNREADS,
data: {
channel: string,
uris: Array<string>,
type?: SubscriptionNotificationType,
},
};
declare type DoRemoveSubscriptionUnreads = {
type: ACTIONS.REMOVE_SUBSCRIPTION_UNREADS,
data: {
channel: string,
uris: Array<string>,
},
};
declare type SetSubscriptionLatest = {
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
data: {
subscription: Subscription,
uri: string,
},
};
declare type CheckSubscriptionStarted = {
type: ACTIONS.CHECK_SUBSCRIPTION_STARTED,
};
declare type CheckSubscriptionCompleted = {
type: ACTIONS.CHECK_SUBSCRIPTION_COMPLETED,
};
declare type FetchedSubscriptionsSucess = {
type: ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS,
data: Array<Subscription>,
};
declare type SetViewMode = {
type: ACTIONS.SET_VIEW_MODE,
data: ViewMode,
};
declare type GetSuggestedSubscriptionsSuccess = {
type: ACTIONS.GET_SUGGESTED_SUBSCRIPTIONS_START,
data: SuggestedSubscriptions,
};
declare type SubscriptionAction =
| DoChannelSubscribe
| DoChannelUnsubscribe
| DoUpdateSubscriptionUnreads
| DoRemoveSubscriptionUnreads
| SetSubscriptionLatest
| CheckSubscriptionStarted
| CheckSubscriptionCompleted
| SetViewMode
| Function;
declare type SubscriptionDispatch = Dispatch<SubscriptionAction>;

23
flow-typed/User.js vendored Normal file
View file

@ -0,0 +1,23 @@
// @flow
declare type User = {
created_at: string,
family_name: ?string,
given_name: ?string,
groups: Array<string>,
has_verified_email: boolean,
id: number,
invite_reward_claimed: boolean,
invited_at: ?number,
invited_by_id: number,
invites_remaining: number,
is_email_enabled: boolean,
is_identity_verified: boolean,
is_reward_approved: boolean,
language: string,
manual_approval_user_id: ?number,
primary_email: string,
reward_status_change_trigger: string,
updated_at: string,
youtube_channels: ?Array<string>,
};

View file

@ -57,7 +57,9 @@
"lint-staged": "^7.0.4",
"prettier": "^1.4.2",
"rollup": "^1.8.0",
"rollup-plugin-alias": "^2.0.0",
"rollup-plugin-babel": "^4.3.2",
"rollup-plugin-copy": "^3.1.0",
"rollup-plugin-flow": "^1.1.1",
"rollup-plugin-includepaths": "^0.2.3",
"webpack": "^4.5.0",

View file

@ -1,27 +1,38 @@
import babel from 'rollup-plugin-babel';
import flow from 'rollup-plugin-flow';
import includePaths from 'rollup-plugin-includepaths';
import copy from 'rollup-plugin-copy';
import alias from 'rollup-plugin-alias';
let includePathOptions = {
include: {},
paths: ['src'],
external: [],
extensions: ['.js']
extensions: ['.js'],
};
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.es.js',
format: 'cjs'
format: 'cjs',
},
plugins: [
alias({
entries: [
{
find: 'flow-typed',
replacement: './flow-typed',
},
],
}),
flow({ all: true }),
includePaths(includePathOptions),
babel({
babelrc: false,
presets: [],
}),
copy({ targets: [{ src: './flow-typed', dest: 'dist' }] }),
],
external: ['lbry-redux']
}
external: ['lbry-redux'],
};

View file

@ -3,6 +3,9 @@ import Lbryio from 'lbryio';
import rewards from 'rewards';
import subscriptionsReducer from 'redux/reducers/subscriptions';
// middleware
export { userStateSyncMiddleware } from 'redux/middleware/sync';
// constants
export { LBRYINC_ACTIONS };
@ -147,6 +150,7 @@ export {
selectUserInviteNewIsPending,
selectUserInviteNewErrorMessage,
selectUserInviteReferralLink,
selectUserVerifiedEmail,
} from 'redux/selectors/user';
export {
makeSelectFetchingCostInfoForUri,

View file

@ -48,6 +48,13 @@ Lbryio.call = (resource, action, params = {}, method = 'get') => {
return Lbryio.getAuthToken().then(token => {
const fullParams = { auth_token: token, ...params };
Object.keys(fullParams).forEach(key => {
const value = fullParams[key];
if (typeof value === 'object') {
fullParams[key] = JSON.stringify(value);
}
});
const qs = querystring.stringify(fullParams);
let url = `${Lbryio.CONNECTION_STRING}${resource}/${action}?${qs}`;

View file

@ -37,14 +37,19 @@ export function doClaimRewardType(rewardType, options = {}) {
? { reward_type: rewards.TYPE_REWARD_CODE }
: unclaimedRewards.find(ur => ur.reward_type === rewardType);
if (rewardType !== rewards.TYPE_REWARD_CODE) {
// Try to claim the email reward right away, even if we haven't called reward_list yet
if (rewardType !== rewards.TYPE_REWARD_CODE || rewardType !== rewards.TYPE_CONFIRM_EMAIL) {
if (!reward || reward.transaction_id) {
// already claimed or doesn't exist, do nothing
return;
}
}
if (!userIsRewardApproved && rewardType !== rewards.TYPE_CONFIRM_EMAIL) {
if (
!userIsRewardApproved &&
rewardType !== rewards.TYPE_CONFIRM_EMAIL &&
rewardType !== rewards.TYPE_REWARD_CODE
) {
if (!options || (!options.failSilently && rewards.callbacks.rewardApprovalRequested)) {
rewards.callbacks.rewardApprovalRequested();
}
@ -78,6 +83,10 @@ export function doClaimRewardType(rewardType, options = {}) {
}
dispatch(doRewardList());
if (options.callback) {
options.callback();
}
};
const failure = error => {
@ -92,6 +101,10 @@ export function doClaimRewardType(rewardType, options = {}) {
if (options.notifyError) {
dispatch(doToast({ message: error.message, isError: true }));
}
if (options.callback) {
options.callback(error);
}
};
rewards.claimReward(rewardType, params).then(success, failure);

View file

@ -1,13 +1,5 @@
// @flow
import type { GetState } from 'types/redux';
import type {
Dispatch as ReduxDispatch,
SubscriptionState,
Subscription,
SubscriptionNotificationType,
ViewMode,
UnreadSubscription,
} from 'types/subscription';
import type { SubscriptionDispatch } from 'flow-typed/Subscription';
import { PAGE_SIZE } from 'constants/claim';
import { doClaimRewardType } from 'redux/actions/rewards';
import { selectSubscriptions, selectUnreadByChannel } from 'redux/selectors/subscriptions';
@ -20,14 +12,14 @@ import rewards from 'rewards';
const CHECK_SUBSCRIPTIONS_INTERVAL = 15 * 60 * 1000;
const SUBSCRIPTION_DOWNLOAD_LIMIT = 1;
export const doSetViewMode = (viewMode: ViewMode) => (dispatch: ReduxDispatch) =>
export const doSetViewMode = (viewMode: ViewMode) => (dispatch: SubscriptionDispatch) =>
dispatch({
type: ACTIONS.SET_VIEW_MODE,
data: viewMode,
});
export const setSubscriptionLatest = (subscription: Subscription, uri: string) => (
dispatch: ReduxDispatch
dispatch: SubscriptionDispatch
) =>
dispatch({
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
@ -42,7 +34,7 @@ export const doUpdateUnreadSubscriptions = (
channelUri: string,
uris: ?Array<string>,
type: ?SubscriptionNotificationType
) => (dispatch: ReduxDispatch, getState: GetState) => {
) => (dispatch: SubscriptionDispatch, getState: GetState) => {
const state = getState();
const unreadByChannel = selectUnreadByChannel(state);
const currentUnreadForChannel: UnreadSubscription = unreadByChannel[channelUri];
@ -84,7 +76,7 @@ export const doUpdateUnreadSubscriptions = (
// Remove multiple files (or all) from a channels unread subscriptions
export const doRemoveUnreadSubscriptions = (channelUri: ?string, readUris: ?Array<string>) => (
dispatch: ReduxDispatch,
dispatch: SubscriptionDispatch,
getState: GetState
) => {
const state = getState();
@ -133,13 +125,13 @@ export const doRemoveUnreadSubscriptions = (channelUri: ?string, readUris: ?Arra
// Remove a single file from a channels unread subscriptions
export const doRemoveUnreadSubscription = (channelUri: string, readUri: string) => (
dispatch: ReduxDispatch
dispatch: SubscriptionDispatch
) => {
dispatch(doRemoveUnreadSubscriptions(channelUri, [readUri]));
};
export const doCheckSubscription = (subscriptionUri: string, shouldNotify?: boolean) => (
dispatch: ReduxDispatch,
dispatch: SubscriptionDispatch,
getState: GetState
) => {
// no dispatching FETCH_CHANNEL_CLAIMS_STARTED; causes loading issues on <SubscriptionsPage>
@ -238,7 +230,7 @@ export const doCheckSubscription = (subscriptionUri: string, shouldNotify?: bool
};
export const doChannelSubscribe = (subscription: Subscription) => (
dispatch: ReduxDispatch,
dispatch: SubscriptionDispatch,
getState: GetState
) => {
const {
@ -275,7 +267,7 @@ export const doChannelSubscribe = (subscription: Subscription) => (
};
export const doChannelUnsubscribe = (subscription: Subscription) => (
dispatch: ReduxDispatch,
dispatch: SubscriptionDispatch,
getState: GetState
) => {
const {
@ -296,7 +288,7 @@ export const doChannelUnsubscribe = (subscription: Subscription) => (
}
};
export const doCheckSubscriptions = () => (dispatch: ReduxDispatch, getState: GetState) => {
export const doCheckSubscriptions = () => (dispatch: SubscriptionDispatch, getState: GetState) => {
const state = getState();
const subscriptions = selectSubscriptions(state);
@ -305,7 +297,10 @@ export const doCheckSubscriptions = () => (dispatch: ReduxDispatch, getState: Ge
});
};
export const doFetchMySubscriptions = () => (dispatch: ReduxDispatch, getState: GetState) => {
export const doFetchMySubscriptions = () => (
dispatch: SubscriptionDispatch,
getState: GetState
) => {
const state: { subscriptions: SubscriptionState, settings: any } = getState();
const { subscriptions: reduxSubscriptions } = state.subscriptions;
@ -386,7 +381,7 @@ export const doFetchMySubscriptions = () => (dispatch: ReduxDispatch, getState:
});
};
export const doCheckSubscriptionsInit = () => (dispatch: ReduxDispatch) => {
export const doCheckSubscriptionsInit = () => (dispatch: SubscriptionDispatch) => {
// doCheckSubscriptionsInit is called by doDaemonReady
// setTimeout below is a hack to ensure redux is hydrated when subscriptions are checked
// this will be replaced with <PersistGate> which reqiures a package upgrade
@ -402,7 +397,7 @@ export const doCheckSubscriptionsInit = () => (dispatch: ReduxDispatch) => {
setInterval(() => dispatch(doCheckSubscriptions()), CHECK_SUBSCRIPTIONS_INTERVAL);
};
export const doFetchRecommendedSubscriptions = () => (dispatch: ReduxDispatch) => {
export const doFetchRecommendedSubscriptions = () => (dispatch: SubscriptionDispatch) => {
dispatch({
type: ACTIONS.GET_SUGGESTED_SUBSCRIPTIONS_START,
});
@ -422,18 +417,18 @@ export const doFetchRecommendedSubscriptions = () => (dispatch: ReduxDispatch) =
);
};
export const doCompleteFirstRun = () => (dispatch: ReduxDispatch) =>
export const doCompleteFirstRun = () => (dispatch: SubscriptionDispatch) =>
dispatch({
type: ACTIONS.SUBSCRIPTION_FIRST_RUN_COMPLETED,
});
export const doShowSuggestedSubs = () => (dispatch: ReduxDispatch) =>
export const doShowSuggestedSubs = () => (dispatch: SubscriptionDispatch) =>
dispatch({
type: ACTIONS.VIEW_SUGGESTED_SUBSCRIPTIONS,
});
export const doChannelSubscriptionEnableNotifications = (channelName: string) => (
dispatch: ReduxDispatch
dispatch: SubscriptionDispatch
) =>
dispatch({
type: ACTIONS.CHANNEL_SUBSCRIPTION_ENABLE_NOTIFICATIONS,
@ -441,7 +436,7 @@ export const doChannelSubscriptionEnableNotifications = (channelName: string) =>
});
export const doChannelSubscriptionDisableNotifications = (channelName: string) => (
dispatch: ReduxDispatch
dispatch: SubscriptionDispatch
) =>
dispatch({
type: ACTIONS.CHANNEL_SUBSCRIPTION_DISABLE_NOTIFICATIONS,

View file

@ -0,0 +1,58 @@
import {
ACTIONS as LBRY_REDUX_ACTIONS,
makeSelectIsFollowingTag,
selectFollowedTags,
} from 'lbry-redux';
import Lbryio from 'lbryio';
import * as ACTIONS from 'constants/action_types';
import { selectSubscriptions } from 'redux/selectors/subscriptions';
const persistShape = {
version: '0',
shared: {},
};
export function userStateSyncMiddleware() {
return ({ getState }) => next => action => {
if (
action.type === ACTIONS.CHANNEL_SUBSCRIBE ||
action.type === ACTIONS.CHANNEL_UNSUBSCRIBE ||
action.type === LBRY_REDUX_ACTIONS.TOGGLE_TAG_FOLLOW
) {
const newShape = { ...persistShape };
const state = getState();
const subscriptions = selectSubscriptions(state).map(({ uri }) => uri);
const tags = selectFollowedTags(state);
newShape.shared.subscriptions = subscriptions;
newShape.shared.tags = tags;
const { uri } = action.data;
if (action.type === ACTIONS.CHANNEL_SUBSCRIBE) {
const newSubscriptions = subscriptions.slice();
newSubscriptions.push(uri);
newShape.shared.subscriptions = newSubscriptions;
} else if (action.type === ACTIONS.CHANNEL_UNSUBSCRIBE) {
let newSubscriptions = subscriptions.slice();
newSubscriptions = newSubscriptions.filter(subscribedUri => subscribedUri !== uri);
newShape.shared.subscriptions = newSubscriptions;
} else {
const toggledTag = action.data.name;
const followedTags = selectFollowedTags(state).map(({ name }) => name);
const isFollowing = makeSelectIsFollowingTag(toggledTag)(state);
let newTags = followedTags.slice();
if (isFollowing) {
newTags = newTags.filter(followedTag => followedTag.name !== toggledTag);
} else {
newTags.push(toggledTag);
}
newShape.shared.tags = newTags;
}
Lbryio.call('user_settings', 'set', { settings: newShape });
}
return next(action);
};
}

View file

@ -2,20 +2,6 @@
import * as ACTIONS from 'constants/action_types';
import { VIEW_ALL } from 'constants/subscriptions';
import { handleActions } from 'util/redux-utils';
import type {
SubscriptionState,
Subscription,
DoChannelSubscribe,
DoChannelUnsubscribe,
DoChannelSubscriptionEnableNotifications,
DoChannelSubscriptionDisableNotifications,
SetSubscriptionLatest,
DoUpdateSubscriptionUnreads,
DoRemoveSubscriptionUnreads,
FetchedSubscriptionsSucess,
SetViewMode,
GetSuggestedSubscriptionsSuccess,
} from 'types/subscription';
const defaultState: SubscriptionState = {
enabledChannelNotifications: [],

View file

@ -46,6 +46,7 @@ reducers[ACTIONS.USER_FETCH_SUCCESS] = (state, action) =>
Object.assign({}, state, {
userIsPending: false,
user: action.data.user,
emailToVerify: action.data.user.has_verified_email ? null : state.emailToVerify,
});
reducers[ACTIONS.USER_FETCH_FAILURE] = state =>

View file

@ -93,6 +93,11 @@ export const selectIdentityVerifyErrorMessage = createSelector(
state => state.identityVerifyErrorMessage
);
export const selectUserVerifiedEmail = createSelector(
selectUser,
user => user && user.has_verified_email
);
export const selectUserIsVerificationCandidate = createSelector(
selectUser,
user => user && (!user.has_verified_email || !user.is_identity_verified)

View file

@ -5,7 +5,7 @@ const rewards = {};
rewards.TYPE_NEW_DEVELOPER = 'new_developer';
rewards.TYPE_NEW_USER = 'new_user';
rewards.TYPE_CONFIRM_EMAIL = 'verified_email';
rewards.TYPE_CONFIRM_EMAIL = 'email_provided';
rewards.TYPE_FIRST_CHANNEL = 'new_channel';
rewards.TYPE_FIRST_STREAM = 'first_stream';
rewards.TYPE_MANY_DOWNLOADS = 'many_downloads';

View file

@ -20,6 +20,9 @@ module.exports = {
},
resolve: {
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
alias: {
'flow-typed': path.resolve(__dirname, './flow-typed'),
},
},
externals: 'lbry-redux',
};

226
yarn.lock
View file

@ -642,6 +642,27 @@
lodash "^4.17.11"
to-fast-properties "^2.0.0"
"@nodelib/fs.scandir@2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.2.tgz#1f981cd5b83e85cfdeb386fc693d4baab392fa54"
integrity sha512-wrIBsjA5pl13f0RN4Zx4FNWmU71lv03meGKnqRUoCyan17s4V3WL92f3w3AIuWbNnpcrQyFBU5qMavJoB8d27w==
dependencies:
"@nodelib/fs.stat" "2.0.2"
run-parallel "^1.1.9"
"@nodelib/fs.stat@2.0.2", "@nodelib/fs.stat@^2.0.1":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.2.tgz#2762aea8fe78ea256860182dcb52d61ee4b8fda6"
integrity sha512-z8+wGWV2dgUhLqrtRYa03yDx4HWMvXKi1z8g3m2JyxAx8F7xk74asqPk5LAETjqDSGLFML/6CDl0+yFunSYicw==
"@nodelib/fs.walk@^1.2.1":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.3.tgz#a555dc256acaf00c62b0db29529028dd4d4cb141"
integrity sha512-l6t8xEhfK9Sa4YO5mIRdau7XSOADfmh3jCr0evNHdY+HNkW6xuQhgMH7D73VV6WpZOagrW0UludvMTiifiwTfA==
dependencies:
"@nodelib/fs.scandir" "2.1.2"
fastq "^1.6.0"
"@samverschueren/stream-to-observable@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
@ -652,6 +673,37 @@
version "0.0.39"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
"@types/events@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
"@types/fs-extra@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.0.0.tgz#d3e2c313ca29f95059f198dd60d1f774642d4b25"
integrity sha512-bCtL5v9zdbQW86yexOlXWTEGvLNqWxMFyi7gQA7Gcthbezr2cPSOb8SkESVKA937QD5cIwOFLDFt0MQoXOEr9Q==
dependencies:
"@types/node" "*"
"@types/glob@^7.1.1":
version "7.1.1"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
dependencies:
"@types/events" "*"
"@types/minimatch" "*"
"@types/node" "*"
"@types/minimatch@*":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/node@*":
version "12.7.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.5.tgz#e19436e7f8e9b4601005d73673b6dc4784ffcc2f"
integrity sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==
"@types/node@^11.11.6":
version "11.13.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.0.tgz#b0df8d6ef9b5001b2be3a94d909ce3c29a80f9e1"
@ -974,6 +1026,11 @@ array-union@^1.0.1:
dependencies:
array-uniq "^1.0.1"
array-union@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
array-uniq@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
@ -1154,6 +1211,13 @@ braces@^2.3.1, braces@^2.3.2:
split-string "^3.0.2"
to-regex "^3.0.1"
braces@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
brorand@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
@ -1477,6 +1541,11 @@ color-name@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689"
colorette@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.1.0.tgz#1f943e5a357fac10b4e0f5aaef3b14cdc1af6ec7"
integrity sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg==
colors@^1.1.2:
version "1.3.0"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.0.tgz#5f20c9fef6945cb1134260aab33bfbdc8295e04e"
@ -1790,6 +1859,13 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
dependencies:
path-type "^4.0.0"
doctrine@1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
@ -2201,6 +2277,18 @@ fast-diff@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154"
fast-glob@^3.0.3:
version "3.0.4"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.0.4.tgz#d484a41005cb6faeb399b951fd1bd70ddaebb602"
integrity sha512-wkIbV6qg37xTJwqSsdnIphL1e+LaGz4AIQqr00mIubMaEhv1/HEmJ0uuCGZRNRUkZZmOB5mJKO0ZUTVq+SxMQg==
dependencies:
"@nodelib/fs.stat" "^2.0.1"
"@nodelib/fs.walk" "^1.2.1"
glob-parent "^5.0.0"
is-glob "^4.0.1"
merge2 "^1.2.3"
micromatch "^4.0.2"
fast-json-stable-stringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
@ -2209,6 +2297,13 @@ fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
fastq@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2"
integrity sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==
dependencies:
reusify "^1.0.0"
figgy-pudding@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
@ -2243,6 +2338,13 @@ fill-range@^4.0.0:
repeat-string "^1.6.1"
to-regex-range "^2.1.0"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
find-babel-config@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.1.0.tgz#acc01043a6749fec34429be6b64f542ebb5d6355"
@ -2370,6 +2472,15 @@ fs-extra@^5.0.0:
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-minipass@^1.2.5:
version "1.2.6"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07"
@ -2476,6 +2587,13 @@ glob-parent@^3.1.0:
is-glob "^3.1.0"
path-dirname "^1.0.0"
glob-parent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954"
integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==
dependencies:
is-glob "^4.0.1"
glob@^7.0.3, glob@^7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
@ -2538,6 +2656,20 @@ globals@^11.0.1, globals@^11.1.0:
version "11.7.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.7.0.tgz#a583faa43055b1aca771914bf68258e2fc125673"
globby@10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.1.tgz#4782c34cb75dd683351335c5829cc3420e606b22"
integrity sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==
dependencies:
"@types/glob" "^7.1.1"
array-union "^2.1.0"
dir-glob "^3.0.1"
fast-glob "^3.0.3"
glob "^7.1.3"
ignore "^5.1.1"
merge2 "^1.2.3"
slash "^3.0.0"
globby@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d"
@ -2568,7 +2700,7 @@ got@^7.1.0:
url-parse-lax "^1.0.0"
url-to-options "^1.0.1"
graceful-fs@^4.1.11, graceful-fs@^4.1.15:
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.2.0:
version "4.2.2"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02"
integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==
@ -2717,6 +2849,11 @@ ignore@^3.3.3:
version "3.3.10"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
ignore@^5.1.1:
version "5.1.4"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf"
integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==
import-local@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d"
@ -2945,12 +3082,24 @@ is-glob@^4.0.0:
dependencies:
is-extglob "^2.1.1"
is-glob@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
dependencies:
is-extglob "^2.1.1"
is-number@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
dependencies:
kind-of "^3.0.2"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-obj@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
@ -2992,6 +3141,13 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
dependencies:
isobject "^3.0.1"
is-plain-object@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.0.tgz#47bfc5da1b5d50d64110806c199359482e75a928"
integrity sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==
dependencies:
isobject "^4.0.0"
is-promise@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
@ -3072,6 +3228,11 @@ isobject@^3.0.0, isobject@^3.0.1:
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
isobject@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0"
integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==
isurl@^1.0.0-alpha5:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67"
@ -3468,6 +3629,11 @@ memory-fs@^0.4.0, memory-fs@^0.4.1:
errno "^0.1.3"
readable-stream "^2.0.1"
merge2@^1.2.3:
version "1.3.0"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81"
integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==
micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
@ -3486,6 +3652,14 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8:
snapdragon "^0.8.1"
to-regex "^3.0.2"
micromatch@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
dependencies:
braces "^3.0.1"
picomatch "^2.0.5"
miller-rabin@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
@ -4073,6 +4247,11 @@ path-type@^2.0.0:
dependencies:
pify "^2.0.0"
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
pbkdf2@^3.0.3:
version "3.0.16"
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.16.tgz#7404208ec6b01b62d85bf83853a8064f8d9c2a5c"
@ -4083,6 +4262,11 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
picomatch@^2.0.5:
version "2.0.7"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6"
integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==
pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@ -4496,6 +4680,11 @@ ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
reusify@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rimraf@2, rimraf@^2.2.8, rimraf@^2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
@ -4516,6 +4705,13 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
rollup-plugin-alias@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-alias/-/rollup-plugin-alias-2.0.0.tgz#eea77466a8a8a063007c8edb2e9d7a3d06cbb889"
integrity sha512-JVwxV9nwzjc0q7JmrV9jvZlJ8FykNd5r7RmYps8i/7v+vcmmVpeMvXrljhCboYErf4VZ2z9FEj+fP7eJlEGfug==
dependencies:
slash "^3.0.0"
rollup-plugin-babel@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-babel/-/rollup-plugin-babel-4.3.2.tgz#8c0e1bd7aa9826e90769cf76895007098ffd1413"
@ -4523,6 +4719,17 @@ rollup-plugin-babel@^4.3.2:
"@babel/helper-module-imports" "^7.0.0"
rollup-pluginutils "^2.3.0"
rollup-plugin-copy@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-copy/-/rollup-plugin-copy-3.1.0.tgz#435589857f4e346ea63fc137d99dcc1b25f51c3b"
integrity sha512-oVw3ljRV5jv7Yw/6eCEHntVs9Mc+NFglc0iU0J8ei76gldYmtBQ0M/j6WAkZUFVRSrhgfCrEakUllnN87V2f4w==
dependencies:
"@types/fs-extra" "^8.0.0"
colorette "^1.1.0"
fs-extra "^8.1.0"
globby "10.0.1"
is-plain-object "^3.0.0"
rollup-plugin-flow@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/rollup-plugin-flow/-/rollup-plugin-flow-1.1.1.tgz#6ce568f1dd559666b77ab76b4bae251407528db6"
@ -4562,6 +4769,11 @@ run-async@^2.2.0:
dependencies:
is-promise "^2.1.0"
run-parallel@^1.1.9:
version "1.1.9"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679"
integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==
run-queue@^1.0.0, run-queue@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
@ -4682,6 +4894,11 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
slice-ansi@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
@ -5064,6 +5281,13 @@ to-regex-range@^2.1.0:
is-number "^3.0.0"
repeat-string "^1.6.1"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
to-regex@^3.0.1, to-regex@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"