changes after redesign merge

This commit is contained in:
Akinwale Ariwodola 2018-03-28 05:53:32 +01:00
parent 37ccdd4fe4
commit 12d4b6c775
13 changed files with 1294 additions and 635 deletions

File diff suppressed because it is too large Load diff

View file

@ -39,8 +39,6 @@ export const FETCH_TRANSACTIONS_COMPLETED = 'FETCH_TRANSACTIONS_COMPLETED';
export const UPDATE_BALANCE = 'UPDATE_BALANCE';
export const CHECK_ADDRESS_IS_MINE_STARTED = 'CHECK_ADDRESS_IS_MINE_STARTED';
export const CHECK_ADDRESS_IS_MINE_COMPLETED = 'CHECK_ADDRESS_IS_MINE_COMPLETED';
export const SET_DRAFT_TRANSACTION_AMOUNT = 'SET_DRAFT_TRANSACTION_AMOUNT';
export const SET_DRAFT_TRANSACTION_ADDRESS = 'SET_DRAFT_TRANSACTION_ADDRESS';
export const SEND_TRANSACTION_STARTED = 'SEND_TRANSACTION_STARTED';
export const SEND_TRANSACTION_COMPLETED = 'SEND_TRANSACTION_COMPLETED';
export const SEND_TRANSACTION_FAILED = 'SEND_TRANSACTION_FAILED';
@ -90,9 +88,11 @@ export const FETCH_AVAILABILITY_COMPLETED = 'FETCH_AVAILABILITY_COMPLETED';
export const FILE_DELETE = 'FILE_DELETE';
// Search
export const SEARCH_STARTED = 'SEARCH_STARTED';
export const SEARCH_COMPLETED = 'SEARCH_COMPLETED';
export const SEARCH_CANCELLED = 'SEARCH_CANCELLED';
export const SEARCH_START = 'SEARCH_START';
export const SEARCH_SUCCESS = 'SEARCH_SUCCESS';
export const SEARCH_FAIL = 'SEARCH_FAIL';
export const UPDATE_SEARCH_QUERY = 'UPDATE_SEARCH_QUERY';
export const UPDATE_SEARCH_SUGGESTIONS = 'UPDATE_SEARCH_SUGGESTIONS';
// Settings
export const DAEMON_SETTINGS_RECEIVED = 'DAEMON_SETTINGS_RECEIVED';
@ -165,6 +165,8 @@ export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE';
export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE';
export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS';
export const SET_SUBSCRIPTION_LATEST = 'SET_SUBSCRIPTION_LATEST';
export const SET_SUBSCRIPTION_NOTIFICATION = 'SET_SUBSCRIPTION_NOTIFICATION';
export const SET_SUBSCRIPTION_NOTIFICATIONS = 'SET_SUBSCRIPTION_NOTIFICATIONS';
export const CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED';
export const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED';
export const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE';
@ -176,3 +178,13 @@ export const SET_VIDEO_PAUSE = 'SET_VIDEO_PAUSE';
export const MEDIA_PLAY = 'MEDIA_PLAY';
export const MEDIA_PAUSE = 'MEDIA_PAUSE';
export const MEDIA_POSITION = 'MEDIA_POSITION';
// Publishing
export const CLEAR_PUBLISH = 'CLEAR_PUBLISH';
export const UPDATE_PUBLISH_FORM = 'UPDATE_PUBLISH_FORM';
export const PUBLISH_START = 'PUBLISH_START';
export const PUBLISH_SUCCESS = 'PUBLISH_SUCCESS';
export const PUBLISH_FAIL = 'PUBLISH_FAIL';
export const CLEAR_PUBLISH_ERROR = 'CLEAR_PUBLISH_ERROR';
export const REMOVE_PENDING_PUBLISH = 'REMOVE_PENDING_PUBLISH';
export const DO_PREPARE_EDIT = 'DO_PREPARE_EDIT';

3
src/constants/search.js Normal file
View file

@ -0,0 +1,3 @@
export const FILE = 'file';
export const CHANNEL = 'channel';
export const SEARCH = 'search';

View file

@ -43,7 +43,7 @@ export {
doFetchFileInfosAndPublishedClaims,
} from 'redux/actions/file_info';
export { doSearch } from 'redux/actions/search';
export { doSearch, doUpdateSearchQuery } from 'redux/actions/search';
export {
doUpdateBalance,
@ -128,6 +128,7 @@ export {
selectFileInfosDownloaded,
selectDownloadingFileInfos,
selectTotalDownloadProgress,
selectSearchDownloadUris,
} from 'redux/selectors/file_info';
export {
@ -144,15 +145,17 @@ export {
selectHistoryIndex,
selectHistoryStack,
selectActiveHistoryEntry,
selectNavLinks,
} from 'redux/selectors/navigation';
export {
makeSelectSearchUris,
selectSearchState,
selectSearchQuery,
selectSearchValue,
selectIsSearching,
selectSearchUrisByQuery,
selectWunderBarAddress,
selectWunderBarIcon,
} from 'redux/selectors/search';
export {

View file

@ -1,68 +1,173 @@
import * as ACTIONS from 'constants/action_types';
import { buildURI } from 'lbryURI';
import * as SEARCH_TYPES from 'constants/search';
import { buildURI, normalizeURI, parseURI } from 'lbryURI';
import { doResolveUri } from 'redux/actions/claims';
import { selectCurrentPage } from 'redux/selectors/navigation';
import { batchActions } from 'util/batchActions';
import { makeSelectSearchUris } from 'redux/selectors/search';
import handleFetchResponse from 'util/handle-fetch';
// eslint-disable-next-line import/prefer-default-export
export function doSearch(rawQuery, currentPageNotSearchHandler) {
return (dispatch, getState) => {
const state = getState();
const page = selectCurrentPage(state);
const query = rawQuery.replace(/^lbry:\/\//i, '');
if (!query) {
dispatch({
type: ACTIONS.SEARCH_CANCELLED,
});
return;
}
export const doSearch = rawQuery => (dispatch, getState) => {
const state = getState();
const query = rawQuery.replace(/^lbry:\/\//i, '');
if (!query) {
dispatch({
type: ACTIONS.SEARCH_STARTED,
data: { query },
type: ACTIONS.SEARCH_FAIL,
});
return;
}
if (page !== 'search') {
if (currentPageNotSearchHandler) {
currentPageNotSearchHandler();
}
} else {
fetch(`https://lighthouse.lbry.io/search?s=${query}`)
.then(
response =>
response.status === 200
? Promise.resolve(response.json())
: Promise.reject(new Error(response.statusText))
)
.then(data => {
const uris = [];
const actions = [];
// If we have already searched for something, we don't need to do anything
const urisForQuery = makeSelectSearchUris(query)(state);
if (urisForQuery && !!urisForQuery.length) {
return;
}
data.forEach(result => {
const uri = buildURI({
name: result.name,
claimId: result.claimId,
});
actions.push(doResolveUri(uri));
uris.push(uri);
});
dispatch({
type: ACTIONS.SEARCH_START,
});
actions.push({
type: ACTIONS.SEARCH_COMPLETED,
data: {
query,
uris,
},
});
dispatch(batchActions(...actions));
})
.catch(() => {
dispatch({
type: ACTIONS.SEARCH_CANCELLED,
});
// If the user is on the file page with a pre-populated uri and they select
// the search option without typing anything, searchQuery will be empty
// We need to populate it so the input is filled on the search page
if (!state.search.searchQuery) {
dispatch({
type: ACTIONS.UPDATE_SEARCH_QUERY,
data: { searchQuery: query },
});
}
fetch(`https://lighthouse.lbry.io/search?s=${query}`)
.then(handleFetchResponse)
.then(data => {
const uris = [];
const actions = [];
data.forEach(result => {
const uri = buildURI({
claimName: result.name,
claimId: result.claimId,
});
actions.push(doResolveUri(uri));
uris.push(uri);
});
actions.push({
type: ACTIONS.SEARCH_SUCCESS,
data: {
query,
uris,
},
});
dispatch(batchActions(...actions));
})
.catch(() => {
dispatch({
type: ACTIONS.SEARCH_FAIL,
});
});
};
export const doUpdateSearchQuery = (query: string, shouldSkipSuggestions: ?boolean) => dispatch => {
dispatch({
type: ACTIONS.UPDATE_SEARCH_QUERY,
data: { query },
});
// Don't fetch new suggestions if the user just added a space
if (!query.endsWith(' ') || !shouldSkipSuggestions) {
dispatch(getSearchSuggestions(query));
}
};
export const getSearchSuggestions = (value: string) => dispatch => {
const query = value.trim();
const isPrefix = () =>
query === '@' || query === 'lbry:' || query === 'lbry:/' || query === 'lbry://';
if (!query || isPrefix()) {
dispatch({
type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS,
data: { suggestions: [] },
});
return;
}
let suggestions = [];
try {
// If the user is about to manually add the claim id ignore it until they
// actually add one. This would hardly ever happen, but then the search
// suggestions won't change just from adding a '#' after a uri
let uriQuery = query;
if (uriQuery.endsWith('#')) {
uriQuery = uriQuery.slice(0, -1);
}
};
}
const uri = normalizeURI(uriQuery);
const { claimName, isChannel } = parseURI(uri);
suggestions.push(
{
value: uri,
shorthand: isChannel ? claimName.slice(1) : claimName,
type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE,
},
{
value: claimName,
type: SEARCH_TYPES.SEARCH,
}
);
// If it's a valid url, don't fetch any extra search results
return dispatch({
type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS,
data: { suggestions },
});
} catch (e) {
suggestions.push({
value: query,
type: SEARCH_TYPES.SEARCH,
});
}
// Populate the current search query suggestion before fetching results
dispatch({
type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS,
data: { suggestions },
});
// strip out any basic stuff for more accurate search results
let searchValue = value.replace(/lbry:\/\//g, '').replace(/-/g, ' ');
if (searchValue.includes('#')) {
// This should probably be more robust, but I think it's fine for now
// Remove everything after # to get rid of the claim id
searchValue = searchValue.substring(0, searchValue.indexOf('#'));
}
return fetch(`https://lighthouse.lbry.io/autocomplete?s=${searchValue}`)
.then(handleFetchResponse)
.then(apiSuggestions => {
const formattedSuggestions = apiSuggestions.slice(0, 6).map(suggestion => {
// This will need to be more robust when the api starts returning lbry uris
const isChannel = suggestion.startsWith('@');
const suggestionObj = {
value: isChannel ? `lbry://${suggestion}` : suggestion,
shorthand: isChannel ? suggestion.slice(1) : '',
type: isChannel ? 'channel' : 'search',
};
return suggestionObj;
});
suggestions = suggestions.concat(formattedSuggestions);
dispatch({
type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS,
data: { suggestions },
});
})
.catch(() => {
// If the fetch fails, do nothing
// Basic search suggestions are already populated at this point
});
};

View file

@ -34,7 +34,7 @@ export function doFetchTransactions() {
type: ACTIONS.FETCH_TRANSACTIONS_STARTED,
});
Lbry.transaction_list({ include_tip_info: true }).then(results => {
Lbry.transaction_list().then(results => {
dispatch({
type: ACTIONS.FETCH_TRANSACTIONS_COMPLETED,
data: {
@ -62,8 +62,8 @@ export function doGetNewAddress() {
type: ACTIONS.GET_NEW_ADDRESS_STARTED,
});
// Removed localStorage use, since address is expected to be stored in redux store
Lbry.wallet_new_address().then(address => {
// localStorage.setItem('wallet_address', address);
dispatch({
type: ACTIONS.GET_NEW_ADDRESS_COMPLETED,
data: { address },

View file

@ -1,32 +1,103 @@
// @flow
import * as ACTIONS from 'constants/action_types';
import { handleActions } from 'util/redux-utils';
const reducers = {};
const defaultState = {
type SearchSuccess = {
type: ACTIONS.SEARCH_SUCCESS,
data: {
query: string,
uris: Array<string>,
},
};
type UpdateSearchQuery = {
type: ACTIONS.UPDATE_SEARCH_QUERY,
data: {
query: string,
},
};
type SearchSuggestion = {
value: string,
shorthand: string,
type: string,
};
type UpdateSearchSuggestions = {
type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS,
data: {
suggestions: Array<SearchSuggestion>,
},
};
type SearchState = {
isActive: boolean,
searchQuery: string,
suggestions: Array<SearchSuggestion>,
urisByQuery: {},
searching: false,
};
reducers[ACTIONS.SEARCH_STARTED] = state =>
Object.assign({}, state, {
searching: true,
});
reducers[ACTIONS.SEARCH_COMPLETED] = (state, action) => {
const { query, uris } = action.data;
return Object.assign({}, state, {
searching: false,
urisByQuery: Object.assign({}, state.urisByQuery, { [query]: uris }),
});
const defaultState = {
isActive: false,
searchQuery: '', // needs to be an empty string for input focusing
suggestions: [],
urisByQuery: {},
};
reducers[ACTIONS.SEARCH_CANCELLED] = state =>
Object.assign({}, state, {
searching: false,
});
export const searchReducer = handleActions(
{
[ACTIONS.SEARCH_START]: (state: SearchState): SearchState => ({
...state,
searching: true,
}),
[ACTIONS.SEARCH_SUCCESS]: (state: SearchState, action: SearchSuccess): SearchState => {
const { query, uris } = action.data;
export function searchReducer(state = defaultState, action) {
const handler = reducers[action.type];
if (handler) return handler(state, action);
return state;
}
return {
...state,
searching: false,
urisByQuery: Object.assign({}, state.urisByQuery, { [query]: uris }),
};
},
[ACTIONS.SEARCH_FAIL]: (state: SearchState): SearchState => ({
...state,
searching: false,
}),
[ACTIONS.UPDATE_SEARCH_QUERY]: (
state: SearchState,
action: UpdateSearchQuery
): SearchState => ({
...state,
searchQuery: action.data.query,
isActive: true,
}),
[ACTIONS.UPDATE_SEARCH_SUGGESTIONS]: (
state: SearchState,
action: UpdateSearchSuggestions
): SearchState => ({
...state,
suggestions: action.data.suggestions,
}),
// clear the searchQuery on back/forward
// it may be populated by the page title for search/file pages
// if going home, it should be blank
[ACTIONS.HISTORY_NAVIGATE]: (state: SearchState): SearchState => ({
...state,
searchQuery: '',
suggestions: [],
isActive: false,
}),
// 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
[ACTIONS.CLOSE_MODAL]: (state: SearchState): SearchState => ({
...state,
isActive: false,
}),
},
defaultState
);

View file

@ -1,7 +1,6 @@
import * as ACTIONS from 'constants/action_types';
const reducers = {};
const receiveAddress = null; // localStorage.getItem('receiveAddress');
const buildDraftTransaction = () => ({
amount: undefined,
address: undefined,
@ -12,7 +11,7 @@ const defaultState = {
blocks: {},
transactions: {},
fetchingTransactions: false,
receiveAddress,
receiveAddress: null,
gettingNewAddress: false,
draftTransaction: buildDraftTransaction(),
sendingSupport: false,
@ -46,7 +45,7 @@ reducers[ACTIONS.GET_NEW_ADDRESS_STARTED] = state =>
reducers[ACTIONS.GET_NEW_ADDRESS_COMPLETED] = (state, action) => {
const { address } = action.data;
// localStorage.setItem('receiveAddress', address);
// Say no to localStorage!
return Object.assign({}, state, {
gettingNewAddress: false,
receiveAddress: address,

View file

@ -3,6 +3,7 @@ import {
selectIsFetchingClaimListMine,
selectMyClaims,
} from 'redux/selectors/claims';
import { buildURI } from 'lbryURI';
import { createSelector } from 'reselect';
export const selectState = state => state.fileInfo || {};
@ -27,7 +28,6 @@ export const makeSelectFileInfoForUri = uri =>
createSelector(selectClaimsByUri, selectFileInfosByOutpoint, (claims, byOutpoint) => {
const claim = claims[uri];
const outpoint = claim ? `${claim.txid}:${claim.nout}` : undefined;
return outpoint ? byOutpoint[outpoint] : undefined;
});
@ -104,3 +104,92 @@ export const selectTotalDownloadProgress = createSelector(selectDownloadingFileI
if (fileInfos.length > 0) return totalProgress / fileInfos.length / 100.0;
return -1;
});
export const selectSearchDownloadUris = query =>
createSelector(selectFileInfosDownloaded, fileInfos => {
if (!query || !fileInfos.length) {
return null;
}
const queryParts = query.toLowerCase().split(' ');
const searchQueryDictionary = {};
queryParts.forEach(subQuery => {
searchQueryDictionary[subQuery] = subQuery;
});
const arrayContainsQueryPart = array => {
for (let i = 0; i < array.length; i += 1) {
const subQuery = array[i];
if (searchQueryDictionary[subQuery]) {
return true;
}
}
return false;
};
const downloadResultsFromQuery = [];
fileInfos.forEach(fileInfo => {
const { channel_name, claim_name, metadata } = fileInfo;
const { author, description, title } = metadata;
if (channel_name) {
const channelName = channel_name.toLowerCase();
const strippedOutChannelName = channelName.slice(1); // trim off the @
if (searchQueryDictionary[channel_name] || searchQueryDictionary[strippedOutChannelName]) {
downloadResultsFromQuery.push(fileInfo);
return;
}
}
const nameParts = claim_name.toLowerCase().split('-');
if (arrayContainsQueryPart(nameParts)) {
downloadResultsFromQuery.push(fileInfo);
return;
}
const titleParts = title.toLowerCase().split(' ');
if (arrayContainsQueryPart(titleParts)) {
downloadResultsFromQuery.push(fileInfo);
return;
}
if (author) {
const authorParts = author.toLowerCase().split(' ');
if (arrayContainsQueryPart(authorParts)) {
downloadResultsFromQuery.push(fileInfo);
return;
}
}
if (description) {
const descriptionParts = description.toLowerCase().split(' ');
if (arrayContainsQueryPart(descriptionParts)) {
downloadResultsFromQuery.push(fileInfo);
}
}
});
return downloadResultsFromQuery.length
? downloadResultsFromQuery.map(fileInfo => {
const {
channel_name: channelName,
claim_id: claimId,
claim_name: claimName,
value,
metadata,
} = fileInfo;
const uriParams = {};
if (channelName) {
uriParams.channelName = channelName;
}
uriParams.claimId = claimId;
uriParams.claimId = claimId;
uriParams.contentName = claimName;
const uri = buildURI(uriParams);
return uri;
})
: null;
});

View file

@ -1,6 +1,5 @@
import { createSelector } from 'reselect';
import { normalizeURI } from 'lbryURI';
import { parseQueryParams, toQueryString } from 'util/query_params';
import { parseQueryParams } from 'util/query_params';
export const selectState = state => state.navigation || {};
@ -22,103 +21,6 @@ export const selectCurrentParams = createSelector(selectCurrentPath, path => {
export const makeSelectCurrentParam = param =>
createSelector(selectCurrentParams, params => (params ? params[param] : undefined));
export const selectHeaderLinks = createSelector(selectCurrentPage, page => {
// This contains intentional fall throughs
switch (page) {
case 'wallet':
case 'history':
case 'send':
case 'getcredits':
case 'invite':
case 'rewards':
case 'backup':
return {
wallet: __('Overview'),
getcredits: __('Get Credits'),
send: __('Send / Receive'),
rewards: __('Rewards'),
invite: __('Invites'),
history: __('History'),
};
case 'downloaded':
case 'published':
return {
downloaded: __('Downloaded'),
published: __('Published'),
};
case 'settings':
case 'help':
return {
settings: __('Settings'),
help: __('Help'),
};
case 'discover':
case 'subscriptions':
return {
discover: __('Discover'),
subscriptions: __('Subscriptions'),
};
default:
return null;
}
});
export const selectPageTitle = createSelector(
selectCurrentPage,
selectCurrentParams,
(page, params) => {
switch (page) {
case 'settings':
return __('Settings');
case 'report':
return __('Report');
case 'wallet':
return __('Wallet');
case 'send':
return __('Send or Receive LBRY Credits');
case 'getcredits':
return __('Get LBRY Credits');
case 'backup':
return __('Backup Your Wallet');
case 'rewards':
return __('Rewards');
case 'invite':
return __('Invites');
case 'start':
return __('Start');
case 'publish':
return params.id ? __('Edit') : __('Publish');
case 'help':
return __('Help');
case 'developer':
return __('Developer');
case 'show': {
const parts = [normalizeURI(params.uri)];
// If the params has any keys other than "uri"
if (Object.keys(params).length > 1) {
parts.push(toQueryString(Object.assign({}, params, { uri: null })));
}
return parts.join('?');
}
case 'downloaded':
return __('Downloads & Purchases');
case 'published':
return __('Publications');
case 'search':
return params.query ? __('Search results for %s', params.query) : __('Search');
case 'subscriptions':
return __('Your Subscriptions');
case 'discover':
case false:
case null:
case '':
return '';
default:
return page[0].toUpperCase() + (page.length > 0 ? page.substr(1) : '');
}
}
);
export const selectPathAfterAuth = createSelector(selectState, state => state.pathAfterAuth);
export const selectIsBackDisabled = createSelector(selectState, state => state.index === 0);
@ -128,6 +30,8 @@ export const selectIsForwardDisabled = createSelector(
state => state.index === state.stack.length - 1
);
export const selectIsHome = createSelector(selectCurrentPage, page => page === 'discover');
export const selectHistoryIndex = createSelector(selectState, state => state.index);
export const selectHistoryStack = createSelector(selectState, state => state.stack);
@ -137,3 +41,162 @@ export const selectActiveHistoryEntry = createSelector(
selectState,
state => state.stack[state.index]
);
export const selectPageTitle = createSelector(selectCurrentPage, page => {
switch (page) {
default:
return '';
}
});
export const selectNavLinks = createSelector(
selectCurrentPage,
selectHistoryStack,
(currentPage, historyStack) => {
const isWalletPage = page =>
page === 'wallet' ||
page === 'send' ||
page === 'getcredits' ||
page === 'rewards' ||
page === 'history' ||
page === 'invite';
const isMyLbryPage = page =>
page === 'downloaded' || page === 'published' || page === 'settings';
const previousStack = historyStack.slice().reverse();
const getPreviousSubLinkPath = checkIfValidPage => {
for (let i = 0; i < previousStack.length; i += 1) {
const currentStackItem = previousStack[i];
// Trim off the "/" from the path
const pageInStack = currentStackItem.path.slice(1);
if (checkIfValidPage(pageInStack)) {
return currentStackItem.path;
}
}
return undefined;
};
// Gets the last active sublink in a section
const getActiveSublink = category => {
if (category === 'wallet') {
const previousPath = getPreviousSubLinkPath(isWalletPage);
return previousPath || '/wallet';
} else if (category === 'myLbry') {
const previousPath = getPreviousSubLinkPath(isMyLbryPage);
return previousPath || '/downloaded';
}
return undefined;
};
const isCurrentlyWalletPage = isWalletPage(currentPage);
const isCurrentlyMyLbryPage = isMyLbryPage(currentPage);
const walletSubLinks = [
{
label: 'Overview',
path: '/wallet',
active: currentPage === 'wallet',
},
{
label: 'Send & Recieve',
path: '/send',
active: currentPage === 'send',
},
{
label: 'Get Credits',
path: '/getcredits',
active: currentPage === 'getcredits',
},
{
label: 'Rewards',
path: '/rewards',
active: currentPage === 'rewards',
},
{
label: 'Invites',
path: '/invite',
active: currentPage === 'invite',
},
{
label: 'Transactions',
path: '/history',
active: currentPage === 'history',
},
];
const myLbrySubLinks = [
{
label: 'Downloads',
path: '/downloaded',
active: currentPage === 'downloaded',
},
{
label: 'Publishes',
path: '/published',
active: currentPage === 'published',
},
{
label: 'Settings',
path: '/settings',
active: currentPage === 'settings',
},
{
label: 'Backup',
path: '/backup',
active: currentPage === 'backup',
},
];
const navLinks = {
primary: [
{
label: 'Explore',
path: '/discover',
active: currentPage === 'discover',
icon: 'Compass',
},
{
label: 'Subscriptions',
path: '/subscriptions',
active: currentPage === 'subscriptions',
icon: 'AtSign',
},
],
secondary: [
{
label: 'Wallet',
icon: 'CreditCard',
subLinks: walletSubLinks,
path: isCurrentlyWalletPage ? '/wallet' : getActiveSublink('wallet'),
active: isWalletPage(currentPage),
},
{
label: 'My LBRY',
icon: 'Settings',
subLinks: myLbrySubLinks,
path: isCurrentlyMyLbryPage ? '/downloaded' : getActiveSublink('myLbry'),
active: isMyLbryPage(currentPage),
},
{
label: 'Publish',
icon: 'UploadCloud',
path: '/publish',
active: currentPage === 'publish',
},
{
label: 'Help',
path: '/help',
active: currentPage === 'help',
icon: 'HelpCircle',
},
],
};
return navLinks;
}
);

View file

@ -1,12 +1,12 @@
import {
selectCurrentPage,
selectCurrentParams,
selectPageTitle,
} from 'redux/selectors/navigation';
import { selectCurrentPage, selectCurrentParams } from 'redux/selectors/navigation';
import { createSelector } from 'reselect';
export const selectState = state => state.search || {};
export const selectSearchState = selectState;
export const selectSearchValue = createSelector(selectState, state => state.searchQuery);
export const selectSearchQuery = createSelector(
selectCurrentPage,
selectCurrentParams,
@ -26,54 +26,14 @@ export const makeSelectSearchUris = query =>
export const selectWunderBarAddress = createSelector(
selectCurrentPage,
selectPageTitle,
selectSearchQuery,
(page, title, query) => (page !== 'search' ? title : query || title)
);
export const selectWunderBarIcon = createSelector(
selectCurrentPage,
selectCurrentParams,
(page, params) => {
switch (page) {
case 'auth':
return 'icon-user';
case 'settings':
return 'icon-gear';
case 'help':
return 'icon-question';
case 'report':
return 'icon-file';
case 'downloaded':
return 'icon-folder';
case 'published':
return 'icon-folder';
case 'history':
return 'icon-history';
case 'send':
return 'icon-send';
case 'rewards':
return 'icon-rocket';
case 'invite':
return 'icon-envelope-open';
case 'getcredits':
return 'icon-shopping-cart';
case 'wallet':
case 'backup':
return 'icon-bank';
case 'show':
return 'icon-file';
case 'publish':
return params.id ? __('icon-pencil') : __('icon-upload');
case 'developer':
return 'icon-code';
case 'discover':
case 'search':
return 'icon-search';
case 'subscriptions':
return 'icon-th-list';
default:
return 'icon-file';
(page, query, params) => {
// only populate the wunderbar address if we are on the file/channel pages
// or show the search query
if (page === 'show') {
return params.uri;
}
return query;
}
);

5
src/util/handle-fetch.js Normal file
View file

@ -0,0 +1,5 @@
export default function handleFetchResponse(response) {
return response.status === 200
? Promise.resolve(response.json())
: Promise.reject(new Error(response.statusText));
}

17
src/util/redux-utils.js Normal file
View file

@ -0,0 +1,17 @@
// util for creating reducers
// based off of redux-actions
// https://redux-actions.js.org/docs/api/handleAction.html#handleactions
// eslint-disable-next-line import/prefer-default-export
export const handleActions = (actionMap, defaultState) => (state = defaultState, action) => {
const handler = actionMap[action.type];
if (handler) {
const newState = handler(state, action);
return Object.assign({}, state, newState);
}
// just return the original state if no handler
// returning a copy here breaks redux-persist
return state;
};