From a22d0270d08cc87f647bdc6eae61a14ff5bba2aa Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Sun, 9 Jun 2019 22:45:47 -0400 Subject: [PATCH] add tags --- .eslintrc.json | 1 + dist/bundle.es.js | 453 +++++++++++++++++++++++-------- dist/flow-typed/Tags.js | 30 ++ flow-typed/Claim.js | 3 +- flow-typed/Tags.js | 30 ++ src/constants/action_types.js | 25 +- src/constants/tags.js | 21 ++ src/index.js | 12 + src/redux/actions/claims.js | 47 +++- src/redux/actions/tags.js | 51 ++++ src/redux/reducers/claims.js | 20 +- src/redux/reducers/tags.js | 84 ++++++ src/redux/selectors/claims.js | 8 + src/redux/selectors/file_info.js | 6 + src/redux/selectors/tags.js | 47 ++++ 15 files changed, 705 insertions(+), 133 deletions(-) create mode 100644 dist/flow-typed/Tags.js create mode 100644 flow-typed/Tags.js create mode 100644 src/constants/tags.js create mode 100644 src/redux/actions/tags.js create mode 100644 src/redux/reducers/tags.js create mode 100644 src/redux/selectors/tags.js diff --git a/.eslintrc.json b/.eslintrc.json index 9ac4855..1860ea4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -10,6 +10,7 @@ "__": true }, "rules": { + "camelcase": 0, "no-multi-spaces": 0, "new-cap": 0, "prefer-promise-reject-errors": 0, diff --git a/dist/bundle.es.js b/dist/bundle.es.js index b2f04b7..129ae72 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -98,6 +98,9 @@ const SET_CONTENT_POSITION = 'SET_CONTENT_POSITION'; const SET_CONTENT_LAST_VIEWED = 'SET_CONTENT_LAST_VIEWED'; const CLEAR_CONTENT_HISTORY_URI = 'CLEAR_CONTENT_HISTORY_URI'; const CLEAR_CONTENT_HISTORY_ALL = 'CLEAR_CONTENT_HISTORY_ALL'; +const CLAIM_SEARCH_STARTED = 'CLAIM_SEARCH_STARTED'; +const CLAIM_SEARCH_COMPLETED = 'CLAIM_SEARCH_COMPLETED'; +const CLAIM_SEARCH_FAILED = 'CLAIM_SEARCH_FAILED'; // Files const FILE_LIST_STARTED = 'FILE_LIST_STARTED'; @@ -187,21 +190,6 @@ const FETCH_REWARD_CONTENT_COMPLETED = 'FETCH_REWARD_CONTENT_COMPLETED'; const DOWNLOAD_LANGUAGE_SUCCEEDED = 'DOWNLOAD_LANGUAGE_SUCCEEDED'; const DOWNLOAD_LANGUAGE_FAILED = 'DOWNLOAD_LANGUAGE_FAILED'; -// ShapeShift -const GET_SUPPORTED_COINS_START = 'GET_SUPPORTED_COINS_START'; -const GET_SUPPORTED_COINS_SUCCESS = 'GET_SUPPORTED_COINS_SUCCESS'; -const GET_SUPPORTED_COINS_FAIL = 'GET_SUPPORTED_COINS_FAIL'; -const GET_COIN_STATS_START = 'GET_COIN_STATS_START'; -const GET_COIN_STATS_SUCCESS = 'GET_COIN_STATS_SUCCESS'; -const GET_COIN_STATS_FAIL = 'GET_COIN_STATS_FAIL'; -const PREPARE_SHAPE_SHIFT_START = 'PREPARE_SHAPE_SHIFT_START'; -const PREPARE_SHAPE_SHIFT_SUCCESS = 'PREPARE_SHAPE_SHIFT_SUCCESS'; -const PREPARE_SHAPE_SHIFT_FAIL = 'PREPARE_SHAPE_SHIFT_FAIL'; -const GET_ACTIVE_SHIFT_START = 'GET_ACTIVE_SHIFT_START'; -const GET_ACTIVE_SHIFT_SUCCESS = 'GET_ACTIVE_SHIFT_SUCCESS'; -const GET_ACTIVE_SHIFT_FAIL = 'GET_ACTIVE_SHIFT_FAIL'; -const CLEAR_SHAPE_SHIFT = 'CLEAR_SHAPE_SHIFT'; - // Subscriptions const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE'; const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE'; @@ -239,6 +227,13 @@ const FETCH_DATE = 'FETCH_DATE'; const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED'; const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED'; const FETCH_COST_INFO_FAILED = 'FETCH_COST_INFO_FAILED'; +// Tags +const TOGGLE_TAG_FOLLOW = 'TOGGLE_TAG_FOLLOW'; +const TAG_ADD = 'TAG_ADD'; +const TAG_DELETE = 'TAG_DELETE'; +const FETCH_TRENDING_STARTED = 'FETCH_TRENDING_STARTED'; +const FETCH_TRENDING_COMPLETED = 'FETCH_TRENDING_COMPLETED'; +const FETCH_TRENDING_FAILED = 'FETCH_TRENDING_FAILED'; var action_types = /*#__PURE__*/Object.freeze({ WINDOW_FOCUSED: WINDOW_FOCUSED, @@ -323,6 +318,9 @@ var action_types = /*#__PURE__*/Object.freeze({ SET_CONTENT_LAST_VIEWED: SET_CONTENT_LAST_VIEWED, CLEAR_CONTENT_HISTORY_URI: CLEAR_CONTENT_HISTORY_URI, CLEAR_CONTENT_HISTORY_ALL: CLEAR_CONTENT_HISTORY_ALL, + CLAIM_SEARCH_STARTED: CLAIM_SEARCH_STARTED, + CLAIM_SEARCH_COMPLETED: CLAIM_SEARCH_COMPLETED, + CLAIM_SEARCH_FAILED: CLAIM_SEARCH_FAILED, FILE_LIST_STARTED: FILE_LIST_STARTED, FILE_LIST_SUCCEEDED: FILE_LIST_SUCCEEDED, FETCH_FILE_INFO_STARTED: FETCH_FILE_INFO_STARTED, @@ -399,19 +397,6 @@ var action_types = /*#__PURE__*/Object.freeze({ FETCH_REWARD_CONTENT_COMPLETED: FETCH_REWARD_CONTENT_COMPLETED, DOWNLOAD_LANGUAGE_SUCCEEDED: DOWNLOAD_LANGUAGE_SUCCEEDED, DOWNLOAD_LANGUAGE_FAILED: DOWNLOAD_LANGUAGE_FAILED, - GET_SUPPORTED_COINS_START: GET_SUPPORTED_COINS_START, - GET_SUPPORTED_COINS_SUCCESS: GET_SUPPORTED_COINS_SUCCESS, - GET_SUPPORTED_COINS_FAIL: GET_SUPPORTED_COINS_FAIL, - GET_COIN_STATS_START: GET_COIN_STATS_START, - GET_COIN_STATS_SUCCESS: GET_COIN_STATS_SUCCESS, - GET_COIN_STATS_FAIL: GET_COIN_STATS_FAIL, - PREPARE_SHAPE_SHIFT_START: PREPARE_SHAPE_SHIFT_START, - PREPARE_SHAPE_SHIFT_SUCCESS: PREPARE_SHAPE_SHIFT_SUCCESS, - PREPARE_SHAPE_SHIFT_FAIL: PREPARE_SHAPE_SHIFT_FAIL, - GET_ACTIVE_SHIFT_START: GET_ACTIVE_SHIFT_START, - GET_ACTIVE_SHIFT_SUCCESS: GET_ACTIVE_SHIFT_SUCCESS, - GET_ACTIVE_SHIFT_FAIL: GET_ACTIVE_SHIFT_FAIL, - CLEAR_SHAPE_SHIFT: CLEAR_SHAPE_SHIFT, CHANNEL_SUBSCRIBE: CHANNEL_SUBSCRIBE, CHANNEL_UNSUBSCRIBE: CHANNEL_UNSUBSCRIBE, HAS_FETCHED_SUBSCRIPTIONS: HAS_FETCHED_SUBSCRIPTIONS, @@ -440,7 +425,13 @@ var action_types = /*#__PURE__*/Object.freeze({ FETCH_DATE: FETCH_DATE, FETCH_COST_INFO_STARTED: FETCH_COST_INFO_STARTED, FETCH_COST_INFO_COMPLETED: FETCH_COST_INFO_COMPLETED, - FETCH_COST_INFO_FAILED: FETCH_COST_INFO_FAILED + FETCH_COST_INFO_FAILED: FETCH_COST_INFO_FAILED, + TOGGLE_TAG_FOLLOW: TOGGLE_TAG_FOLLOW, + TAG_ADD: TAG_ADD, + TAG_DELETE: TAG_DELETE, + FETCH_TRENDING_STARTED: FETCH_TRENDING_STARTED, + FETCH_TRENDING_COMPLETED: FETCH_TRENDING_COMPLETED, + FETCH_TRENDING_FAILED: FETCH_TRENDING_FAILED }); const API_DOWN = 'apiDown'; @@ -1428,6 +1419,10 @@ const makeSelectChannelForClaimUri = (uri, includePrefix = false) => reselect.cr return includePrefix ? `lbry://${channel}` : channel; }); +const makeSelectTagsForUri = uri => reselect.createSelector(makeSelectMetadataForUri(uri), metadata => { + return metadata && metadata.tags || []; +}); + const selectState$2 = state => state.wallet || {}; const selectWalletState = selectState$2; @@ -1946,6 +1941,14 @@ function doUpdateBlockHeight() { }); } +// https://github.com/reactjs/redux/issues/911 +function batchActions(...actions) { + return { + type: 'BATCH_ACTIONS', + actions + }; +} + var _extends$3 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; function doResolveUris(uris, returnCachedClaims = false) { @@ -1997,10 +2000,13 @@ function doResolveUris(uris, returnCachedClaims = false) { result.stream = uriResolveInfo; if (uriResolveInfo.signing_channel) { result.channel = uriResolveInfo.signing_channel; +<<<<<<< HEAD result.claimsInChannel = uriResolveInfo.signing_channel.meta && uriResolveInfo.signing_channel.meta.claims_in_channel || 0; +======= + result.claimsInChannel = uriResolveInfo.meta && uriResolveInfo.meta.claims_in_channel || 0; +>>>>>>> add tags } } - // $FlowFixMe resolveInfo[uri] = result; } @@ -2119,7 +2125,11 @@ function doFetchClaimsByChannel(uri, page = 1) { lbryProxy.claim_search({ channel: uri, +<<<<<<< HEAD valid_channel_signatures: true, +======= + is_controlling: true, +>>>>>>> add tags page: page || 1, order_by: ['release_time'] }).then(result => { @@ -2181,6 +2191,46 @@ function doFetchChannelListMine() { }; } +function doClaimSearch(amount = 20, options = {}, cb) { + return dispatch => { + dispatch({ + type: CLAIM_SEARCH_STARTED + }); + + const success = data => { + const resolveInfo = {}; + const uris = []; + data.items.forEach(stream => { + resolveInfo[stream.permanent_url] = { stream }; + uris.push(stream.permanent_url); + }); + + dispatch({ + type: CLAIM_SEARCH_COMPLETED, + data: { resolveInfo } + }); + + if (cb) { + cb(null, uris); + } + }; + + const failure = err => { + dispatch({ + type: CLAIM_SEARCH_FAILED, + error: err + }); + if (cb) { + cb(err); + } + }; + + lbryProxy.claim_search(_extends$3({ + page_size: amount + }, options)).then(success, failure); + }; +} + const selectState$3 = state => state.fileInfo || {}; const selectFileInfosByOutpoint = reselect.createSelector(selectState$3, state => state.byOutpoint || {}); @@ -2344,6 +2394,10 @@ const selectFileListPublishedSort = reselect.createSelector(selectState$3, state const selectFileListDownloadedSort = reselect.createSelector(selectState$3, state => state.fileListDownloadedSort); +const selectDownloadedUris = reselect.createSelector(selectFileInfosDownloaded, +// We should use permament_url but it doesn't exist in file_list +info => info.map(claim => `lbry://${claim.claim_name}#${claim.claim_id}`)); + // const selectState$4 = state => state.file || {}; @@ -2522,14 +2576,6 @@ function doSetFileListSort(page, value) { }; } -// https://github.com/reactjs/redux/issues/911 -function batchActions(...actions) { - return { - type: 'BATCH_ACTIONS', - actions - }; -} - // Returns a function, that, as long as it continues to be invoked, will not // be triggered. The function will be called after it stops being called for // N milliseconds. If `immediate` is passed, trigger the function on the @@ -2709,6 +2755,55 @@ function savePosition(claimId, outpoint, position) { // +const doToggleTagFollow = name => ({ + type: TOGGLE_TAG_FOLLOW, + data: { + name + } +}); + +const doAddTag = name => ({ + type: TAG_ADD, + data: { + name + } +}); + +const doDeleteTag = name => ({ + type: TAG_DELETE, + data: { + name + } +}); + +const doFetchByTags = (amount = 10, options = {}) => { + return dispatch => { + dispatch({ + type: FETCH_TRENDING_STARTED + }); + + const callback = (error, uris = []) => { + if (error) { + return dispatch({ + type: FETCH_TRENDING_FAILED, + error + }); + } + + dispatch({ + type: FETCH_TRENDING_COMPLETED, + data: { + uris + } + }); + }; + + dispatch(doClaimSearch(amount, options, callback)); + }; +}; + +var _extends$4 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + const reducers = {}; const defaultState = { byId: {}, @@ -2725,7 +2820,7 @@ const defaultState = { pendingById: {} }; -reducers[RESOLVE_URIS_COMPLETED] = (state, action) => { +function handleClaimAction(state, action) { const { resolveInfo } = action.data; @@ -2762,6 +2857,10 @@ reducers[RESOLVE_URIS_COMPLETED] = (state, action) => { channelClaimCounts, resolvingUris: (state.resolvingUris || []).filter(uri => !resolveInfo[uri]) }); +} + +reducers[RESOLVE_URIS_COMPLETED] = (state, action) => { + return handleClaimAction(state, action); }; reducers[FETCH_CLAIM_LIST_MINE_STARTED] = state => Object.assign({}, state, { @@ -2936,13 +3035,27 @@ reducers[RESOLVE_URIS_STARTED] = (state, action) => { }); }; +reducers[CLAIM_SEARCH_STARTED] = state => { + return Object.assign({}, state, { + fetchingClaimSearch: true + }); +}; +reducers[CLAIM_SEARCH_COMPLETED] = (state, action) => { + return _extends$4({}, handleClaimAction(state, action), { fetchingClaimSearch: false }); +}; +reducers[CLAIM_SEARCH_FAILED] = state => { + return Object.assign({}, state, { + fetchingClaimSearch: false + }); +}; + function claimsReducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); return state; } -var _extends$4 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; +var _extends$5 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const reducers$1 = {}; const defaultState$1 = { @@ -2959,7 +3072,7 @@ reducers$1[PURCHASE_URI_STARTED] = (state, action) => { newFailedPurchaseUris.splice(newFailedPurchaseUris.indexOf(uri), 1); } - return _extends$4({}, state, { + return _extends$5({}, state, { failedPurchaseUris: newFailedPurchaseUris, purchaseUriErrorMessage: '' }); @@ -2981,7 +3094,7 @@ reducers$1[PURCHASE_URI_COMPLETED] = (state, action) => { newPurchasedStreamingUrls[uri] = streamingUrl; } - return _extends$4({}, state, { + return _extends$5({}, state, { failedPurchaseUris: newFailedPurchaseUris, purchasedUris: newPurchasedUris, purchasedStreamingUrls: newPurchasedStreamingUrls, @@ -2997,7 +3110,7 @@ reducers$1[PURCHASE_URI_FAILED] = (state, action) => { newFailedPurchaseUris.push(uri); } - return _extends$4({}, state, { + return _extends$5({}, state, { failedPurchaseUris: newFailedPurchaseUris, purchaseUriErrorMessage: error }); @@ -3010,7 +3123,7 @@ reducers$1[DELETE_PURCHASED_URI] = (state, action) => { newPurchasedUris.splice(newPurchasedUris.indexOf(uri), 1); } - return _extends$4({}, state, { + return _extends$5({}, state, { purchasedUris: newPurchasedUris }); }; @@ -3021,7 +3134,7 @@ function fileReducer(state = defaultState$1, action) { return state; } -var _extends$5 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; +var _extends$6 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const reducers$2 = {}; const defaultState$2 = { @@ -3157,12 +3270,12 @@ reducers$2[LOADING_VIDEO_STARTED] = (state, action) => { const newLoading = Object.assign({}, state.urisLoading); newLoading[uri] = true; - const newErrors = _extends$5({}, state.errors); + const newErrors = _extends$6({}, state.errors); if (uri in newErrors) delete newErrors[uri]; return Object.assign({}, state, { urisLoading: newLoading, - errors: _extends$5({}, newErrors) + errors: _extends$6({}, newErrors) }); }; @@ -3172,12 +3285,12 @@ reducers$2[LOADING_VIDEO_FAILED] = (state, action) => { const newLoading = Object.assign({}, state.urisLoading); delete newLoading[uri]; - const newErrors = _extends$5({}, state.errors); + const newErrors = _extends$6({}, state.errors); newErrors[uri] = true; return Object.assign({}, state, { urisLoading: newLoading, - errors: _extends$5({}, newErrors) + errors: _extends$6({}, newErrors) }); }; @@ -3218,7 +3331,7 @@ const handleActions = (actionMap, defaultState) => (state = defaultState, action return state; }; -var _extends$6 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; +var _extends$7 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const defaultState$3 = { notifications: [], @@ -3233,7 +3346,7 @@ const notificationsReducer = handleActions({ const newToasts = state.toasts.slice(); newToasts.push(toast); - return _extends$6({}, state, { + return _extends$7({}, state, { toasts: newToasts }); }, @@ -3241,7 +3354,7 @@ const notificationsReducer = handleActions({ const newToasts = state.toasts.slice(); newToasts.shift(); - return _extends$6({}, state, { + return _extends$7({}, state, { toasts: newToasts }); }, @@ -3252,7 +3365,7 @@ const notificationsReducer = handleActions({ const newNotifications = state.notifications.slice(); newNotifications.push(notification); - return _extends$6({}, state, { + return _extends$7({}, state, { notifications: newNotifications }); }, @@ -3263,7 +3376,7 @@ const notificationsReducer = handleActions({ notifications = notifications.map(pastNotification => pastNotification.id === notification.id ? notification : pastNotification); - return _extends$6({}, state, { + return _extends$7({}, state, { notifications }); }, @@ -3272,7 +3385,7 @@ const notificationsReducer = handleActions({ let newNotifications = state.notifications.slice(); newNotifications = newNotifications.filter(notification => notification.id !== id); - return _extends$6({}, state, { + return _extends$7({}, state, { notifications: newNotifications }); }, @@ -3283,7 +3396,7 @@ const notificationsReducer = handleActions({ const newErrors = state.errors.slice(); newErrors.push(error); - return _extends$6({}, state, { + return _extends$7({}, state, { errors: newErrors }); }, @@ -3291,13 +3404,13 @@ const notificationsReducer = handleActions({ const newErrors = state.errors.slice(); newErrors.shift(); - return _extends$6({}, state, { + return _extends$7({}, state, { errors: newErrors }); } }, defaultState$3); -var _extends$7 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; +var _extends$8 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const defaultState$4 = { isActive: false, // does the user have any typed text in the search input @@ -3317,29 +3430,29 @@ const defaultState$4 = { }; const searchReducer = handleActions({ - [SEARCH_START]: state => _extends$7({}, state, { + [SEARCH_START]: state => _extends$8({}, state, { searching: true }), [SEARCH_SUCCESS]: (state, action) => { const { query, uris } = action.data; - return _extends$7({}, state, { + return _extends$8({}, state, { searching: false, urisByQuery: Object.assign({}, state.urisByQuery, { [query]: uris }) }); }, - [SEARCH_FAIL]: state => _extends$7({}, state, { + [SEARCH_FAIL]: state => _extends$8({}, state, { searching: false }), - [UPDATE_SEARCH_QUERY]: (state, action) => _extends$7({}, state, { + [UPDATE_SEARCH_QUERY]: (state, action) => _extends$8({}, state, { searchQuery: action.data.query, isActive: true }), - [UPDATE_SEARCH_SUGGESTIONS]: (state, action) => _extends$7({}, state, { - suggestions: _extends$7({}, state.suggestions, { + [UPDATE_SEARCH_SUGGESTIONS]: (state, action) => _extends$8({}, state, { + suggestions: _extends$8({}, state.suggestions, { [action.data.query]: action.data.suggestions }) }), @@ -3347,27 +3460,27 @@ const searchReducer = handleActions({ // sets isActive to false so the uri will be populated correctly if the // user is on a file page. The search query will still be present on any // other page - [DISMISS_NOTIFICATION]: state => _extends$7({}, state, { + [DISMISS_NOTIFICATION]: state => _extends$8({}, state, { isActive: false }), - [SEARCH_FOCUS]: state => _extends$7({}, state, { + [SEARCH_FOCUS]: state => _extends$8({}, state, { focused: true }), - [SEARCH_BLUR]: state => _extends$7({}, state, { + [SEARCH_BLUR]: state => _extends$8({}, state, { focused: false }), [UPDATE_SEARCH_OPTIONS]: (state, action) => { const { options: oldOptions } = state; const newOptions = action.data; - const options = _extends$7({}, oldOptions, newOptions); - return _extends$7({}, state, { + const options = _extends$8({}, oldOptions, newOptions); + return _extends$8({}, state, { options }); } }, defaultState$4); -var _extends$8 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; +var _extends$9 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const buildDraftTransaction = () => ({ amount: undefined, @@ -3407,25 +3520,25 @@ const defaultState$5 = { }; const walletReducer = handleActions({ - [FETCH_TRANSACTIONS_STARTED]: state => _extends$8({}, state, { + [FETCH_TRANSACTIONS_STARTED]: state => _extends$9({}, state, { fetchingTransactions: true }), [FETCH_TRANSACTIONS_COMPLETED]: (state, action) => { - const byId = _extends$8({}, state.transactions); + const byId = _extends$9({}, state.transactions); const { transactions } = action.data; transactions.forEach(transaction => { byId[transaction.txid] = transaction; }); - return _extends$8({}, state, { + return _extends$9({}, state, { transactions: byId, fetchingTransactions: false }); }, - [FETCH_SUPPORTS_STARTED]: state => _extends$8({}, state, { + [FETCH_SUPPORTS_STARTED]: state => _extends$9({}, state, { fetchingSupports: true }), @@ -3438,7 +3551,7 @@ const walletReducer = handleActions({ byOutpoint[`${txid}:${nout}`] = transaction; }); - return _extends$8({}, state, { supports: byOutpoint, fetchingSupports: false }); + return _extends$9({}, state, { supports: byOutpoint, fetchingSupports: false }); }, [ABANDON_SUPPORT_STARTED]: (state, action) => { @@ -3447,7 +3560,7 @@ const walletReducer = handleActions({ currentlyAbandoning[outpoint] = true; - return _extends$8({}, state, { + return _extends$9({}, state, { abandoningSupportsByOutpoint: currentlyAbandoning }); }, @@ -3460,56 +3573,56 @@ const walletReducer = handleActions({ delete currentlyAbandoning[outpoint]; delete byOutpoint[outpoint]; - return _extends$8({}, state, { + return _extends$9({}, state, { supports: byOutpoint, abandoningSupportsById: currentlyAbandoning }); }, - [GET_NEW_ADDRESS_STARTED]: state => _extends$8({}, state, { + [GET_NEW_ADDRESS_STARTED]: state => _extends$9({}, state, { gettingNewAddress: true }), [GET_NEW_ADDRESS_COMPLETED]: (state, action) => { const { address } = action.data; - return _extends$8({}, state, { gettingNewAddress: false, receiveAddress: address }); + return _extends$9({}, state, { gettingNewAddress: false, receiveAddress: address }); }, - [UPDATE_BALANCE]: (state, action) => _extends$8({}, state, { + [UPDATE_BALANCE]: (state, action) => _extends$9({}, state, { balance: action.data.balance }), - [UPDATE_TOTAL_BALANCE]: (state, action) => _extends$8({}, state, { + [UPDATE_TOTAL_BALANCE]: (state, action) => _extends$9({}, state, { totalBalance: action.data.totalBalance }), - [CHECK_ADDRESS_IS_MINE_STARTED]: state => _extends$8({}, state, { + [CHECK_ADDRESS_IS_MINE_STARTED]: state => _extends$9({}, state, { checkingAddressOwnership: true }), - [CHECK_ADDRESS_IS_MINE_COMPLETED]: state => _extends$8({}, state, { + [CHECK_ADDRESS_IS_MINE_COMPLETED]: state => _extends$9({}, state, { checkingAddressOwnership: false }), [SET_DRAFT_TRANSACTION_AMOUNT]: (state, action) => { const oldDraft = state.draftTransaction; - const newDraft = _extends$8({}, oldDraft, { amount: parseFloat(action.data.amount) }); + const newDraft = _extends$9({}, oldDraft, { amount: parseFloat(action.data.amount) }); - return _extends$8({}, state, { draftTransaction: newDraft }); + return _extends$9({}, state, { draftTransaction: newDraft }); }, [SET_DRAFT_TRANSACTION_ADDRESS]: (state, action) => { const oldDraft = state.draftTransaction; - const newDraft = _extends$8({}, oldDraft, { address: action.data.address }); + const newDraft = _extends$9({}, oldDraft, { address: action.data.address }); - return _extends$8({}, state, { draftTransaction: newDraft }); + return _extends$9({}, state, { draftTransaction: newDraft }); }, [SEND_TRANSACTION_STARTED]: state => { - const newDraftTransaction = _extends$8({}, state.draftTransaction, { sending: true }); + const newDraftTransaction = _extends$9({}, state.draftTransaction, { sending: true }); - return _extends$8({}, state, { draftTransaction: newDraftTransaction }); + return _extends$9({}, state, { draftTransaction: newDraftTransaction }); }, [SEND_TRANSACTION_COMPLETED]: state => Object.assign({}, state, { @@ -3522,108 +3635,108 @@ const walletReducer = handleActions({ error: action.data.error }); - return _extends$8({}, state, { draftTransaction: newDraftTransaction }); + return _extends$9({}, state, { draftTransaction: newDraftTransaction }); }, - [SUPPORT_TRANSACTION_STARTED]: state => _extends$8({}, state, { + [SUPPORT_TRANSACTION_STARTED]: state => _extends$9({}, state, { sendingSupport: true }), - [SUPPORT_TRANSACTION_COMPLETED]: state => _extends$8({}, state, { + [SUPPORT_TRANSACTION_COMPLETED]: state => _extends$9({}, state, { sendingSupport: false }), - [SUPPORT_TRANSACTION_FAILED]: (state, action) => _extends$8({}, state, { + [SUPPORT_TRANSACTION_FAILED]: (state, action) => _extends$9({}, state, { error: action.data.error, sendingSupport: false }), - [WALLET_STATUS_COMPLETED]: (state, action) => _extends$8({}, state, { + [WALLET_STATUS_COMPLETED]: (state, action) => _extends$9({}, state, { walletIsEncrypted: action.result }), - [WALLET_ENCRYPT_START]: state => _extends$8({}, state, { + [WALLET_ENCRYPT_START]: state => _extends$9({}, state, { walletEncryptPending: true, walletEncryptSucceded: null, walletEncryptResult: null }), - [WALLET_ENCRYPT_COMPLETED]: (state, action) => _extends$8({}, state, { + [WALLET_ENCRYPT_COMPLETED]: (state, action) => _extends$9({}, state, { walletEncryptPending: false, walletEncryptSucceded: true, walletEncryptResult: action.result }), - [WALLET_ENCRYPT_FAILED]: (state, action) => _extends$8({}, state, { + [WALLET_ENCRYPT_FAILED]: (state, action) => _extends$9({}, state, { walletEncryptPending: false, walletEncryptSucceded: false, walletEncryptResult: action.result }), - [WALLET_DECRYPT_START]: state => _extends$8({}, state, { + [WALLET_DECRYPT_START]: state => _extends$9({}, state, { walletDecryptPending: true, walletDecryptSucceded: null, walletDecryptResult: null }), - [WALLET_DECRYPT_COMPLETED]: (state, action) => _extends$8({}, state, { + [WALLET_DECRYPT_COMPLETED]: (state, action) => _extends$9({}, state, { walletDecryptPending: false, walletDecryptSucceded: true, walletDecryptResult: action.result }), - [WALLET_DECRYPT_FAILED]: (state, action) => _extends$8({}, state, { + [WALLET_DECRYPT_FAILED]: (state, action) => _extends$9({}, state, { walletDecryptPending: false, walletDecryptSucceded: false, walletDecryptResult: action.result }), - [WALLET_UNLOCK_START]: state => _extends$8({}, state, { + [WALLET_UNLOCK_START]: state => _extends$9({}, state, { walletUnlockPending: true, walletUnlockSucceded: null, walletUnlockResult: null }), - [WALLET_UNLOCK_COMPLETED]: (state, action) => _extends$8({}, state, { + [WALLET_UNLOCK_COMPLETED]: (state, action) => _extends$9({}, state, { walletUnlockPending: false, walletUnlockSucceded: true, walletUnlockResult: action.result }), - [WALLET_UNLOCK_FAILED]: (state, action) => _extends$8({}, state, { + [WALLET_UNLOCK_FAILED]: (state, action) => _extends$9({}, state, { walletUnlockPending: false, walletUnlockSucceded: false, walletUnlockResult: action.result }), - [WALLET_LOCK_START]: state => _extends$8({}, state, { + [WALLET_LOCK_START]: state => _extends$9({}, state, { walletLockPending: false, walletLockSucceded: null, walletLockResult: null }), - [WALLET_LOCK_COMPLETED]: (state, action) => _extends$8({}, state, { + [WALLET_LOCK_COMPLETED]: (state, action) => _extends$9({}, state, { walletLockPending: false, walletLockSucceded: true, walletLockResult: action.result }), - [WALLET_LOCK_FAILED]: (state, action) => _extends$8({}, state, { + [WALLET_LOCK_FAILED]: (state, action) => _extends$9({}, state, { walletLockPending: false, walletLockSucceded: false, walletLockResult: action.result }), - [SET_TRANSACTION_LIST_FILTER]: (state, action) => _extends$8({}, state, { + [SET_TRANSACTION_LIST_FILTER]: (state, action) => _extends$9({}, state, { transactionListFilter: action.data }), - [UPDATE_CURRENT_HEIGHT]: (state, action) => _extends$8({}, state, { + [UPDATE_CURRENT_HEIGHT]: (state, action) => _extends$9({}, state, { latestBlock: action.data }) }, defaultState$5); -var _extends$9 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; +var _extends$a = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const reducers$3 = {}; const defaultState$6 = { @@ -3632,9 +3745,9 @@ const defaultState$6 = { reducers$3[SET_CONTENT_POSITION] = (state, action) => { const { claimId, outpoint, position } = action.data; - return _extends$9({}, state, { - positions: _extends$9({}, state.positions, { - [claimId]: _extends$9({}, state.positions[claimId], { + return _extends$a({}, state, { + positions: _extends$a({}, state.positions, { + [claimId]: _extends$a({}, state.positions[claimId], { [outpoint]: position }) }) @@ -3647,6 +3760,79 @@ function contentReducer(state = defaultState$6, action) { return state; } +const defaultFollowedTags = ['gaming', 'blockchain', 'news', 'learning', 'funny', 'technology', 'automotive', 'economics', 'sports', 'food', 'science', 'art', 'nature', 'beliefs', 'music', 'pop culture', 'weapons']; + +const defaultRecommendedTags = []; + +var _extends$b = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +function getDefaultRecommendedTags() { + return defaultFollowedTags.concat(defaultRecommendedTags).reduce((tagsMap, tag) => _extends$b({}, tagsMap, { + [tag]: { name: tag } + }), {}); +} + +const defaultState$7 = { + followedTags: defaultFollowedTags, + knownTags: getDefaultRecommendedTags(), + trending: [], + fetchingTrending: false +}; + +const tagsReducer = handleActions({ + [TOGGLE_TAG_FOLLOW]: (state, action) => { + const { followedTags } = state; + const { name } = action.data; + + let newFollowedTags = followedTags.slice(); + + if (newFollowedTags.includes(name)) { + newFollowedTags = newFollowedTags.filter(tag => tag !== name); + } else { + newFollowedTags.push(name); + } + + return _extends$b({}, state, { + followedTags: newFollowedTags + }); + }, + + [TAG_ADD]: (state, action) => { + const { knownTags } = state; + const { name } = action.data; + + let newKnownTags = _extends$b({}, knownTags); + newKnownTags[name] = { name }; + + return _extends$b({}, state, { + knownTags: newKnownTags + }); + }, + + [TAG_DELETE]: (state, action) => { + const { knownTags, followedTags } = state; + const { name } = action.data; + + let newKnownTags = _extends$b({}, knownTags); + delete newKnownTags[name]; + + return _extends$b({}, state, { + knownTags: newKnownTags + }); + }, + [FETCH_TRENDING_STARTED]: state => _extends$b({}, state, { + fetchingTrending: true + }), + [FETCH_TRENDING_COMPLETED]: (state, action) => _extends$b({}, state, { + trending: action.data.uris, + fetchingTrending: false + }), + [FETCH_TRENDING_FAILED]: state => _extends$b({}, state, { + trending: [], + fetchingTrending: false + }) +}, defaultState$7); + const selectState$5 = state => state.content || {}; const makeSelectContentPositionForUri = uri => reselect.createSelector(selectState$5, makeSelectClaimForUri(uri), (state, claim) => { @@ -3658,14 +3844,14 @@ const makeSelectContentPositionForUri = uri => reselect.createSelector(selectSta return state.positions[id] ? state.positions[id][outpoint] : null; }); -var _extends$a = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; +var _extends$c = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const selectState$6 = state => state.notifications || {}; const selectToast = reselect.createSelector(selectState$6, state => { if (state.toasts.length) { const { id, params } = state.toasts[0]; - return _extends$a({ + return _extends$c({ id }, params); } @@ -3684,6 +3870,34 @@ const selectError = reselect.createSelector(selectState$6, state => { return null; }); +// + +const selectState$7 = state => state.tags || {}; + +const selectKnownTagsByName = reselect.createSelector(selectState$7, state => state.knownTags); + +const selectFollowedTagsList = reselect.createSelector(selectState$7, state => state.followedTags); + +const selectFollowedTags = reselect.createSelector(selectFollowedTagsList, followedTags => followedTags.map(tag => ({ name: tag }))); + +const selectUnfollowedTags = reselect.createSelector(selectKnownTagsByName, selectFollowedTagsList, (tagsByName, followedTags) => { + const followedTagsSet = new Set(followedTags); + let tagsToReturn = []; + + Object.keys(tagsByName).forEach(key => { + if (!followedTagsSet.has(key)) { + const { name } = tagsByName[key]; + tagsToReturn.push({ name }); + } + }); + + return tagsToReturn; +}); + +const selectTrendingUris = reselect.createSelector(selectState$7, state => state.trending || []); + +const selectFetchingTrending = reselect.createSelector(selectState$7, state => state.fetchingTrending); + exports.ACTIONS = action_types; exports.Lbry = lbryProxy; exports.PAGES = pages; @@ -3700,14 +3914,17 @@ exports.contentReducer = contentReducer; exports.convertToShareLink = convertToShareLink; exports.creditsToString = creditsToString; exports.doAbandonClaim = doAbandonClaim; +exports.doAddTag = doAddTag; exports.doBalanceSubscribe = doBalanceSubscribe; exports.doBlurSearchInput = doBlurSearchInput; exports.doCheckAddressIsMine = doCheckAddressIsMine; exports.doCreateChannel = doCreateChannel; exports.doDeletePurchasedUri = doDeletePurchasedUri; +exports.doDeleteTag = doDeleteTag; exports.doDismissError = doDismissError; exports.doDismissToast = doDismissToast; exports.doError = doError; +exports.doFetchByTags = doFetchByTags; exports.doFetchChannelListMine = doFetchChannelListMine; exports.doFetchClaimListMine = doFetchClaimListMine; exports.doFetchClaimsByChannel = doFetchClaimsByChannel; @@ -3729,6 +3946,7 @@ exports.doSetDraftTransactionAmount = doSetDraftTransactionAmount; exports.doSetFileListSort = doSetFileListSort; exports.doSetTransactionListFilter = doSetTransactionListFilter; exports.doToast = doToast; +exports.doToggleTagFollow = doToggleTagFollow; exports.doTotalBalanceSubscribe = doTotalBalanceSubscribe; exports.doUpdateBalance = doUpdateBalance; exports.doUpdateBlockHeight = doUpdateBlockHeight; @@ -3773,6 +3991,7 @@ exports.makeSelectQueryWithOptions = makeSelectQueryWithOptions; exports.makeSelectRecommendedContentForUri = makeSelectRecommendedContentForUri; exports.makeSelectSearchUris = makeSelectSearchUris; exports.makeSelectStreamingUrlForUri = makeSelectStreamingUrlForUri; +exports.makeSelectTagsForUri = makeSelectTagsForUri; exports.makeSelectThumbnailForUri = makeSelectThumbnailForUri; exports.makeSelectTitleForUri = makeSelectTitleForUri; exports.makeSelectTotalItemsForChannel = makeSelectTotalItemsForChannel; @@ -3795,6 +4014,7 @@ exports.selectChannelClaimCounts = selectChannelClaimCounts; exports.selectClaimsById = selectClaimsById; exports.selectClaimsByUri = selectClaimsByUri; exports.selectCurrentChannelPage = selectCurrentChannelPage; +exports.selectDownloadedUris = selectDownloadedUris; exports.selectDownloadingByOutpoint = selectDownloadingByOutpoint; exports.selectDownloadingFileInfos = selectDownloadingFileInfos; exports.selectDraftTransaction = selectDraftTransaction; @@ -3804,10 +4024,12 @@ exports.selectDraftTransactionError = selectDraftTransactionError; exports.selectError = selectError; exports.selectFailedPurchaseUris = selectFailedPurchaseUris; exports.selectFetchingMyChannels = selectFetchingMyChannels; +exports.selectFetchingTrending = selectFetchingTrending; exports.selectFileInfosByOutpoint = selectFileInfosByOutpoint; exports.selectFileInfosDownloaded = selectFileInfosDownloaded; exports.selectFileListDownloadedSort = selectFileListDownloadedSort; exports.selectFileListPublishedSort = selectFileListPublishedSort; +exports.selectFollowedTags = selectFollowedTags; exports.selectGettingNewAddress = selectGettingNewAddress; exports.selectHasTransactions = selectHasTransactions; exports.selectIsFetchingClaimListMine = selectIsFetchingClaimListMine; @@ -3846,6 +4068,8 @@ exports.selectTotalDownloadProgress = selectTotalDownloadProgress; exports.selectTransactionItems = selectTransactionItems; exports.selectTransactionListFilter = selectTransactionListFilter; exports.selectTransactionsById = selectTransactionsById; +exports.selectTrendingUris = selectTrendingUris; +exports.selectUnfollowedTags = selectUnfollowedTags; exports.selectUrisLoading = selectUrisLoading; exports.selectWalletDecryptPending = selectWalletDecryptPending; exports.selectWalletDecryptResult = selectWalletDecryptResult; @@ -3859,5 +4083,6 @@ exports.selectWalletUnlockPending = selectWalletUnlockPending; exports.selectWalletUnlockResult = selectWalletUnlockResult; exports.selectWalletUnlockSucceeded = selectWalletUnlockSucceeded; exports.setSearchApi = setSearchApi; +exports.tagsReducer = tagsReducer; exports.toQueryString = toQueryString; exports.walletReducer = walletReducer; diff --git a/dist/flow-typed/Tags.js b/dist/flow-typed/Tags.js new file mode 100644 index 0000000..d6b4510 --- /dev/null +++ b/dist/flow-typed/Tags.js @@ -0,0 +1,30 @@ +declare type TagState = { + followedTags: FollowedTags, + knownTags: KnownTags, + trending: Array, + fetchingTrending: boolean, +}; + +declare type Tag = { + name: string, +}; + +declare type KnownTags = { + [string]: Tag, +}; + +declare type FollowedTags = Array; + +declare type TagAction = { + type: string, + data: { + name: string, + }, +}; + +declare type TrendingTagAction = { + type: string, + data: { + uris: Array, + }, +}; diff --git a/flow-typed/Claim.js b/flow-typed/Claim.js index f18b128..d5ca709 100644 --- a/flow-typed/Claim.js +++ b/flow-typed/Claim.js @@ -3,8 +3,7 @@ declare type Claim = StreamClaim | ChannelClaim; declare type ChannelClaim = GenericClaim & { - is_channel_signature_valid?: boolean, // we may have signed channels in the future, fixes some flow issues for now. - signing_channel?: ChannelClaim, + is_channel_signature_valid?: boolean, // we may have signed channels in the future value: ChannelMetadata, }; diff --git a/flow-typed/Tags.js b/flow-typed/Tags.js new file mode 100644 index 0000000..d6b4510 --- /dev/null +++ b/flow-typed/Tags.js @@ -0,0 +1,30 @@ +declare type TagState = { + followedTags: FollowedTags, + knownTags: KnownTags, + trending: Array, + fetchingTrending: boolean, +}; + +declare type Tag = { + name: string, +}; + +declare type KnownTags = { + [string]: Tag, +}; + +declare type FollowedTags = Array; + +declare type TagAction = { + type: string, + data: { + name: string, + }, +}; + +declare type TrendingTagAction = { + type: string, + data: { + uris: Array, + }, +}; diff --git a/src/constants/action_types.js b/src/constants/action_types.js index cb1cdd0..7a303f9 100644 --- a/src/constants/action_types.js +++ b/src/constants/action_types.js @@ -88,6 +88,9 @@ export const SET_CONTENT_POSITION = 'SET_CONTENT_POSITION'; export const SET_CONTENT_LAST_VIEWED = 'SET_CONTENT_LAST_VIEWED'; export const CLEAR_CONTENT_HISTORY_URI = 'CLEAR_CONTENT_HISTORY_URI'; export const CLEAR_CONTENT_HISTORY_ALL = 'CLEAR_CONTENT_HISTORY_ALL'; +export const CLAIM_SEARCH_STARTED = 'CLAIM_SEARCH_STARTED'; +export const CLAIM_SEARCH_COMPLETED = 'CLAIM_SEARCH_COMPLETED'; +export const CLAIM_SEARCH_FAILED = 'CLAIM_SEARCH_FAILED'; // Files export const FILE_LIST_STARTED = 'FILE_LIST_STARTED'; @@ -177,21 +180,6 @@ export const FETCH_REWARD_CONTENT_COMPLETED = 'FETCH_REWARD_CONTENT_COMPLETED'; export const DOWNLOAD_LANGUAGE_SUCCEEDED = 'DOWNLOAD_LANGUAGE_SUCCEEDED'; export const DOWNLOAD_LANGUAGE_FAILED = 'DOWNLOAD_LANGUAGE_FAILED'; -// ShapeShift -export const GET_SUPPORTED_COINS_START = 'GET_SUPPORTED_COINS_START'; -export const GET_SUPPORTED_COINS_SUCCESS = 'GET_SUPPORTED_COINS_SUCCESS'; -export const GET_SUPPORTED_COINS_FAIL = 'GET_SUPPORTED_COINS_FAIL'; -export const GET_COIN_STATS_START = 'GET_COIN_STATS_START'; -export const GET_COIN_STATS_SUCCESS = 'GET_COIN_STATS_SUCCESS'; -export const GET_COIN_STATS_FAIL = 'GET_COIN_STATS_FAIL'; -export const PREPARE_SHAPE_SHIFT_START = 'PREPARE_SHAPE_SHIFT_START'; -export const PREPARE_SHAPE_SHIFT_SUCCESS = 'PREPARE_SHAPE_SHIFT_SUCCESS'; -export const PREPARE_SHAPE_SHIFT_FAIL = 'PREPARE_SHAPE_SHIFT_FAIL'; -export const GET_ACTIVE_SHIFT_START = 'GET_ACTIVE_SHIFT_START'; -export const GET_ACTIVE_SHIFT_SUCCESS = 'GET_ACTIVE_SHIFT_SUCCESS'; -export const GET_ACTIVE_SHIFT_FAIL = 'GET_ACTIVE_SHIFT_FAIL'; -export const CLEAR_SHAPE_SHIFT = 'CLEAR_SHAPE_SHIFT'; - // Subscriptions export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE'; export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE'; @@ -229,3 +217,10 @@ export const FETCH_DATE = 'FETCH_DATE'; export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED'; export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED'; export const FETCH_COST_INFO_FAILED = 'FETCH_COST_INFO_FAILED'; +// Tags +export const TOGGLE_TAG_FOLLOW = 'TOGGLE_TAG_FOLLOW'; +export const TAG_ADD = 'TAG_ADD'; +export const TAG_DELETE = 'TAG_DELETE'; +export const FETCH_TRENDING_STARTED = 'FETCH_TRENDING_STARTED'; +export const FETCH_TRENDING_COMPLETED = 'FETCH_TRENDING_COMPLETED'; +export const FETCH_TRENDING_FAILED = 'FETCH_TRENDING_FAILED'; diff --git a/src/constants/tags.js b/src/constants/tags.js new file mode 100644 index 0000000..7f00bd2 --- /dev/null +++ b/src/constants/tags.js @@ -0,0 +1,21 @@ +export const defaultFollowedTags = [ + 'gaming', + 'blockchain', + 'news', + 'learning', + 'funny', + 'technology', + 'automotive', + 'economics', + 'sports', + 'food', + 'science', + 'art', + 'nature', + 'beliefs', + 'music', + 'pop culture', + 'weapons', +]; + +export const defaultRecommendedTags = []; diff --git a/src/index.js b/src/index.js index 848d0e5..6e40213 100644 --- a/src/index.js +++ b/src/index.js @@ -87,6 +87,8 @@ export { doUpdateBlockHeight, } from 'redux/actions/wallet'; +export { doToggleTagFollow, doAddTag, doDeleteTag, doFetchByTags } from 'redux/actions/tags'; + // utils export { batchActions } from 'util/batchActions'; export { parseQueryParams, toQueryString } from 'util/query_params'; @@ -101,6 +103,7 @@ export { notificationsReducer } from 'redux/reducers/notifications'; export { searchReducer } from 'redux/reducers/search'; export { walletReducer } from 'redux/reducers/wallet'; export { contentReducer } from 'redux/reducers/content'; +export { tagsReducer } from 'redux/reducers/tags'; // selectors export { makeSelectContentPositionForUri } from 'redux/selectors/content'; @@ -127,6 +130,7 @@ export { makeSelectCoverForUri, makeSelectTitleForUri, makeSelectDateForUri, + makeSelectTagsForUri, makeSelectContentTypeForUri, makeSelectIsUriResolving, makeSelectTotalItemsForChannel, @@ -177,6 +181,7 @@ export { selectSearchDownloadUris, selectFileListDownloadedSort, selectFileListPublishedSort, + selectDownloadedUris, } from 'redux/selectors/file_info'; export { selectSearchState }; @@ -221,3 +226,10 @@ export { selectWalletUnlockResult, selectTransactionListFilter, } from 'redux/selectors/wallet'; + +export { + selectFollowedTags, + selectUnfollowedTags, + selectTrendingUris, + selectFetchingTrending, +} from 'redux/selectors/tags'; diff --git a/src/redux/actions/claims.js b/src/redux/actions/claims.js index 5248309..ea3da1f 100644 --- a/src/redux/actions/claims.js +++ b/src/redux/actions/claims.js @@ -7,6 +7,7 @@ import { selectMyClaimsRaw, selectResolvingUris, selectClaimsByUri } from 'redux import { doFetchTransactions } from 'redux/actions/wallet'; import { selectSupportsByOutpoint } from 'redux/selectors/wallet'; import { creditsToString } from 'util/formatCredits'; +import { batchActions } from 'util/batchActions'; export function doResolveUris(uris: Array, returnCachedClaims: boolean = false) { return (dispatch: Dispatch, getState: GetState) => { @@ -69,7 +70,6 @@ export function doResolveUris(uris: Array, returnCachedClaims: boolean = 0; } } - // $FlowFixMe resolveInfo[uri] = result; } @@ -264,3 +264,48 @@ export function doFetchChannelListMine() { Lbry.channel_list().then(callback); }; } + +export function doClaimSearch( + amount: number = 20, + options: {} = {}, + cb: (?Error, ?Array) => void +) { + return (dispatch: Dispatch) => { + dispatch({ + type: ACTIONS.CLAIM_SEARCH_STARTED, + }); + + const success = (data: ClaimSearchResponse) => { + const resolveInfo = {}; + const uris = []; + data.items.forEach((stream: Claim) => { + resolveInfo[stream.permanent_url] = { stream }; + uris.push(stream.permanent_url); + }); + + dispatch({ + type: ACTIONS.CLAIM_SEARCH_COMPLETED, + data: { resolveInfo }, + }); + + if (cb) { + cb(null, uris); + } + }; + + const failure = err => { + dispatch({ + type: ACTIONS.CLAIM_SEARCH_FAILED, + error: err, + }); + if (cb) { + cb(err); + } + }; + + Lbry.claim_search({ + page_size: amount, + ...options, + }).then(success, failure); + }; +} diff --git a/src/redux/actions/tags.js b/src/redux/actions/tags.js new file mode 100644 index 0000000..28bb7c8 --- /dev/null +++ b/src/redux/actions/tags.js @@ -0,0 +1,51 @@ +// @flow +import * as ACTIONS from 'constants/action_types'; +import Lbry from 'lbry'; +import { doClaimSearch } from 'redux/actions/claims'; + +export const doToggleTagFollow = (name: string) => ({ + type: ACTIONS.TOGGLE_TAG_FOLLOW, + data: { + name, + }, +}); + +export const doAddTag = (name: string) => ({ + type: ACTIONS.TAG_ADD, + data: { + name, + }, +}); + +export const doDeleteTag = (name: string) => ({ + type: ACTIONS.TAG_DELETE, + data: { + name, + }, +}); + +export const doFetchByTags = (amount: number = 10, options: Object = {}) => { + return (dispatch: Dispatch) => { + dispatch({ + type: ACTIONS.FETCH_TRENDING_STARTED, + }); + + const callback = (error: ?Error, uris: ?Array = []) => { + if (error) { + return dispatch({ + type: ACTIONS.FETCH_TRENDING_FAILED, + error, + }); + } + + dispatch({ + type: ACTIONS.FETCH_TRENDING_COMPLETED, + data: { + uris, + }, + }); + }; + + dispatch(doClaimSearch(amount, options, callback)); + }; +}; diff --git a/src/redux/reducers/claims.js b/src/redux/reducers/claims.js index cd50cd6..92c5f99 100644 --- a/src/redux/reducers/claims.js +++ b/src/redux/reducers/claims.js @@ -45,7 +45,7 @@ const defaultState = { pendingById: {}, }; -reducers[ACTIONS.RESOLVE_URIS_COMPLETED] = (state: State, action: any): State => { +function handleClaimAction(state: State, action: any): State { const { resolveInfo, }: { @@ -88,6 +88,10 @@ reducers[ACTIONS.RESOLVE_URIS_COMPLETED] = (state: State, action: any): State => channelClaimCounts, resolvingUris: (state.resolvingUris || []).filter(uri => !resolveInfo[uri]), }); +} + +reducers[ACTIONS.RESOLVE_URIS_COMPLETED] = (state: State, action: any): State => { + return handleClaimAction(state, action); }; reducers[ACTIONS.FETCH_CLAIM_LIST_MINE_STARTED] = (state: State): State => @@ -265,6 +269,20 @@ reducers[ACTIONS.RESOLVE_URIS_STARTED] = (state: State, action: any): State => { }); }; +reducers[ACTIONS.CLAIM_SEARCH_STARTED] = (state: State): State => { + return Object.assign({}, state, { + fetchingClaimSearch: true, + }); +}; +reducers[ACTIONS.CLAIM_SEARCH_COMPLETED] = (state: State, action: any): State => { + return { ...handleClaimAction(state, action), fetchingClaimSearch: false }; +}; +reducers[ACTIONS.CLAIM_SEARCH_FAILED] = (state: State): State => { + return Object.assign({}, state, { + fetchingClaimSearch: false, + }); +}; + export function claimsReducer(state: State = defaultState, action: any) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/src/redux/reducers/tags.js b/src/redux/reducers/tags.js new file mode 100644 index 0000000..eb11692 --- /dev/null +++ b/src/redux/reducers/tags.js @@ -0,0 +1,84 @@ +// @flow +import * as ACTIONS from 'constants/action_types'; +import { handleActions } from 'util/redux-utils'; +import { defaultRecommendedTags, defaultFollowedTags } from 'constants/tags'; + +function getDefaultRecommendedTags() { + return defaultFollowedTags.concat(defaultRecommendedTags).reduce( + (tagsMap, tag) => ({ + ...tagsMap, + [tag]: { name: tag }, + }), + {} + ); +} + +const defaultState: TagState = { + followedTags: defaultFollowedTags, + knownTags: getDefaultRecommendedTags(), + trending: [], + fetchingTrending: false, +}; + +export const tagsReducer = handleActions( + { + [ACTIONS.TOGGLE_TAG_FOLLOW]: (state: TagState, action: TagAction): TagState => { + const { followedTags } = state; + const { name } = action.data; + + let newFollowedTags = followedTags.slice(); + + if (newFollowedTags.includes(name)) { + newFollowedTags = newFollowedTags.filter(tag => tag !== name); + } else { + newFollowedTags.push(name); + } + + return { + ...state, + followedTags: newFollowedTags, + }; + }, + + [ACTIONS.TAG_ADD]: (state: TagState, action: TagAction) => { + const { knownTags } = state; + const { name } = action.data; + + let newKnownTags = { ...knownTags }; + newKnownTags[name] = { name }; + + return { + ...state, + knownTags: newKnownTags, + }; + }, + + [ACTIONS.TAG_DELETE]: (state: TagState, action: TagAction) => { + const { knownTags, followedTags } = state; + const { name } = action.data; + + let newKnownTags = { ...knownTags }; + delete newKnownTags[name]; + + return { + ...state, + knownTags: newKnownTags, + }; + }, + [ACTIONS.FETCH_TRENDING_STARTED]: (state: TagState) => ({ + ...state, + fetchingTrending: true, + }), + [ACTIONS.FETCH_TRENDING_COMPLETED]: (state: TagState, action: TrendingTagAction) => ({ + ...state, + trending: action.data.uris, + fetchingTrending: false, + }), + [ACTIONS.FETCH_TRENDING_FAILED]: (state: TagState) => ({ + ...state, + trending: [], + fetchingTrending: false, + }), + }, + defaultState +); diff --git a/src/redux/selectors/claims.js b/src/redux/selectors/claims.js index 0cf4bdf..0a0efc3 100644 --- a/src/redux/selectors/claims.js +++ b/src/redux/selectors/claims.js @@ -433,3 +433,11 @@ export const makeSelectChannelForClaimUri = (uri: string, includePrefix: boolean return includePrefix ? `lbry://${channel}` : channel; } ); + +export const makeSelectTagsForUri = (uri: string) => + createSelector( + makeSelectMetadataForUri(uri), + (metadata: ?GenericMetadata) => { + return (metadata && metadata.tags) || []; + } + ); diff --git a/src/redux/selectors/file_info.js b/src/redux/selectors/file_info.js index 4718cd8..fbfc023 100644 --- a/src/redux/selectors/file_info.js +++ b/src/redux/selectors/file_info.js @@ -228,3 +228,9 @@ export const selectFileListDownloadedSort = createSelector( selectState, state => state.fileListDownloadedSort ); + +export const selectDownloadedUris = createSelector( + selectFileInfosDownloaded, + // We should use permament_url but it doesn't exist in file_list + info => info.map(claim => `lbry://${claim.claim_name}#${claim.claim_id}`) +); diff --git a/src/redux/selectors/tags.js b/src/redux/selectors/tags.js new file mode 100644 index 0000000..20f88f9 --- /dev/null +++ b/src/redux/selectors/tags.js @@ -0,0 +1,47 @@ +// @flow +import { createSelector } from 'reselect'; + +const selectState = (state: { tags: TagState }) => state.tags || {}; + +export const selectKnownTagsByName = createSelector( + selectState, + (state: TagState): KnownTags => state.knownTags +); + +export const selectFollowedTagsList = createSelector( + selectState, + (state: TagState): Array => state.followedTags +); + +export const selectFollowedTags = createSelector( + selectFollowedTagsList, + (followedTags: Array): Array => followedTags.map(tag => ({ name: tag })) +); + +export const selectUnfollowedTags = createSelector( + selectKnownTagsByName, + selectFollowedTagsList, + (tagsByName: KnownTags, followedTags: Array): Array => { + const followedTagsSet = new Set(followedTags); + let tagsToReturn = []; + + Object.keys(tagsByName).forEach(key => { + if (!followedTagsSet.has(key)) { + const { name } = tagsByName[key]; + tagsToReturn.push({ name }); + } + }); + + return tagsToReturn; + } +); + +export const selectTrendingUris = createSelector( + selectState, + state => state.trending || [] +); + +export const selectFetchingTrending = createSelector( + selectState, + state => state.fetchingTrending +);