commit
83d5c076e4
36 changed files with 716 additions and 161 deletions
|
@ -50,7 +50,7 @@
|
|||
"formik": "^0.10.4",
|
||||
"hast-util-sanitize": "^1.1.2",
|
||||
"keytar": "^4.2.1",
|
||||
"lbry-redux": "lbryio/lbry-redux#421321a78397251589e5a890f4caa95e79975e2b",
|
||||
"lbry-redux": "lbryio/lbry-redux#d1cee82af119c0c5f98ec27f94b2e7f61e34b54c",
|
||||
"localforage": "^1.7.1",
|
||||
"mammoth": "^1.4.6",
|
||||
"mime": "^2.3.1",
|
||||
|
|
|
@ -24,6 +24,9 @@ type Props = {
|
|||
stretch?: boolean,
|
||||
affixClass?: string, // class applied to prefix/postfix label
|
||||
firstInList?: boolean, // at the top of a list, no padding top
|
||||
inputProps: {
|
||||
disabled?: boolean,
|
||||
},
|
||||
};
|
||||
|
||||
export class FormField extends React.PureComponent<Props> {
|
||||
|
|
|
@ -9,6 +9,7 @@ type Props = {
|
|||
verticallyCentered?: boolean,
|
||||
stretch?: boolean,
|
||||
alignRight?: boolean,
|
||||
centered?: boolean,
|
||||
};
|
||||
|
||||
export class FormRow extends React.PureComponent<Props> {
|
||||
|
@ -17,7 +18,7 @@ export class FormRow extends React.PureComponent<Props> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { children, padded, verticallyCentered, stretch, alignRight } = this.props;
|
||||
const { children, padded, verticallyCentered, stretch, alignRight, centered } = this.props;
|
||||
return (
|
||||
<div
|
||||
className={classnames('form-row', {
|
||||
|
@ -25,6 +26,7 @@ export class FormRow extends React.PureComponent<Props> {
|
|||
'form-row--vertically-centered': verticallyCentered,
|
||||
'form-row--stretch': stretch,
|
||||
'form-row--right': alignRight,
|
||||
'form-row--centered': centered,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -8,7 +8,10 @@ import {
|
|||
makeSelectClaimIsMine,
|
||||
} from 'lbry-redux';
|
||||
import { doNavigate } from 'redux/actions/navigation';
|
||||
import { selectRewardContentClaimIds } from 'redux/selectors/content';
|
||||
import {
|
||||
selectRewardContentClaimIds,
|
||||
makeSelectContentPositionForUri,
|
||||
} from 'redux/selectors/content';
|
||||
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||
import { selectPendingPublish } from 'redux/selectors/publish';
|
||||
import FileCard from './view';
|
||||
|
@ -32,12 +35,14 @@ const select = (state, props) => {
|
|||
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
|
||||
...fileCardInfo,
|
||||
pending: !!pendingPublish,
|
||||
position: makeSelectContentPositionForUri(props.uri)(state),
|
||||
};
|
||||
};
|
||||
|
||||
const perform = dispatch => ({
|
||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||
clearHistoryUri: uri => dispatch(doClearContentHistoryUri(uri)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import moment from 'moment';
|
||||
import { normalizeURI, convertToShareLink } from 'lbry-redux';
|
||||
import type { Claim, Metadata } from 'types/claim';
|
||||
import CardMedia from 'component/cardMedia';
|
||||
import TruncatedText from 'component/common/truncated-text';
|
||||
import Icon from 'component/common/icon';
|
||||
import FilePrice from 'component/filePrice';
|
||||
import UriIndicator from 'component/uriIndicator';
|
||||
import * as icons from 'constants/icons';
|
||||
import classnames from 'classnames';
|
||||
import { openCopyLinkMenu } from '../../util/contextMenu';
|
||||
import FilePrice from 'component/filePrice';
|
||||
import { openCopyLinkMenu } from 'util/contextMenu';
|
||||
|
||||
// TODO: iron these out
|
||||
type Props = {
|
||||
|
@ -21,8 +22,10 @@ type Props = {
|
|||
rewardedContentClaimIds: Array<string>,
|
||||
obscureNsfw: boolean,
|
||||
claimIsMine: boolean,
|
||||
showPrice: boolean,
|
||||
pending?: boolean,
|
||||
position: ?number,
|
||||
lastViewed: ?number,
|
||||
clearHistoryUri: string => void,
|
||||
/* eslint-disable react/no-unused-prop-types */
|
||||
resolveUri: string => void,
|
||||
isResolvingUri: boolean,
|
||||
|
@ -59,8 +62,10 @@ class FileCard extends React.PureComponent<Props> {
|
|||
rewardedContentClaimIds,
|
||||
obscureNsfw,
|
||||
claimIsMine,
|
||||
showPrice,
|
||||
pending,
|
||||
position,
|
||||
clearHistoryUri,
|
||||
showPrice,
|
||||
} = this.props;
|
||||
|
||||
const shouldHide = !claimIsMine && !pending && obscureNsfw && metadata && metadata.nsfw;
|
||||
|
@ -103,6 +108,7 @@ class FileCard extends React.PureComponent<Props> {
|
|||
{showPrice && <FilePrice hideFree uri={uri} />}
|
||||
{isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />}
|
||||
{fileInfo && <Icon icon={icons.LOCAL} />}
|
||||
{position && <Icon icon={icons.REFRESH} />}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -7,8 +7,7 @@ import {
|
|||
makeSelectClaimForUri,
|
||||
} from 'lbry-redux';
|
||||
import { doOpenFileInShell } from 'redux/actions/file';
|
||||
import { doPurchaseUri, doStartDownload } from 'redux/actions/content';
|
||||
import { doPause } from 'redux/actions/media';
|
||||
import { doPurchaseUri, doStartDownload, doSetPlayingUri } from 'redux/actions/content';
|
||||
import FileDownloadLink from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
@ -24,7 +23,7 @@ const perform = dispatch => ({
|
|||
openInShell: path => dispatch(doOpenFileInShell(path)),
|
||||
purchaseUri: uri => dispatch(doPurchaseUri(uri)),
|
||||
restartDownload: (uri, outpoint) => dispatch(doStartDownload(uri, outpoint)),
|
||||
doPause: () => dispatch(doPause()),
|
||||
pause: () => dispatch(doSetPlayingUri(null)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -22,7 +22,7 @@ type Props = {
|
|||
restartDownload: (string, number) => void,
|
||||
openInShell: string => void,
|
||||
purchaseUri: string => void,
|
||||
doPause: () => void,
|
||||
pause: () => void,
|
||||
};
|
||||
|
||||
class FileDownloadLink extends React.PureComponent<Props> {
|
||||
|
@ -50,14 +50,13 @@ class FileDownloadLink extends React.PureComponent<Props> {
|
|||
purchaseUri,
|
||||
costInfo,
|
||||
loading,
|
||||
doPause,
|
||||
claim,
|
||||
pause,
|
||||
} = this.props;
|
||||
|
||||
const openFile = () => {
|
||||
if (fileInfo) {
|
||||
openInShell(fileInfo.download_path);
|
||||
doPause();
|
||||
pause();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -2,9 +2,7 @@ import { connect } from 'react-redux';
|
|||
import * as settings from 'constants/settings';
|
||||
import { doChangeVolume } from 'redux/actions/app';
|
||||
import { selectVolume } from 'redux/selectors/app';
|
||||
import { doPlayUri, doSetPlayingUri } from 'redux/actions/content';
|
||||
import { doPlay, doPause, savePosition } from 'redux/actions/media';
|
||||
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
|
||||
import { doPlayUri, doSetPlayingUri, savePosition } from 'redux/actions/content';
|
||||
import {
|
||||
makeSelectMetadataForUri,
|
||||
makeSelectContentTypeForUri,
|
||||
|
@ -15,9 +13,9 @@ import {
|
|||
makeSelectDownloadingForUri,
|
||||
selectSearchBarFocused,
|
||||
} from 'lbry-redux';
|
||||
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
|
||||
import { makeSelectClientSetting, selectShowNsfw } from 'redux/selectors/settings';
|
||||
import { selectMediaPaused, makeSelectMediaPositionForUri } from 'redux/selectors/media';
|
||||
import { selectPlayingUri } from 'redux/selectors/content';
|
||||
import { selectPlayingUri, makeSelectContentPositionForUri } from 'redux/selectors/content';
|
||||
import { selectFileInfoErrors } from 'redux/selectors/file_info';
|
||||
import FileViewer from './view';
|
||||
|
||||
|
@ -32,8 +30,7 @@ const select = (state, props) => ({
|
|||
playingUri: selectPlayingUri(state),
|
||||
contentType: makeSelectContentTypeForUri(props.uri)(state),
|
||||
volume: selectVolume(state),
|
||||
mediaPaused: selectMediaPaused(state),
|
||||
mediaPosition: makeSelectMediaPositionForUri(props.uri)(state),
|
||||
position: makeSelectContentPositionForUri(props.uri)(state),
|
||||
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state),
|
||||
searchBarFocused: selectSearchBarFocused(state),
|
||||
fileInfoErrors: selectFileInfoErrors(state),
|
||||
|
@ -43,10 +40,9 @@ const perform = dispatch => ({
|
|||
play: uri => dispatch(doPlayUri(uri)),
|
||||
cancelPlay: () => dispatch(doSetPlayingUri(null)),
|
||||
changeVolume: volume => dispatch(doChangeVolume(volume)),
|
||||
doPlay: () => dispatch(doPlay()),
|
||||
doPause: () => dispatch(doPause()),
|
||||
savePosition: (claimId, position) => dispatch(savePosition(claimId, position)),
|
||||
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
|
||||
savePosition: (claimId, outpoint, position) =>
|
||||
dispatch(savePosition(claimId, outpoint, position)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -27,9 +27,11 @@ class MediaPlayer extends React.PureComponent {
|
|||
this.toggleFullScreenVideo = this.toggleFullScreen.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
componentDidUpdate(nextProps) {
|
||||
const el = this.refs.media.children[0];
|
||||
if (!this.props.paused && nextProps.paused && !el.paused) el.pause();
|
||||
if (this.props.playingUri && !nextProps.playingUri && !el.paused) {
|
||||
el.pause();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -86,11 +88,15 @@ class MediaPlayer extends React.PureComponent {
|
|||
document.addEventListener('keydown', this.togglePlayListener);
|
||||
const mediaElement = this.media.children[0];
|
||||
if (mediaElement) {
|
||||
mediaElement.currentTime = position || 0;
|
||||
mediaElement.addEventListener('play', () => this.props.doPlay());
|
||||
mediaElement.addEventListener('pause', () => this.props.doPause());
|
||||
if (position) {
|
||||
mediaElement.currentTime = position;
|
||||
}
|
||||
mediaElement.addEventListener('timeupdate', () =>
|
||||
this.props.savePosition(claim.claim_id, mediaElement.currentTime)
|
||||
this.props.savePosition(
|
||||
claim.claim_id,
|
||||
`${claim.txid}:${claim.nout}`,
|
||||
mediaElement.currentTime
|
||||
)
|
||||
);
|
||||
mediaElement.addEventListener('click', this.togglePlayListener);
|
||||
mediaElement.addEventListener('loadedmetadata', loadedMetadata.bind(this), {
|
||||
|
@ -136,7 +142,6 @@ class MediaPlayer extends React.PureComponent {
|
|||
if (mediaElement) {
|
||||
mediaElement.removeEventListener('click', this.togglePlayListener);
|
||||
}
|
||||
this.props.doPause();
|
||||
}
|
||||
|
||||
toggleFullScreen(event) {
|
||||
|
|
|
@ -34,11 +34,8 @@ type Props = {
|
|||
volume: number,
|
||||
claim: Claim,
|
||||
uri: string,
|
||||
doPlay: () => void,
|
||||
doPause: () => void,
|
||||
savePosition: (string, number) => void,
|
||||
mediaPaused: boolean,
|
||||
mediaPosition: ?number,
|
||||
savePosition: (string, string, number) => void,
|
||||
position: ?number,
|
||||
className: ?string,
|
||||
obscureNsfw: boolean,
|
||||
play: string => void,
|
||||
|
@ -202,11 +199,8 @@ class FileViewer extends React.PureComponent<Props> {
|
|||
volume,
|
||||
claim,
|
||||
uri,
|
||||
doPlay,
|
||||
doPause,
|
||||
savePosition,
|
||||
mediaPaused,
|
||||
mediaPosition,
|
||||
position,
|
||||
className,
|
||||
obscureNsfw,
|
||||
mediaType,
|
||||
|
@ -251,14 +245,12 @@ class FileViewer extends React.PureComponent<Props> {
|
|||
downloadCompleted={fileInfo.completed}
|
||||
changeVolume={changeVolume}
|
||||
volume={volume}
|
||||
doPlay={doPlay}
|
||||
doPause={doPause}
|
||||
savePosition={savePosition}
|
||||
claim={claim}
|
||||
uri={uri}
|
||||
paused={mediaPaused}
|
||||
position={mediaPosition}
|
||||
position={position}
|
||||
startedPlayingCb={this.startedPlayingCb}
|
||||
playingUri={playingUri}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -4,11 +4,10 @@ import {
|
|||
selectPageTitle,
|
||||
selectIsBackDisabled,
|
||||
selectIsForwardDisabled,
|
||||
selectNavLinks,
|
||||
} from 'lbry-redux';
|
||||
import { doNavigate, doHistoryBack, doHistoryForward } from 'redux/actions/navigation';
|
||||
import { doDownloadUpgrade } from 'redux/actions/app';
|
||||
import { selectIsUpgradeAvailable } from 'redux/selectors/app';
|
||||
import { selectIsUpgradeAvailable, selectNavLinks } from 'redux/selectors/app';
|
||||
import { formatCredits } from 'util/formatCredits';
|
||||
import Page from './view';
|
||||
|
||||
|
@ -28,4 +27,7 @@ const perform = dispatch => ({
|
|||
downloadUpgrade: () => dispatch(doDownloadUpgrade()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(Page);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(Page);
|
||||
|
|
|
@ -18,6 +18,7 @@ import InvitePage from 'page/invite';
|
|||
import BackupPage from 'page/backup';
|
||||
import SubscriptionsPage from 'page/subscriptions';
|
||||
import SearchPage from 'page/search';
|
||||
import UserHistoryPage from 'page/userHistory';
|
||||
|
||||
const route = (props, page, routesMap) => {
|
||||
const component = routesMap[page];
|
||||
|
@ -53,6 +54,7 @@ const Router = props => {
|
|||
wallet: <WalletPage params={params} />,
|
||||
subscriptions: <SubscriptionsPage params={params} />,
|
||||
search: <SearchPage {...params} />,
|
||||
user_history: <UserHistoryPage {...params} />,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectNavLinks } from 'lbry-redux';
|
||||
import { selectNavLinks } from 'redux/selectors/app';
|
||||
import { selectNotifications } from 'redux/selectors/subscriptions';
|
||||
import SideBar from './view';
|
||||
|
||||
|
|
27
src/renderer/component/userHistory/index.js
Normal file
27
src/renderer/component/userHistory/index.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectHistoryPageCount, makeSelectHistoryForPage } from 'redux/selectors/content';
|
||||
import { doNavigate } from 'redux/actions/navigation';
|
||||
import { selectCurrentParams, makeSelectCurrentParam } from 'lbry-redux';
|
||||
import { doClearContentHistoryUri } from 'redux/actions/content';
|
||||
import UserHistory from './view';
|
||||
|
||||
const select = state => {
|
||||
const paramPage = Number(makeSelectCurrentParam('page')(state) || 0);
|
||||
return {
|
||||
pageCount: selectHistoryPageCount(state),
|
||||
page: paramPage,
|
||||
params: selectCurrentParams(state),
|
||||
history: makeSelectHistoryForPage(paramPage)(state),
|
||||
};
|
||||
};
|
||||
|
||||
const perform = dispatch => ({
|
||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||
clearHistoryUri: uri => dispatch(doClearContentHistoryUri(uri)),
|
||||
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(UserHistory);
|
167
src/renderer/component/userHistory/view.jsx
Normal file
167
src/renderer/component/userHistory/view.jsx
Normal file
|
@ -0,0 +1,167 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import Button from 'component/button';
|
||||
import { FormField, FormRow } from 'component/common/form';
|
||||
import ReactPaginate from 'react-paginate';
|
||||
import UserHistoryItem from 'component/userHistoryItem';
|
||||
|
||||
type HistoryItem = {
|
||||
uri: string,
|
||||
lastViewed: number,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
history: Array<HistoryItem>,
|
||||
page: number,
|
||||
pageCount: number,
|
||||
navigate: (string, {}) => void,
|
||||
clearHistoryUri: string => void,
|
||||
params: { page: number },
|
||||
};
|
||||
|
||||
type State = {
|
||||
itemsSelected: {},
|
||||
};
|
||||
|
||||
class UserHistoryPage extends React.PureComponent<Props, State> {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
itemsSelected: {},
|
||||
};
|
||||
|
||||
(this: any).selectAll = this.selectAll.bind(this);
|
||||
(this: any).unselectAll = this.unselectAll.bind(this);
|
||||
(this: any).removeSelected = this.removeSelected.bind(this);
|
||||
}
|
||||
|
||||
onSelect(uri: string) {
|
||||
const { itemsSelected } = this.state;
|
||||
|
||||
const newItemsSelected = { ...itemsSelected };
|
||||
if (itemsSelected[uri]) {
|
||||
delete newItemsSelected[uri];
|
||||
} else {
|
||||
newItemsSelected[uri] = true;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
itemsSelected: { ...newItemsSelected },
|
||||
});
|
||||
}
|
||||
|
||||
changePage(pageNumber: number) {
|
||||
const { params } = this.props;
|
||||
const newParams = { ...params, page: pageNumber };
|
||||
this.props.navigate('/user_history', newParams);
|
||||
}
|
||||
|
||||
paginate(e: SyntheticKeyboardEvent<*>) {
|
||||
const pageFromInput = Number(e.currentTarget.value);
|
||||
if (
|
||||
pageFromInput &&
|
||||
e.keyCode === 13 &&
|
||||
!Number.isNaN(pageFromInput) &&
|
||||
pageFromInput > 0 &&
|
||||
pageFromInput <= this.props.pageCount
|
||||
) {
|
||||
this.changePage(pageFromInput);
|
||||
}
|
||||
}
|
||||
|
||||
selectAll() {
|
||||
const { history } = this.props;
|
||||
const newSelectedState = {};
|
||||
history.forEach(({ uri }) => (newSelectedState[uri] = true));
|
||||
this.setState({ itemsSelected: newSelectedState });
|
||||
}
|
||||
|
||||
unselectAll() {
|
||||
this.setState({
|
||||
itemsSelected: {},
|
||||
});
|
||||
}
|
||||
|
||||
removeSelected() {
|
||||
const { clearHistoryUri } = this.props;
|
||||
const { itemsSelected } = this.state;
|
||||
|
||||
Object.keys(itemsSelected).forEach(uri => clearHistoryUri(uri));
|
||||
this.setState({
|
||||
itemsSelected: {},
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { history, page, pageCount } = this.props;
|
||||
const { itemsSelected } = this.state;
|
||||
|
||||
const allSelected = Object.keys(itemsSelected).length === history.length;
|
||||
const selectHandler = allSelected ? this.unselectAll : this.selectAll;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="card__actions card__actions--between">
|
||||
{Object.keys(itemsSelected).length ? (
|
||||
<Button button="link" label={__('Delete')} onClick={this.removeSelected} />
|
||||
) : (
|
||||
<span>
|
||||
{/* Using an empty span so spacing stays the same if the button isn't rendered */}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<Button
|
||||
button="link"
|
||||
label={allSelected ? __('Cancel') : __('Select All')}
|
||||
onClick={selectHandler}
|
||||
/>
|
||||
</div>
|
||||
{!!history.length && (
|
||||
<table className="card--section table table--stretch table--history">
|
||||
<tbody>
|
||||
{history.map(item => (
|
||||
<UserHistoryItem
|
||||
key={item.uri}
|
||||
uri={item.uri}
|
||||
lastViewed={item.lastViewed}
|
||||
selected={!!itemsSelected[item.uri]}
|
||||
onSelect={() => {
|
||||
this.onSelect(item.uri);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
{pageCount > 1 && (
|
||||
<FormRow padded verticallyCentered centered>
|
||||
<ReactPaginate
|
||||
pageCount={pageCount}
|
||||
pageRangeDisplayed={2}
|
||||
previousLabel="‹"
|
||||
nextLabel="›"
|
||||
activeClassName="pagination__item--selected"
|
||||
pageClassName="pagination__item"
|
||||
previousClassName="pagination__item pagination__item--previous"
|
||||
nextClassName="pagination__item pagination__item--next"
|
||||
breakClassName="pagination__item pagination__item--break"
|
||||
marginPagesDisplayed={2}
|
||||
onPageChange={e => this.changePage(e.selected)}
|
||||
forcePage={page}
|
||||
initialPage={page}
|
||||
containerClassName="pagination"
|
||||
/>
|
||||
|
||||
<FormField
|
||||
className="paginate-channel"
|
||||
onKeyUp={e => this.paginate(e)}
|
||||
prefix={__('Go to page:')}
|
||||
type="text"
|
||||
/>
|
||||
</FormRow>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default UserHistoryPage;
|
16
src/renderer/component/userHistoryItem/index.js
Normal file
16
src/renderer/component/userHistoryItem/index.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doResolveUri, makeSelectClaimForUri } from 'lbry-redux';
|
||||
import UserHistoryItem from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(UserHistoryItem);
|
64
src/renderer/component/userHistoryItem/view.jsx
Normal file
64
src/renderer/component/userHistoryItem/view.jsx
Normal file
|
@ -0,0 +1,64 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import type { Claim } from 'types/claim';
|
||||
import moment from 'moment';
|
||||
import classnames from 'classnames';
|
||||
import Button from 'component/button';
|
||||
|
||||
type Props = {
|
||||
lastViewed: number,
|
||||
uri: string,
|
||||
claim: ?Claim,
|
||||
selected: boolean,
|
||||
onSelect: () => void,
|
||||
resolveUri: string => void,
|
||||
};
|
||||
|
||||
class UserHistoryItem extends React.PureComponent<Props> {
|
||||
componentDidMount() {
|
||||
const { claim, uri, resolveUri } = this.props;
|
||||
|
||||
if (!claim) {
|
||||
resolveUri(uri);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { lastViewed, selected, onSelect, claim } = this.props;
|
||||
|
||||
let name;
|
||||
let title;
|
||||
let uri;
|
||||
if (claim && claim.value && claim.value.stream) {
|
||||
({ name } = claim);
|
||||
({ title } = claim.value.stream.metadata);
|
||||
uri = claim.permanent_url;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr
|
||||
onClick={onSelect}
|
||||
className={classnames({
|
||||
history__selected: selected,
|
||||
})}
|
||||
>
|
||||
<td>
|
||||
<input checked={selected} type="checkbox" onClick={onSelect} />
|
||||
</td>
|
||||
<td>{moment(lastViewed).from(moment())}</td>
|
||||
<td>{title}</td>
|
||||
<td>
|
||||
<Button
|
||||
tourniquet
|
||||
button="link"
|
||||
label={name ? `lbry://${name}` : `lbry://...`}
|
||||
navigate="/show"
|
||||
navigateParams={{ uri }}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserHistoryItem;
|
|
@ -73,7 +73,11 @@ export const CREATE_CHANNEL_COMPLETED = 'CREATE_CHANNEL_COMPLETED';
|
|||
export const PUBLISH_STARTED = 'PUBLISH_STARTED';
|
||||
export const PUBLISH_COMPLETED = 'PUBLISH_COMPLETED';
|
||||
export const PUBLISH_FAILED = 'PUBLISH_FAILED';
|
||||
export const SET_PLAYING_URI = 'PLAY_URI';
|
||||
export const SET_PLAYING_URI = 'SET_PLAYING_URI';
|
||||
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';
|
||||
|
||||
// Files
|
||||
export const FILE_LIST_STARTED = 'FILE_LIST_STARTED';
|
||||
|
@ -181,14 +185,6 @@ export const FETCH_SUBSCRIPTIONS_START = 'FETCH_SUBSCRIPTIONS_START';
|
|||
export const FETCH_SUBSCRIPTIONS_FAIL = 'FETCH_SUBSCRIPTIONS_FAIL';
|
||||
export const FETCH_SUBSCRIPTIONS_SUCCESS = 'FETCH_SUBSCRIPTIONS_SUCCESS';
|
||||
|
||||
// Video controls
|
||||
export const SET_VIDEO_PAUSE = 'SET_VIDEO_PAUSE';
|
||||
|
||||
// Media controls
|
||||
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';
|
||||
|
|
1
src/renderer/constants/content.js
Normal file
1
src/renderer/constants/content.js
Normal file
|
@ -0,0 +1 @@
|
|||
export const HISTORY_ITEMS_PER_PAGE = 50;
|
|
@ -4,6 +4,7 @@ import { doNavigate } from 'redux/actions/navigation';
|
|||
import { selectRewardContentClaimIds, selectPlayingUri } from 'redux/selectors/content';
|
||||
import { doCheckSubscription } from 'redux/actions/subscriptions';
|
||||
import { doSetClientSetting } from 'redux/actions/settings';
|
||||
import { doSetContentHistoryItem } from 'redux/actions/content';
|
||||
import {
|
||||
doFetchFileInfo,
|
||||
doFetchCostInfoForUri,
|
||||
|
@ -17,7 +18,6 @@ import {
|
|||
} from 'lbry-redux';
|
||||
import { selectShowNsfw, makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import { selectMediaPaused } from 'redux/selectors/media';
|
||||
import { doPrepareEdit } from 'redux/actions/publish';
|
||||
import FilePage from './view';
|
||||
|
||||
|
@ -31,7 +31,6 @@ const select = (state, props) => ({
|
|||
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
|
||||
subscriptions: selectSubscriptions(state),
|
||||
playingUri: selectPlayingUri(state),
|
||||
isPaused: selectMediaPaused(state),
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state),
|
||||
});
|
||||
|
@ -44,6 +43,10 @@ const perform = dispatch => ({
|
|||
openModal: (modal, props) => dispatch(doNotify(modal, props)),
|
||||
prepareEdit: (publishData, uri) => dispatch(doPrepareEdit(publishData, uri)),
|
||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||
setViewed: uri => dispatch(doSetContentHistoryItem(uri)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(FilePage);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(FilePage);
|
||||
|
|
|
@ -46,8 +46,7 @@ type Props = {
|
|||
prepareEdit: ({}, string) => void,
|
||||
checkSubscription: (uri: string) => void,
|
||||
subscriptions: Array<Subscription>,
|
||||
setClientSetting: (string, boolean | string) => void,
|
||||
autoplay: boolean,
|
||||
setViewed: string => void,
|
||||
};
|
||||
|
||||
class FilePage extends React.Component<Props> {
|
||||
|
@ -71,7 +70,7 @@ class FilePage extends React.Component<Props> {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { uri, fileInfo, fetchFileInfo, fetchCostInfo } = this.props;
|
||||
const { uri, fileInfo, fetchFileInfo, fetchCostInfo, setViewed } = this.props;
|
||||
|
||||
if (fileInfo === undefined) {
|
||||
fetchFileInfo(uri);
|
||||
|
@ -81,13 +80,19 @@ class FilePage extends React.Component<Props> {
|
|||
fetchCostInfo(uri);
|
||||
|
||||
this.checkSubscription(this.props);
|
||||
|
||||
setViewed(uri);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
const { fetchFileInfo, uri } = this.props;
|
||||
const { fetchFileInfo, uri, setViewed } = this.props;
|
||||
if (nextProps.fileInfo === undefined) {
|
||||
fetchFileInfo(uri);
|
||||
}
|
||||
|
||||
if (uri !== nextProps.uri) {
|
||||
setViewed(nextProps.uri);
|
||||
}
|
||||
}
|
||||
|
||||
onAutoplayChange(event: SyntheticInputEvent<*>) {
|
||||
|
@ -129,7 +134,7 @@ class FilePage extends React.Component<Props> {
|
|||
const { title, thumbnail } = metadata;
|
||||
const { height, channel_name: channelName, value } = claim;
|
||||
const { PLAYABLE_MEDIA_TYPES, PREVIEW_MEDIA_TYPES } = FilePage;
|
||||
const isRewardContent = rewardedContentClaimIds.includes(claim.claim_id);
|
||||
const isRewardContent = (rewardedContentClaimIds || []).includes(claim.claim_id);
|
||||
const shouldObscureThumbnail = obscureNsfw && metadata.nsfw;
|
||||
const fileName = fileInfo ? fileInfo.file_name : null;
|
||||
const mediaType = getMediaType(contentType, fileName);
|
||||
|
|
4
src/renderer/page/userHistory/index.js
Normal file
4
src/renderer/page/userHistory/index.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
import { connect } from 'react-redux';
|
||||
import UserHistoryPage from './view';
|
||||
|
||||
export default connect(null, null)(UserHistoryPage);
|
15
src/renderer/page/userHistory/view.jsx
Normal file
15
src/renderer/page/userHistory/view.jsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Page from 'component/page';
|
||||
import UserHistory from 'component/userHistory';
|
||||
|
||||
class UserHistoryPage extends React.PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<Page>
|
||||
<UserHistory />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default UserHistoryPage;
|
|
@ -16,7 +16,7 @@ import Native from 'native';
|
|||
import { doFetchRewardedContent } from 'redux/actions/content';
|
||||
import { doFetchDaemonSettings } from 'redux/actions/settings';
|
||||
import { doAuthNavigate } from 'redux/actions/navigation';
|
||||
import { doPause } from 'redux/actions/media';
|
||||
import { doAuthenticate } from 'redux/actions/user';
|
||||
import { doCheckSubscriptionsInit } from 'redux/actions/subscriptions';
|
||||
import {
|
||||
selectIsUpgradeSkipped,
|
||||
|
@ -28,7 +28,6 @@ import {
|
|||
selectRemoteVersion,
|
||||
selectUpgradeTimer,
|
||||
} from 'redux/selectors/app';
|
||||
import { doAuthenticate } from 'redux/actions/user';
|
||||
import { lbrySettings as config } from 'package.json';
|
||||
|
||||
const { autoUpdater } = remote.require('electron-updater');
|
||||
|
@ -109,9 +108,6 @@ export function doDownloadUpgradeRequested() {
|
|||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
||||
// Pause video if needed
|
||||
dispatch(doPause());
|
||||
|
||||
const autoUpdateDeclined = selectAutoUpdateDeclined(state);
|
||||
|
||||
if (['win32', 'darwin'].includes(process.platform)) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// @flow
|
||||
import * as NOTIFICATION_TYPES from 'constants/notification_types';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import Lbryio from 'lbryio';
|
||||
|
@ -494,3 +495,45 @@ export function doPublish(params) {
|
|||
Lbry.publishDeprecated(params, null, success, failure);
|
||||
});
|
||||
}
|
||||
|
||||
export function savePosition(claimId: string, outpoint: string, position: number) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.SET_CONTENT_POSITION,
|
||||
data: { claimId, outpoint, position },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doSetContentHistoryItem(uri: string) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.SET_CONTENT_LAST_VIEWED,
|
||||
data: { uri, lastViewed: Date.now() },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doClearContentHistoryUri(uri: string) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.CLEAR_CONTENT_HISTORY_URI,
|
||||
data: { uri },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doClearContentHistoryAll() {
|
||||
return dispatch => {
|
||||
dispatch({ type: ACTIONS.CLEAR_CONTENT_HISTORY_ALL });
|
||||
};
|
||||
}
|
||||
|
||||
export function doSetHistoryPage(page) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.SET_CONTENT_HISTORY_PAGE,
|
||||
data: { page },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
// @flow
|
||||
import * as actions from 'constants/action_types';
|
||||
import type { Dispatch } from 'redux/reducers/media';
|
||||
|
||||
export const doPlay = () => (dispatch: Dispatch) =>
|
||||
dispatch({
|
||||
type: actions.MEDIA_PLAY,
|
||||
});
|
||||
|
||||
export const doPause = () => (dispatch: Dispatch) =>
|
||||
dispatch({
|
||||
type: actions.MEDIA_PAUSE,
|
||||
});
|
||||
|
||||
export function savePosition(claimId: String, position: Number) {
|
||||
return function(dispatch: Dispatch, getState: Function) {
|
||||
const state = getState();
|
||||
const claim = state.claims.byId[claimId];
|
||||
const outpoint = `${claim.txid}:${claim.nout}`;
|
||||
dispatch({
|
||||
type: actions.MEDIA_POSITION,
|
||||
data: {
|
||||
outpoint,
|
||||
position,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
|
@ -3,9 +3,10 @@ import * as ACTIONS from 'constants/action_types';
|
|||
const reducers = {};
|
||||
const defaultState = {
|
||||
playingUri: null,
|
||||
currentlyIsPlaying: false,
|
||||
rewardedContentClaimIds: [],
|
||||
channelClaimCounts: {},
|
||||
positions: {},
|
||||
history: [],
|
||||
};
|
||||
|
||||
reducers[ACTIONS.FETCH_FEATURED_CONTENT_STARTED] = state =>
|
||||
|
@ -80,6 +81,46 @@ reducers[ACTIONS.FETCH_CHANNEL_CLAIM_COUNT_COMPLETED] = (state, action) => {
|
|||
});
|
||||
};
|
||||
|
||||
reducers[ACTIONS.SET_CONTENT_POSITION] = (state, action) => {
|
||||
const { claimId, outpoint, position } = action.data;
|
||||
return {
|
||||
...state,
|
||||
positions: {
|
||||
...state.positions,
|
||||
[claimId]: {
|
||||
...state.positions[claimId],
|
||||
[outpoint]: position,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
reducers[ACTIONS.SET_CONTENT_LAST_VIEWED] = (state, action) => {
|
||||
const { uri, lastViewed } = action.data;
|
||||
const { history } = state;
|
||||
const historyObj = { uri, lastViewed };
|
||||
const index = history.findIndex(i => i.uri === uri);
|
||||
const newHistory =
|
||||
index === -1
|
||||
? [historyObj].concat(history)
|
||||
: [historyObj].concat(history.slice(0, index), history.slice(index + 1));
|
||||
return { ...state, history: [...newHistory] };
|
||||
};
|
||||
|
||||
reducers[ACTIONS.CLEAR_CONTENT_HISTORY_URI] = (state, action) => {
|
||||
const { uri } = action.data;
|
||||
const { history } = state;
|
||||
const index = history.findIndex(i => i.uri === uri);
|
||||
return index === -1
|
||||
? state
|
||||
: {
|
||||
...state,
|
||||
history: history.slice(0, index).concat(history.slice(index + 1)),
|
||||
};
|
||||
};
|
||||
|
||||
reducers[ACTIONS.CLEAR_CONTENT_HISTORY_ALL] = state => ({ ...state, history: [] });
|
||||
|
||||
export default function reducer(state = defaultState, action) {
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
// @flow
|
||||
import * as actions from 'constants/action_types';
|
||||
import { handleActions } from 'util/redux-utils';
|
||||
|
||||
export type MediaState = {
|
||||
paused: Boolean,
|
||||
positions: {
|
||||
[string]: number,
|
||||
},
|
||||
};
|
||||
|
||||
export type Action = any;
|
||||
export type Dispatch = (action: Action) => any;
|
||||
|
||||
const defaultState = { paused: true, positions: {} };
|
||||
|
||||
export default handleActions(
|
||||
{
|
||||
[actions.MEDIA_PLAY]: (state: MediaState, action: Action) => ({
|
||||
...state,
|
||||
paused: false,
|
||||
}),
|
||||
|
||||
[actions.MEDIA_PAUSE]: (state: MediaState, action: Action) => ({
|
||||
...state,
|
||||
paused: true,
|
||||
}),
|
||||
|
||||
[actions.MEDIA_POSITION]: (state: MediaState, action: Action) => {
|
||||
const { outpoint, position } = action.data;
|
||||
return {
|
||||
...state,
|
||||
positions: {
|
||||
...state.positions,
|
||||
[outpoint]: position,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
|
@ -3,7 +3,7 @@ import * as ACTIONS from 'constants/action_types';
|
|||
const getCurrentPath = () => {
|
||||
const { hash } = document.location;
|
||||
if (hash !== '') return hash.replace(/^#/, '');
|
||||
return '/discover';
|
||||
return '/user_history';
|
||||
};
|
||||
|
||||
const reducers = {};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import { selectCurrentPage, selectHistoryStack } from 'lbry-redux';
|
||||
|
||||
export const selectState = state => state.app || {};
|
||||
|
||||
|
@ -86,3 +87,162 @@ export const selectCurrentLanguage = createSelector(
|
|||
export const selectVolume = createSelector(selectState, state => state.volume);
|
||||
|
||||
export const selectUpgradeTimer = createSelector(selectState, state => state.checkUpgradeTimer);
|
||||
|
||||
export const selectNavLinks = createSelector(
|
||||
selectCurrentPage,
|
||||
selectHistoryStack,
|
||||
(currentPage, historyStack) => {
|
||||
const isWalletPage = page =>
|
||||
page === 'wallet' ||
|
||||
page === 'send' ||
|
||||
page === 'getcredits' ||
|
||||
page === 'rewards' ||
|
||||
page === 'history' ||
|
||||
page === 'invite' ||
|
||||
page === 'backup';
|
||||
|
||||
const isMyLbryPage = page =>
|
||||
page === 'downloaded' || page === 'published' || page === 'user_history';
|
||||
|
||||
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 & Receive',
|
||||
path: '/send',
|
||||
active: currentPage === 'send',
|
||||
},
|
||||
{
|
||||
label: 'Transactions',
|
||||
path: '/history',
|
||||
active: currentPage === 'history',
|
||||
},
|
||||
{
|
||||
label: 'Get Credits',
|
||||
path: '/getcredits',
|
||||
active: currentPage === 'getcredits',
|
||||
},
|
||||
{
|
||||
label: 'Rewards',
|
||||
path: '/rewards',
|
||||
active: currentPage === 'rewards',
|
||||
},
|
||||
{
|
||||
label: 'Invites',
|
||||
path: '/invite',
|
||||
active: currentPage === 'invite',
|
||||
},
|
||||
{
|
||||
label: 'Backup',
|
||||
path: '/backup',
|
||||
active: currentPage === 'backup',
|
||||
},
|
||||
];
|
||||
|
||||
const myLbrySubLinks = [
|
||||
{
|
||||
label: 'Downloads',
|
||||
path: '/downloaded',
|
||||
active: currentPage === 'downloaded',
|
||||
},
|
||||
{
|
||||
label: 'Publishes',
|
||||
path: '/published',
|
||||
active: currentPage === 'published',
|
||||
},
|
||||
{
|
||||
label: 'History',
|
||||
path: '/user_history',
|
||||
active: currentPage === 'user_history',
|
||||
},
|
||||
];
|
||||
|
||||
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: 'Folder',
|
||||
subLinks: myLbrySubLinks,
|
||||
path: isCurrentlyMyLbryPage ? '/downloaded' : getActiveSublink('myLbry'),
|
||||
active: isMyLbryPage(currentPage),
|
||||
},
|
||||
{
|
||||
label: 'Publish',
|
||||
icon: 'UploadCloud',
|
||||
path: '/publish',
|
||||
active: currentPage === 'publish',
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
icon: 'Settings',
|
||||
path: '/settings',
|
||||
active: currentPage === 'settings',
|
||||
},
|
||||
{
|
||||
label: 'Help',
|
||||
path: '/help',
|
||||
icon: 'HelpCircle',
|
||||
active: currentPage === 'help',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return navLinks;
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import { makeSelectClaimForUri, selectClaimsByUri } from 'lbry-redux';
|
||||
import { HISTORY_ITEMS_PER_PAGE } from 'constants/content';
|
||||
|
||||
export const selectState = state => state.content || {};
|
||||
|
||||
|
@ -29,3 +31,40 @@ export const selectRewardContentClaimIds = createSelector(
|
|||
selectState,
|
||||
state => state.rewardedContentClaimIds
|
||||
);
|
||||
|
||||
export const makeSelectContentPositionForUri = uri =>
|
||||
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;
|
||||
});
|
||||
|
||||
export const selectHistoryPageCount = createSelector(selectState, state =>
|
||||
Math.ceil(state.history.length / HISTORY_ITEMS_PER_PAGE)
|
||||
);
|
||||
|
||||
export const makeSelectHistoryForPage = page =>
|
||||
createSelector(selectState, selectClaimsByUri, (state, claimsByUri) => {
|
||||
const left = page * HISTORY_ITEMS_PER_PAGE;
|
||||
const historyItems = state.history.slice(left, left + HISTORY_ITEMS_PER_PAGE);
|
||||
|
||||
// See if we have the claim info for the uris in your history
|
||||
// If not, it will need to be fetched in the component
|
||||
return historyItems.map((historyItem) => {
|
||||
const { uri, lastViewed } = historyItem;
|
||||
const claimAtUri = claimsByUri[uri];
|
||||
|
||||
if (claimAtUri) {
|
||||
return { lastViewed, uri, ...claimAtUri }
|
||||
} else {
|
||||
console.log("jsut returning item")
|
||||
return historyItem;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
export const makeSelectHistoryForUri = uri =>
|
||||
createSelector(selectState, state => state.history.find(i => i.uri === uri));
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||
|
||||
const selectState = state => state.media || {};
|
||||
|
||||
export const selectMediaPaused = createSelector(selectState, state => state.paused);
|
||||
|
||||
export const makeSelectMediaPositionForUri = uri =>
|
||||
createSelector(selectState, makeSelectClaimForUri(uri), (state, claim) => {
|
||||
const outpoint = `${claim.txid}:${claim.nout}`;
|
||||
return state.positions[outpoint] || null;
|
||||
});
|
|
@ -19,6 +19,10 @@
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
&.form-row--centered {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.form-row--stretch {
|
||||
flex: 1;
|
||||
}
|
||||
|
|
|
@ -54,10 +54,10 @@ table.table,
|
|||
tr {
|
||||
border-bottom: var(--table-item-border);
|
||||
padding: 8px 0;
|
||||
&:nth-child(even):not(.odd) {
|
||||
&:nth-child(even) {
|
||||
background-color: var(--table-item-odd);
|
||||
}
|
||||
&:nth-child(odd):not(.even) {
|
||||
&:nth-child(odd) {
|
||||
background-color: var(--table-item-even);
|
||||
}
|
||||
&.thead {
|
||||
|
@ -107,3 +107,48 @@ table.table--transactions {
|
|||
width: 15%;
|
||||
}
|
||||
}
|
||||
|
||||
table.table--history {
|
||||
margin-top: $spacing-vertical * 1/3;
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
&:nth-child(even),
|
||||
&:nth-child(odd) {
|
||||
background-color: var(--table-item-even);
|
||||
|
||||
&.history__selected {
|
||||
color: red;
|
||||
background-color: var(--table-item-odd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
cursor: default;
|
||||
padding: $spacing-vertical * 1/3 0;
|
||||
}
|
||||
|
||||
td:nth-of-type(1) {
|
||||
width: 7.5%;
|
||||
}
|
||||
td:nth-of-type(2) {
|
||||
width: 17.5%;
|
||||
}
|
||||
td:nth-of-type(3) {
|
||||
width: 40%;
|
||||
max-width: 30vw;
|
||||
padding-right: $spacing-vertical * 2/3;
|
||||
}
|
||||
td:nth-of-type(4) {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
td:nth-of-type(3),
|
||||
td:nth-of-type(4) {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import settingsReducer from 'redux/reducers/settings';
|
|||
import userReducer from 'redux/reducers/user';
|
||||
import shapeShiftReducer from 'redux/reducers/shape_shift';
|
||||
import subscriptionsReducer from 'redux/reducers/subscriptions';
|
||||
import mediaReducer from 'redux/reducers/media';
|
||||
import publishReducer from 'redux/reducers/publish';
|
||||
import { persistStore, autoRehydrate } from 'redux-persist';
|
||||
import createCompressor from 'redux-persist-transform-compress';
|
||||
|
@ -69,7 +68,6 @@ const reducers = combineReducers({
|
|||
user: userReducer,
|
||||
shapeShift: shapeShiftReducer,
|
||||
subscriptions: subscriptionsReducer,
|
||||
media: mediaReducer,
|
||||
publish: publishReducer,
|
||||
notifications: notificationsReducer,
|
||||
blacklist: blacklistReducer,
|
||||
|
@ -102,15 +100,16 @@ const store = createStore(
|
|||
const compressor = createCompressor();
|
||||
const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']);
|
||||
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
||||
const contentFilter = createFilter('content', ['positions', 'history']);
|
||||
|
||||
// We only need to persist the receiveAddress for the wallet
|
||||
const walletFilter = createFilter('wallet', ['receiveAddress']);
|
||||
|
||||
const persistOptions = {
|
||||
whitelist: ['claims', 'subscriptions', 'publish', 'wallet'],
|
||||
whitelist: ['claims', 'subscriptions', 'publish', 'wallet', 'content'],
|
||||
// Order is important. Needs to be compressed last or other transforms can't
|
||||
// read the data
|
||||
transforms: [saveClaimsFilter, subscriptionsFilter, walletFilter, compressor],
|
||||
transforms: [saveClaimsFilter, subscriptionsFilter, walletFilter, contentFilter, compressor],
|
||||
debounce: 10000,
|
||||
storage: localForage,
|
||||
};
|
||||
|
|
|
@ -5655,9 +5655,9 @@ lazy-val@^1.0.3:
|
|||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.3.tgz#bb97b200ef00801d94c317e29dc6ed39e31c5edc"
|
||||
|
||||
lbry-redux@lbryio/lbry-redux#421321a78397251589e5a890f4caa95e79975e2b:
|
||||
lbry-redux@lbryio/lbry-redux#d1cee82af119c0c5f98ec27f94b2e7f61e34b54c:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/421321a78397251589e5a890f4caa95e79975e2b"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/d1cee82af119c0c5f98ec27f94b2e7f61e34b54c"
|
||||
dependencies:
|
||||
proxy-polyfill "0.1.6"
|
||||
reselect "^3.0.0"
|
||||
|
|
Loading…
Reference in a new issue