[WIP] Comments #131

Closed
neb-b wants to merge 2 commits from comments into master
11 changed files with 380 additions and 6169 deletions

View file

@ -1,6 +1,12 @@
{
"linters": {
"src/**/*.{js,json}": ["prettier --write", "git add"],
"src/**/*.js": ["eslint --fix", "git add"]
"src/**/*.{js,json}": [
"prettier --write",
"git add"
],
"src/**/*.js": [
"eslint --fix",
"git add"
]
}
}
}

View file

@ -20,6 +20,15 @@ npm link lbry-redux
### Build
Run `$ yarn build`. If the symlink does not work, just build the file and move the `bundle.js` file in to the `node_modules/` folder.
#### Local Development with `lbry-desktop`
If you're working with the desktop app and you've followed the steps above, then you'll want to
run `$ yarn dev` (or equivalently `$ webpack --watch`). This will allow any changes made to the code
to be automatically reflected in `dist/bundle.js`.
Once you've made your changes, running `(lbry-desktop)$ yarn dev` should have it automatically
reloading changes. If this doesn't happen, just rebuild `lbry-redux` and
[relink it to `lbry-desktop`](.README.md:11)
## Contributing
We :heart: contributions from everyone! We welcome [bug reports](https://github.com/lbryio/lbry-redux/issues/), [bug fixes](https://github.com/lbryio/lbry-redux/pulls) and feedback on the module is always appreciated.

24
dist/bundle.es.js vendored
View file

@ -653,6 +653,9 @@ const Lbry = {
sync_hash: (params = {}) => daemonCallWithResult('sync_hash', params),
sync_apply: (params = {}) => daemonCallWithResult('sync_apply', params),
comment_list: (params = {}) => daemonCallWithResult('comment_list', params),
comment_create: (params = {}) => daemonCallWithResult('comment_create', params),
// Connect to the sdk
connect: () => {
if (Lbry.connectPromise === null) {
@ -1430,9 +1433,14 @@ const selectTransactionItems = reselect.createSelector(selectTransactionsById, b
append.push(...tx.abandon_info.map(item => Object.assign({}, tx, item, { type: ABANDON })));
if (!append.length) {
append.push(Object.assign({}, tx, {
type: tx.value < 0 ? SPEND : RECEIVE
}));
append.push(...tx.claim_info.map(item => Object.assign({}, tx, item, {
type: item.claim_name[0] === '@' ? CHANNEL : PUBLISH
})));
append.push(...tx.support_info.map(item => Object.assign({}, tx, item, {
type: !item.is_tip ? SUPPORT : TIP
})));
append.push(...tx.update_info.map(item => Object.assign({}, tx, item, { type: UPDATE })));
append.push(...tx.abandon_info.map(item => Object.assign({}, tx, item, { type: ABANDON })));
}
items.push(...append.map(item => {
@ -2229,14 +2237,18 @@ const selectSearchDownloadUris = query => reselect.createSelector(selectFileInfo
});
return downloadResultsFromQuery.length ? downloadResultsFromQuery.map(fileInfo => {
const { channel_name: channelName, claim_id: claimId, claim_name: claimName } = fileInfo;
const {
channel_name: channelName,
claim_id: claimId,
claim_name: claimName
} = fileInfo;
const uriParams = {};
if (channelName) {
const claim = claimsById[claimId];
if (claim && claim.value) {
uriParams.claimId = claim.value.publisherSignature.certificateId;
if (claim) {
uriParams.claimId = claim.channel_id;
} else {
uriParams.claimId = claimId;
}

5929
dist/bundle.js vendored

File diff suppressed because it is too large Load diff

View file

@ -90,6 +90,9 @@ const Lbry: LbryTypes = {
sync_hash: (params = {}) => daemonCallWithResult('sync_hash', params),
sync_apply: (params = {}) => daemonCallWithResult('sync_apply', params),
comment_list: (params = {}) => daemonCallWithResult('comment_list', params),
comment_create: (params = {}) => daemonCallWithResult('comment_create', params),
// Connect to the sdk
connect: () => {
if (Lbry.connectPromise === null) {

View file

@ -4,11 +4,15 @@ import { makeSelectClaimForUri } from 'redux/selectors/claims';
export const selectState = (state: any) => state.content || {};
export const makeSelectContentPositionForUri = (uri: string) =>
createSelector(selectState, makeSelectClaimForUri(uri), (state, claim) => {
if (!claim) {
return null;
createSelector(
selectState,
makeSelectClaimForUri(uri),
(state, claim) => {
if (!claim) {
return null;
}
const outpoint = `${claim.txid}:${claim.nout}`;
const id = claim.claim_id;
return state.positions[id] ? state.positions[id][outpoint] : null;
}
const outpoint = `${claim.txid}:${claim.nout}`;
const id = claim.claim_id;
return state.positions[id] ? state.positions[id][outpoint] : null;
});
);

View file

@ -26,11 +26,15 @@ export const selectIsFetchingFileListDownloadedOrPublished = createSelector(
);
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;
});
createSelector(
selectClaimsByUri,
selectFileInfosByOutpoint,
(claims, byOutpoint) => {
const claim = claims[uri];
const outpoint = claim ? `${claim.txid}:${claim.nout}` : undefined;
return outpoint ? byOutpoint[outpoint] : undefined;
}
);
export const selectDownloadingByOutpoint = createSelector(
selectState,
@ -47,10 +51,16 @@ export const makeSelectDownloadingForUri = uri =>
}
);
export const selectUrisLoading = createSelector(selectState, state => state.urisLoading || {});
export const selectUrisLoading = createSelector(
selectState,
state => state.urisLoading || {}
);
export const makeSelectLoadingForUri = uri =>
createSelector(selectUrisLoading, byUri => byUri && byUri[uri]);
createSelector(
selectUrisLoading,
byUri => byUri && byUri[uri]
);
export const selectFileInfosDownloaded = createSelector(
selectFileInfosByOutpoint,
@ -93,93 +103,103 @@ export const selectDownloadingFileInfos = createSelector(
}
);
export const selectTotalDownloadProgress = createSelector(selectDownloadingFileInfos, fileInfos => {
const progress = [];
export const selectTotalDownloadProgress = createSelector(
selectDownloadingFileInfos,
fileInfos => {
const progress = [];
fileInfos.forEach(fileInfo => {
progress.push((fileInfo.written_bytes / fileInfo.total_bytes) * 100);
});
fileInfos.forEach(fileInfo => {
progress.push((fileInfo.written_bytes / fileInfo.total_bytes) * 100);
});
const totalProgress = progress.reduce((a, b) => a + b, 0);
const totalProgress = progress.reduce((a, b) => a + b, 0);
if (fileInfos.length > 0) return totalProgress / fileInfos.length / 100.0;
return -1;
});
if (fileInfos.length > 0) return totalProgress / fileInfos.length / 100.0;
return -1;
}
);
export const selectSearchDownloadUris = query =>
createSelector(selectFileInfosDownloaded, selectClaimsById, (fileInfos, claimsById) => {
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;
}
createSelector(
selectFileInfosDownloaded,
selectClaimsById,
(fileInfos, claimsById) => {
if (!query || !fileInfos.length) {
return null;
}
return false;
};
const downloadResultsFromQuery = [];
fileInfos.forEach(fileInfo => {
const { channel_name: channelName, claim_name: claimName, metadata } = fileInfo;
const { author, description, title } = metadata;
const queryParts = query.toLowerCase().split(' ');
const searchQueryDictionary = {};
queryParts.forEach(subQuery => {
searchQueryDictionary[subQuery] = subQuery;
});
if (channelName) {
const lowerCaseChannel = channelName.toLowerCase();
const strippedOutChannelName = lowerCaseChannel.slice(1); // trim off the @
if (searchQueryDictionary[channelName] || searchQueryDictionary[strippedOutChannelName]) {
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: channelName, claim_name: claimName, metadata } = fileInfo;
const { author, description, title } = metadata;
if (channelName) {
const lowerCaseChannel = channelName.toLowerCase();
const strippedOutChannelName = lowerCaseChannel.slice(1); // trim off the @
if (searchQueryDictionary[channelName] || searchQueryDictionary[strippedOutChannelName]) {
downloadResultsFromQuery.push(fileInfo);
return;
}
}
const nameParts = claimName.toLowerCase().split('-');
if (arrayContainsQueryPart(nameParts)) {
downloadResultsFromQuery.push(fileInfo);
return;
}
}
const nameParts = claimName.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)) {
const titleParts = title.toLowerCase().split(' ');
if (arrayContainsQueryPart(titleParts)) {
downloadResultsFromQuery.push(fileInfo);
return;
}
}
if (description) {
const descriptionParts = description.toLowerCase().split(' ');
if (arrayContainsQueryPart(descriptionParts)) {
downloadResultsFromQuery.push(fileInfo);
if (author) {
const authorParts = author.toLowerCase().split(' ');
if (arrayContainsQueryPart(authorParts)) {
downloadResultsFromQuery.push(fileInfo);
return;
}
}
}
});
return downloadResultsFromQuery.length
? downloadResultsFromQuery.map(fileInfo => {
const { channel_name: channelName, claim_id: claimId, claim_name: claimName } = fileInfo;
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,
} = fileInfo;
const uriParams = {};
if (channelName) {
const claim = claimsById[claimId];
if (claim && claim.value) {
uriParams.claimId = claim.value.publisherSignature.certificateId;
if (claim) {
uriParams.claimId = claim.channel_id;
} else {
uriParams.claimId = claimId;
}
@ -193,8 +213,9 @@ export const selectSearchDownloadUris = query =>
const uri = buildURI(uriParams);
return uri;
})
: null;
});
: null;
}
);
export const selectFileListPublishedSort = createSelector(
selectState,

View file

@ -3,38 +3,63 @@ import { parseQueryParams } from 'util/query_params';
export const selectState = state => state.navigation || {};
export const selectCurrentPath = createSelector(selectState, state => state.currentPath);
export const selectCurrentPath = createSelector(
selectState,
state => state.currentPath
);
export const computePageFromPath = path => (path ? path.replace(/^\//, '').split('?')[0] : '');
export const selectCurrentPage = createSelector(selectCurrentPath, path =>
computePageFromPath(path)
export const selectCurrentPage = createSelector(
selectCurrentPath,
path => computePageFromPath(path)
);
export const selectCurrentParams = createSelector(selectCurrentPath, path => {
if (path === undefined) return {};
if (!path.match(/\?/)) return {};
export const selectCurrentParams = createSelector(
selectCurrentPath,
path => {
if (path === undefined) return {};
if (!path.match(/\?/)) return {};
return parseQueryParams(path.split('?')[1]);
});
return parseQueryParams(path.split('?')[1]);
}
);
export const makeSelectCurrentParam = param =>
createSelector(selectCurrentParams, params => (params ? params[param] : undefined));
createSelector(
selectCurrentParams,
params => (params ? params[param] : undefined)
);
export const selectPathAfterAuth = createSelector(selectState, state => state.pathAfterAuth);
export const selectPathAfterAuth = createSelector(
selectState,
state => state.pathAfterAuth
);
export const selectIsBackDisabled = createSelector(selectState, state => state.index === 0);
export const selectIsBackDisabled = createSelector(
selectState,
state => state.index === 0
);
export const selectIsForwardDisabled = createSelector(
selectState,
state => state.index === state.stack.length - 1
);
export const selectIsHome = createSelector(selectCurrentPage, page => page === 'discover');
export const selectIsHome = createSelector(
selectCurrentPage,
page => page === 'discover'
);
export const selectHistoryIndex = createSelector(selectState, state => state.index);
export const selectHistoryIndex = createSelector(
selectState,
state => state.index
);
export const selectHistoryStack = createSelector(selectState, state => state.stack);
export const selectHistoryStack = createSelector(
selectState,
state => state.stack
);
// returns current page attributes (scrollY, path)
export const selectActiveHistoryEntry = createSelector(
@ -42,9 +67,12 @@ export const selectActiveHistoryEntry = createSelector(
state => state.stack[state.index]
);
export const selectPageTitle = createSelector(selectCurrentPage, page => {
switch (page) {
default:
return '';
export const selectPageTitle = createSelector(
selectCurrentPage,
page => {
switch (page) {
default:
return '';
}
}
});
);

View file

@ -1,26 +1,32 @@
import { createSelector } from 'reselect';
export const selectState = (state) => state.notifications || {};
export const selectState = state => state.notifications || {};
export const selectToast = createSelector(selectState, (state) => {
if (state.toasts.length) {
const { id, params } = state.toasts[0];
return {
id,
...params,
};
export const selectToast = createSelector(
selectState,
state => {
if (state.toasts.length) {
const { id, params } = state.toasts[0];
return {
id,
...params,
};
}
return null;
}
);
return null;
});
export const selectError = createSelector(
selectState,
state => {
if (state.errors.length) {
const { error } = state.errors[0];
return {
error,
};
}
export const selectError = createSelector(selectState, (state) => {
if (state.errors.length) {
const { error } = state.errors[0];
return {
error,
};
return null;
}
return null;
});
);

View file

@ -65,112 +65,147 @@ export const selectWalletLockSucceeded = createSelector(
state => state.walletLockSucceded
);
export const selectWalletLockResult = createSelector(selectState, state => state.walletLockResult);
export const selectWalletLockResult = createSelector(
selectState,
state => state.walletLockResult
);
export const selectBalance = createSelector(selectState, state => state.balance);
export const selectBalance = createSelector(
selectState,
state => state.balance
);
export const selectTotalBalance = createSelector(selectState, state => state.totalBalance);
export const selectTotalBalance = createSelector(
selectState,
state => state.totalBalance
);
export const selectTransactionsById = createSelector(selectState, state => state.transactions);
export const selectTransactionsById = createSelector(
selectState,
state => state.transactions
);
export const selectTransactionItems = createSelector(selectTransactionsById, byId => {
const items = [];
export const selectTransactionItems = createSelector(
selectTransactionsById,
byId => {
const items = [];
Object.keys(byId).forEach(txid => {
const tx = byId[txid];
Object.keys(byId).forEach(txid => {
const tx = byId[txid];
// ignore dust/fees
// it is fee only txn if all infos are also empty
if (
Math.abs(tx.value) === Math.abs(tx.fee) &&
tx.claim_info.length === 0 &&
tx.support_info.length === 0 &&
tx.update_info.length === 0 &&
tx.abandon_info.length === 0
) {
return;
}
// ignore dust/fees
// it is fee only txn if all infos are also empty
if (
Math.abs(tx.value) === Math.abs(tx.fee) &&
tx.claim_info.length === 0 &&
tx.support_info.length === 0 &&
tx.update_info.length === 0 &&
tx.abandon_info.length === 0
) {
return;
}
const append = [];
const append = [];
append.push(
...tx.claim_info.map(item =>
Object.assign({}, tx, item, {
type: item.claim_name[0] === '@' ? TRANSACTIONS.CHANNEL : TRANSACTIONS.PUBLISH,
})
)
);
append.push(
...tx.support_info.map(item =>
Object.assign({}, tx, item, {
type: !item.is_tip ? TRANSACTIONS.SUPPORT : TRANSACTIONS.TIP,
})
)
);
append.push(
...tx.update_info.map(item => Object.assign({}, tx, item, { type: TRANSACTIONS.UPDATE }))
);
append.push(
...tx.abandon_info.map(item => Object.assign({}, tx, item, { type: TRANSACTIONS.ABANDON }))
);
if (!append.length) {
append.push(
Object.assign({}, tx, {
type: tx.value < 0 ? TRANSACTIONS.SPEND : TRANSACTIONS.RECEIVE,
...tx.claim_info.map(item =>
Object.assign({}, tx, item, {
type: item.claim_name[0] === '@' ? TRANSACTIONS.CHANNEL : TRANSACTIONS.PUBLISH,
})
)
);
append.push(
...tx.support_info.map(item =>
Object.assign({}, tx, item, {
type: !item.is_tip ? TRANSACTIONS.SUPPORT : TRANSACTIONS.TIP,
})
)
);
append.push(
...tx.update_info.map(item => Object.assign({}, tx, item, { type: TRANSACTIONS.UPDATE }))
);
append.push(
...tx.abandon_info.map(item => Object.assign({}, tx, item, { type: TRANSACTIONS.ABANDON }))
);
if (!append.length) {
append.push(
...tx.claim_info.map(item =>
Object.assign({}, tx, item, {
type: item.claim_name[0] === '@' ? TRANSACTIONS.CHANNEL : TRANSACTIONS.PUBLISH,
})
)
);
append.push(
...tx.support_info.map(item =>
Object.assign({}, tx, item, {
type: !item.is_tip ? TRANSACTIONS.SUPPORT : TRANSACTIONS.TIP,
})
)
);
append.push(
...tx.update_info.map(item => Object.assign({}, tx, item, { type: TRANSACTIONS.UPDATE }))
);
append.push(
...tx.abandon_info.map(item =>
Object.assign({}, tx, item, { type: TRANSACTIONS.ABANDON })
)
);
}
items.push(
...append.map(item => {
// value on transaction, amount on outpoint
// amount is always positive, but should match sign of value
const balanceDelta = parseFloat(item.balance_delta);
const value = parseFloat(item.value);
const amount = balanceDelta || value;
const fee = parseFloat(tx.fee);
return {
txid,
timestamp: tx.timestamp,
date: tx.timestamp ? new Date(Number(tx.timestamp) * 1000) : null,
amount,
fee,
claim_id: item.claim_id,
claim_name: item.claim_name,
type: item.type || TRANSACTIONS.SPEND,
nout: item.nout,
confirmations: tx.confirmations,
};
})
);
}
});
items.push(
...append.map(item => {
// value on transaction, amount on outpoint
// amount is always positive, but should match sign of value
const balanceDelta = parseFloat(item.balance_delta);
const value = parseFloat(item.value);
const amount = balanceDelta || value;
const fee = parseFloat(tx.fee);
return items.sort((tx1, tx2) => {
if (!tx1.timestamp && !tx2.timestamp) {
return 0;
} else if (!tx1.timestamp && tx2.timestamp) {
return -1;
} else if (tx1.timestamp && !tx2.timestamp) {
return 1;
}
return {
txid,
timestamp: tx.timestamp,
date: tx.timestamp ? new Date(Number(tx.timestamp) * 1000) : null,
amount,
fee,
claim_id: item.claim_id,
claim_name: item.claim_name,
type: item.type || TRANSACTIONS.SPEND,
nout: item.nout,
confirmations: tx.confirmations,
};
})
);
});
return tx2.timestamp - tx1.timestamp;
});
}
);
return items.sort((tx1, tx2) => {
if (!tx1.timestamp && !tx2.timestamp) {
return 0;
} else if (!tx1.timestamp && tx2.timestamp) {
return -1;
} else if (tx1.timestamp && !tx2.timestamp) {
return 1;
}
export const selectRecentTransactions = createSelector(
selectTransactionItems,
transactions => {
const threshold = new Date();
threshold.setDate(threshold.getDate() - 7);
return transactions.filter(transaction => {
if (!transaction.date) {
return true; // pending transaction
}
return tx2.timestamp - tx1.timestamp;
});
});
export const selectRecentTransactions = createSelector(selectTransactionItems, transactions => {
const threshold = new Date();
threshold.setDate(threshold.getDate() - 7);
return transactions.filter(transaction => {
if (!transaction.date) {
return true; // pending transaction
}
return transaction.date > threshold;
});
});
return transaction.date > threshold;
});
}
);
export const selectHasTransactions = createSelector(
selectTransactionItems,
@ -182,9 +217,15 @@ export const selectIsFetchingTransactions = createSelector(
state => state.fetchingTransactions
);
export const selectIsSendingSupport = createSelector(selectState, state => state.sendingSupport);
export const selectIsSendingSupport = createSelector(
selectState,
state => state.sendingSupport
);
export const selectReceiveAddress = createSelector(selectState, state => state.receiveAddress);
export const selectReceiveAddress = createSelector(
selectState,
state => state.receiveAddress
);
export const selectGettingNewAddress = createSelector(
selectState,
@ -211,30 +252,40 @@ export const selectDraftTransactionError = createSelector(
draft => draft.error
);
export const selectBlocks = createSelector(selectState, state => state.blocks);
export const selectBlocks = createSelector(
selectState,
state => state.blocks
);
export const selectCurrentHeight = createSelector(selectState, state => state.latestBlock);
export const selectCurrentHeight = createSelector(
selectState,
state => state.latestBlock
);
export const makeSelectBlockDate = block =>
createSelector(selectBlocks, selectCurrentHeight, (blocks, latestBlock) => {
// If we have the block data, look at the actual date,
// If not, try to simulate it based on 2.5 minute blocks
// Adding this on 11/7/2018 because caling block_show for every claim is causing
// performance issues.
if (blocks && blocks[block]) {
return new Date(blocks[block].time * 1000);
}
createSelector(
selectBlocks,
selectCurrentHeight,
(blocks, latestBlock) => {
// If we have the block data, look at the actual date,
// If not, try to simulate it based on 2.5 minute blocks
// Adding this on 11/7/2018 because caling block_show for every claim is causing
// performance issues.
if (blocks && blocks[block]) {
return new Date(blocks[block].time * 1000);
}
// Pending claim
if (block < 1) {
return null;
}
// Pending claim
if (block < 1) {
return null;
}
const difference = latestBlock - block;
const msSincePublish = difference * 2.5 * 60 * 1000; // Number of blocks * 2.5 minutes in ms
const publishDate = Date.now() - msSincePublish;
return new Date(publishDate);
});
const difference = latestBlock - block;
const msSincePublish = difference * 2.5 * 60 * 1000; // Number of blocks * 2.5 minutes in ms
const publishDate = Date.now() - msSincePublish;
return new Date(publishDate);
}
);
export const selectTransactionListFilter = createSelector(
selectState,

View file

@ -13,7 +13,7 @@ export function formatFullPrice(amount, precision = 1) {
if (fraction) {
const decimals = fraction.split('');
const first = decimals.filter((number) => number !== '0')[0];
const first = decimals.filter(number => number !== '0')[0];
const index = decimals.indexOf(first);
// Set format fraction