Skip to content

Commit

Permalink
ListArticle: Swipe left/right behaviors
Browse files Browse the repository at this point in the history
This also fixes the layout when an article is fave'd, though there is
still a bit of layout shift when it is toggled. I think that fixing that
can wait until favicons (#232), when the fave heart icon can move to
stick on the badge.

Closes #258.
  • Loading branch information
twm committed Jun 17, 2018
1 parent 34b9967 commit 0429054
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 9 deletions.
2 changes: 1 addition & 1 deletion assets/views/FeedView.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ function renderArticleList(articleId, articleIds, articlesById, feedsById, onMar
const article = articlesById[id] || {loading: true};
// FIXME Shouldn't assume all feeds have loaded.
const feed = feedsById[article.feedId];
elements.push(<ListArticle key={id} renderLink={renderLink} feed={feed} onMarkArticlesRead={onMarkArticlesRead} {...article} />);
elements.push(<ListArticle key={id} renderLink={renderLink} feed={feed} onMarkArticlesRead={onMarkArticlesRead} onMarkArticlesFave={onMarkArticlesFave} {...article} />);
}
return elements;
}
Expand Down
94 changes: 92 additions & 2 deletions assets/widgets/ListArticle.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,83 @@ import "./ListArticle.less";
const OUTBOUND_ICON = <span className="outbound-icon"><OutboundIcon aria-hidden={true} /></span>;
const HEART_ICON = <HeartIcon />;

function cancel(event) {
event.preventDefault();
}

/**
* The row must be slid at least this many pixels to trigger an action.
*/
const MIN_SLIDE = 40;
/**
* The maximum number of pixels the row can be slid. It will stop moving after
* this point.
*/
const MAX_SLIDE = 70;

/**
* ListArticle displays article metadata in a few lines of text, along with
* some links and buttons.
*
* +----------------------------------------------------+------+------+
* | FEED TITLE - date | read | view |
* | Title of article | read | view |
* +----------------------------------------------------+------+------+
* ↑ ↑ ↑
* link: article on source site | |
* button: toggle read |
* link: view in Yarrharr
*
* The row can also be dragged right to toggle read or left to toggle
* fave.
*/
export default class ListArticle extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
dx: 0,
};
this.dragPointer = null;
this.x0 = null;
this.suppressNextClick = false;
this.onDown = event => {
// console.log('onDown', event.pointerId, event.pageX);
if (this.dragPointer == null) {
event.target.setPointerCapture(event.pointerId);
this.dragPointer = event.pointerId;
this.x0 = event.pageX;
}
};
this.onDrag = event => {
if (this.dragPointer !== event.pointerId) return;
// TODO cap dx in each direction, animate action icon and text appearing
const dx = event.pageX - this.x0;
// console.log('onMove', event.pointerId, event.pageX, dx);
this.setState({dx});
};
this.onUp = event => {
if (this.dragPointer !== event.pointerId) return;
const dx = event.pageX - this.x0;
// console.log('onUp', event.pointerId, event.pageX, dx);
if (dx < -MIN_SLIDE && dx <= -MAX_SLIDE) {
this.props.onMarkArticlesFave([this.props.id], !this.props.fave);
this.suppressNextClick = true;
} else if (dx > MAX_SLIDE && dx >= MAX_SLIDE) {
this.props.onMarkArticlesRead([this.props.id], !this.props.read);
this.suppressNextClick = true;
}
this.dragPointer = null;
this.setState({dx: 0});
}
this.onClickCapture = event => {
// Prevent buttons and links in sub-components from handling the event when dragging.
if (this.suppressNextClick) {
event.preventDefault();
event.stopPropagation();
this.suppressNextClick = false;
}
};
}
render() {
const props = this.props;
if (props.loading) {
Expand All @@ -17,10 +93,24 @@ export default class ListArticle extends React.PureComponent {
</div>
</div>
}
var {dx} = this.state;
if (dx < -MAX_SLIDE) {
dx = -MAX_SLIDE;
} else if (dx > MAX_SLIDE) {
dx = MAX_SLIDE;
}
return <div className="list-article">
<div className="list-article-inner">
<div className="list-article-slider">
<a className="outbound" href={props.url} target="_blank" title="View on source site">
<div className="list-article-slider"
onPointerDown={this.onDown}
onPointerMove={this.onDrag}
onPointerUp={this.onUp}
onPointerCancel={this.onUp}
onClickCapture={this.onClickCapture}
onDragStart={cancel}
style={{transform: `translateX(${dx}px)`}}
>
<a className="outbound" href={props.url} target="_blank" title="View on source site" onDragStart={cancel}>
<span className="meta">{props.feed.text || props.feed.title}<RelativeTime then={props.date} /></span>
<span className="title">{props.title || "Untitled"} {props.fave ? HEART_ICON : null}</span>
{OUTBOUND_ICON}
Expand Down
9 changes: 3 additions & 6 deletions assets/widgets/ListArticle.less
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,6 @@
flex-flow: row nowrap;
align-items: stretch;

.icon {
display: block;
align-self: center;
justify-self: center;
}

.outbound {
.flat-button;
&:focus { .flat-button-focus; }
Expand Down Expand Up @@ -96,6 +90,9 @@
.icon {
flex: 0 0 auto;
// background: white;
display: block;
align-self: center;
justify-self: center;
}
}

Expand Down

0 comments on commit 0429054

Please sign in to comment.