lbry-desktop/src/renderer/page/discover/view.jsx

285 lines
8 KiB
React
Raw Normal View History

2017-06-06 23:19:12 +02:00
import React from "react";
import ReactDOM from "react-dom";
2017-06-06 23:19:12 +02:00
import lbryuri from "lbryuri";
import FileCard from "component/fileCard";
import { Icon, BusyMessage } from "component/common.js";
2017-06-06 23:19:12 +02:00
import ToolTip from "component/tooltip.js";
2017-12-08 21:14:35 +01:00
import SubHeader from "component/subHeader";
import classnames from "classnames";
import Link from "component/link";
// This should be in a separate file
export class FeaturedCategory extends React.PureComponent {
constructor() {
super();
this.state = {
numItems: undefined,
canScrollPrevious: false,
canScrollNext: false,
};
}
2016-04-10 02:00:56 +02:00
componentWillMount() {
this.setState({
numItems: this.props.names.length,
});
}
2017-12-08 21:14:35 +01:00
componentDidMount() {
const cardRow = ReactDOM.findDOMNode(this.refs.rowitems);
const cards = cardRow.getElementsByTagName("section");
// check if the last card is visible
const lastCard = cards[cards.length - 1];
const isCompletelyVisible = this.isCardVisible(lastCard, cardRow, false);
if (!isCompletelyVisible) {
this.setState({
canScrollNext: true,
});
}
}
handleScrollPrevious() {
const cardRow = ReactDOM.findDOMNode(this.refs.rowitems);
if (cardRow.scrollLeft > 0) {
// check the visible cards
const cards = cardRow.getElementsByTagName("section");
let firstVisibleCard = null;
let firstVisibleIdx = -1;
for (var i = 0; i < cards.length; i++) {
if (this.isCardVisible(cards[i], cardRow, false)) {
firstVisibleCard = cards[i];
firstVisibleIdx = i;
break;
}
}
const numDisplayed = this.numDisplayedCards(cardRow);
const scrollToIdx = firstVisibleIdx - numDisplayed;
const animationCallback = () => {
this.setState({
canScrollPrevious: cardRow.scrollLeft !== 0,
canScrollNext: true,
});
};
this.scrollCardItemsLeftAnimated(
cardRow,
scrollToIdx < 0 ? 0 : cards[scrollToIdx].offsetLeft,
100,
animationCallback
);
}
}
handleScrollNext() {
const cardRow = ReactDOM.findDOMNode(this.refs.rowitems);
// check the visible cards
const cards = cardRow.getElementsByTagName("section");
let lastVisibleCard = null;
let lastVisibleIdx = -1;
for (var i = 0; i < cards.length; i++) {
if (this.isCardVisible(cards[i], cardRow, true)) {
lastVisibleCard = cards[i];
lastVisibleIdx = i;
}
}
if (lastVisibleCard) {
const numDisplayed = this.numDisplayedCards(cardRow);
const animationCallback = () => {
// update last visible index after scroll
for (var i = 0; i < cards.length; i++) {
if (this.isCardVisible(cards[i], cardRow, true)) {
lastVisibleIdx = i;
}
}
this.setState({ canScrollPrevious: true });
if (lastVisibleIdx === cards.length - 1) {
this.setState({ canScrollNext: false });
}
};
this.scrollCardItemsLeftAnimated(
cardRow,
Math.min(
lastVisibleCard.offsetLeft,
cardRow.scrollWidth - cardRow.clientWidth
),
100,
animationCallback
);
}
}
scrollCardItemsLeftAnimated(cardRow, target, duration, callback) {
if (!duration || duration <= diff) {
cardRow.scrollLeft = target;
if (callback) {
callback();
}
return;
}
const component = this;
const diff = target - cardRow.scrollLeft;
const tick = diff / duration * 10;
setTimeout(() => {
cardRow.scrollLeft = cardRow.scrollLeft + tick;
if (cardRow.scrollLeft === target) {
if (callback) {
callback();
}
return;
}
component.scrollCardItemsLeftAnimated(
cardRow,
target,
duration - 10,
callback
);
}, 10);
}
isCardVisible(section, cardRow, partialVisibility) {
// check if a card is fully or partialy visible in its parent
const cardRowWidth = cardRow.offsetWidth;
const cardRowLeft = cardRow.scrollLeft;
const cardRowEnd = cardRowLeft + cardRow.offsetWidth;
const sectionLeft = section.offsetLeft - cardRowLeft;
const sectionEnd = sectionLeft + section.offsetWidth;
return (
(sectionLeft >= 0 && sectionEnd <= cardRowWidth) ||
(((sectionLeft < 0 && sectionEnd > 0) ||
(sectionLeft > 0 && sectionLeft <= cardRowWidth)) &&
partialVisibility)
);
}
numDisplayedCards(cardRow) {
const cards = cardRow.getElementsByTagName("section");
const cardRowWidth = cardRow.offsetWidth;
// get the width of the first card and then calculate
const cardWidth = cards.length > 0 ? cards[0].offsetWidth : 0;
if (cardWidth > 0) {
return Math.ceil(cardRowWidth / cardWidth);
}
// return a default value of 1 card displayed if the card width couldn't be determined
return 1;
}
render() {
2017-12-08 21:14:35 +01:00
const { category, names, categoryLink } = this.props;
return (
<div className="card-row card-row--small">
<h3 className="card-row__header">
2017-12-08 21:14:35 +01:00
{categoryLink ? (
<Link
className="button-text no-underline"
2017-12-08 21:14:35 +01:00
label={category}
2017-12-09 20:07:17 +01:00
navigate="/show"
navigateParams={{ uri: categoryLink }}
2017-12-08 21:14:35 +01:00
/>
) : (
category
)}
{category &&
2017-11-24 15:31:05 +01:00
category.match(/^community/i) && (
<ToolTip
label={__("What's this?")}
body={__(
'Community Content is a public space where anyone can share content with the rest of the LBRY community. Bid on the names "one," "two," "three," "four" and "five" to put your content here!'
)}
className="tooltip--header"
/>
)}
</h3>
2017-07-29 20:12:13 +02:00
<div className="card-row__scrollhouse">
2017-11-24 15:31:05 +01:00
{this.state.canScrollPrevious && (
2017-07-29 20:12:13 +02:00
<div className="card-row__nav card-row__nav--left">
<a
className="card-row__scroll-button"
onClick={this.handleScrollPrevious.bind(this)}
>
<Icon icon="icon-chevron-left" />
</a>
2017-11-24 15:31:05 +01:00
</div>
)}
{this.state.canScrollNext && (
2017-07-29 20:12:13 +02:00
<div className="card-row__nav card-row__nav--right">
<a
className="card-row__scroll-button"
onClick={this.handleScrollNext.bind(this)}
>
<Icon icon="icon-chevron-right" />
</a>
2017-11-24 15:31:05 +01:00
</div>
)}
2017-07-29 20:12:13 +02:00
<div ref="rowitems" className="card-row__items">
{names &&
2017-11-24 15:31:05 +01:00
names.map(name => (
2017-07-29 20:12:13 +02:00
<FileCard
key={name}
displayStyle="card"
uri={lbryuri.normalize(name)}
/>
2017-11-24 15:31:05 +01:00
))}
2017-07-29 20:12:13 +02:00
</div>
</div>
</div>
);
}
}
2017-06-08 06:42:19 +02:00
class DiscoverPage extends React.PureComponent {
componentWillMount() {
2017-06-06 23:19:12 +02:00
this.props.fetchFeaturedUris();
2017-05-07 09:39:13 +02:00
}
render() {
2017-06-06 23:19:12 +02:00
const { featuredUris, fetchingFeaturedUris } = this.props;
2017-07-15 21:24:57 +02:00
const hasContent =
2017-11-24 15:31:05 +01:00
typeof featuredUris === "object" && Object.keys(featuredUris).length,
2017-07-15 21:24:57 +02:00
failedToLoad = !fetchingFeaturedUris && !hasContent;
return (
2017-12-08 21:14:35 +01:00
<main
className={classnames("main main--no-margin", {
reloading: hasContent && fetchingFeaturedUris,
})}
>
<SubHeader fullWidth smallMargin />
2017-07-15 21:15:17 +02:00
{!hasContent &&
2017-11-24 15:31:05 +01:00
fetchingFeaturedUris && (
<BusyMessage message={__("Fetching content")} />
)}
2017-07-15 21:15:17 +02:00
{hasContent &&
2017-12-08 21:14:35 +01:00
Object.keys(featuredUris).map(category => {
return featuredUris[category].length ? (
<FeaturedCategory
key={category}
category={category}
names={featuredUris[category]}
/>
) : (
""
);
})}
2017-11-24 15:31:05 +01:00
{failedToLoad && (
<div className="empty">{__("Failed to load landing content.")}</div>
)}
</main>
2017-06-06 23:19:12 +02:00
);
}
2017-05-04 05:44:08 +02:00
}
2017-05-25 18:29:56 +02:00
export default DiscoverPage;