User history #1846

Merged
daovist merged 17 commits from user-history into master 2018-09-07 07:21:38 +02:00
13 changed files with 295 additions and 126 deletions
Showing only changes of commit d01b893a68 - Show all commits

View file

@ -76,8 +76,8 @@ export class FormField extends React.PureComponent<Props> {
);
} else if (type === 'textarea') {
input = <textarea type={type} id={name} {...inputProps} />;
} else if (type === 'checkbox') {
input = <Toggle id={name} {...inputProps} />;
// } else if (type === 'checkbox') {
// input = <Toggle id={name} {...inputProps} />;
} else {
input = <input type={type} id={name} {...inputProps} />;
}

View file

@ -12,7 +12,6 @@ import {
selectRewardContentClaimIds,
makeSelectContentPositionForUri,
} from 'redux/selectors/content';
import { doClearContentHistoryUri } from 'redux/actions/content';
import { selectShowNsfw } from 'redux/selectors/settings';
import { selectPendingPublish } from 'redux/selectors/publish';
import FileCard from './view';

View file

@ -64,7 +64,6 @@ class FileCard extends React.PureComponent<Props> {
claimIsMine,
pending,
position,
lastViewed,
clearHistoryUri,
showPrice,
} = this.props;
@ -111,20 +110,6 @@ class FileCard extends React.PureComponent<Props> {
{fileInfo && <Icon icon={icons.LOCAL} />}
{position && <Icon icon={icons.REFRESH} />}
</div>
{lastViewed && (
<div className="card__subtitle">
{moment(lastViewed).from(moment())}
{position && <Icon icon={icons.REFRESH} />}
<span
onClick={e => {
e.stopPropagation();
clearHistoryUri(uri);
}}
>
<Icon icon={icons.CLOSE} iconColor="red" />
</span>
</div>
)}
</div>
</section>
);

View 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);

View file

@ -0,0 +1,149 @@
// @flow
import React from 'react';
import FileCard from 'component/fileCard';
import Page from 'component/page';
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,
params: { page: number },
};
class UserHistoryPage extends React.PureComponent<Props> {
constructor() {
super();
this.state = {
itemsSelected: {},
}
this.selectAll = this.selectAll.bind(this);
this.unselectAll = this.unselectAll.bind(this);
this.removeSelected = this.removeSelected.bind(this);
}
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);
}
}
onSelect(uri: string) {
console.log("onSelect", uri)
this.setState({
itemsSelected: { ...this.state.itemsSelected, [uri]: !this.state.itemsSelected[uri] }
})
}
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));
}
render() {
const { history, page, pageCount, navigate } = 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>
<Button
button="link"
label={allSelected ? __("Cancel") : __("Select All")}
onClick={selectHandler} />
{!!Object.keys(itemsSelected).length && (
<Button
button="link"
label={__("Delete")}
onClick={this.removeSelected} />
)}
</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 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;

View file

@ -0,0 +1,9 @@
import { connect } from 'react-redux';
import { doResolveUri } from 'lbry-redux';
import UserHistoryItem from './view';
const perform = (dispatch) => ({
resolveUri: uri => dispatch(doResolveUri(uri)),
})
export default connect(null, perform)(UserHistoryItem);

View file

@ -0,0 +1,53 @@
// @flow
import React from 'react';
import type { Claim } from 'types/claim';
import moment from 'moment';
import classnames from 'classnames';
import { FormRow, FormField } from 'component/common/form';
type Props = {
lastViewed: number,
uri: string,
claim: ?{}
};
class UserHistoryItem extends React.PureComponent<Props> {
componentDidMount() {
const { claim, uri, resolveUri } = this.props;
if (!claim) {
console.log("fetch claim")
resolveUri(uri)
}
}
render() {
const { lastViewed, selected, onSelect, claim } = this.props;
let name;
if (claim) {
({ name } = claim);
}
return (
<tr className={classnames({
"history__selected": selected
})}>
<td>
<FormField
checked={selected}
type="checkbox"
onClick={onSelect}
/>
</td>
<td>
{moment(lastViewed).from(moment())}
</td>
<td>
{name}
</td>
</tr>
);
}
}
export default UserHistoryItem;

View file

@ -89,6 +89,10 @@ class FilePage extends React.Component<Props> {
if (nextProps.fileInfo === undefined) {
fetchFileInfo(uri);
}
if (uri !== nextProps.uri) {
setViewed(nextProps.uri);
}
}
onAutoplayChange(event: SyntheticInputEvent<*>) {

View file

@ -1,24 +1,4 @@
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 UserHistoryPage 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)),
});
export default connect(
select,
perform
)(UserHistoryPage);
export default connect(null, null)(UserHistoryPage);

View file

@ -1,91 +1,13 @@
// @flow
import React from 'react';
import FileCard from 'component/fileCard';
import Page from 'component/page';
import Button from 'component/button';
import { FormField, FormRow } from 'component/common/form';
import ReactPaginate from 'react-paginate';
type HistoryItem = {
uri: string,
lastViewed: number,
};
type Props = {
history: Array<HistoryItem>,
page: number,
pageCount: number,
navigate: string => void,
params: { page: number },
};
class UserHistoryPage extends React.PureComponent<Props> {
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);
}
}
import UserHistory from 'component/userHistory';
class UserHistoryPage extends React.PureComponent {
render() {
const { history, page, pageCount, navigate } = this.props;
return (
<Page>
<div className="card__list">
{history && history.length ? (
<React.Fragment>
{history.map(item => (
<FileCard key={item.uri} uri={item.uri} lastViewed={item.lastViewed} />
))}
{pageCount > 1 && (
<FormRow 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>
) : (
<p className="card__subtitle">
{__('You have no saved history. Go')}{' '}
<Button button="link" label={__('explore')} onClick={() => navigate('/discover')} />{' '}
{__('the content available on LBRY!')}
</p>
)}
</div>
<UserHistory />
</Page>
);
}

View file

@ -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 = {};

View file

@ -1,5 +1,5 @@
import { createSelector } from 'reselect';
import { makeSelectClaimForUri } from 'lbry-redux';
import { makeSelectClaimForUri, selectClaimsByUri } from 'lbry-redux';
import { HISTORY_ITEMS_PER_PAGE } from 'constants/content';
export const selectState = state => state.content || {};
@ -47,9 +47,23 @@ export const selectHistoryPageCount = createSelector(selectState, state =>
);
export const makeSelectHistoryForPage = page =>
createSelector(selectState, state => {
createSelector(selectState, selectClaimsByUri, (state, claimsByUri) => {
const left = page * HISTORY_ITEMS_PER_PAGE;
return state.history.slice(left, left + 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 =>

View file

@ -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,30 @@ table.table--transactions {
width: 15%;
}
}
table.table--history {
margin-top: 0;
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:nth-of-type(2) {
/*Tourniquets text over 20VW*/
max-width: 20vw;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--color-help);
}
}
}