diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index fc57a5ba8..2817fb946 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -1,38 +1,206 @@ import React from "react"; +import ReactDOM from "react-dom"; import lbryio from "lbryio.js"; import lbryuri from "lbryuri"; import FileCard from "component/fileCard"; -import { BusyMessage } from "component/common.js"; +import { Icon, BusyMessage } from "component/common.js"; import ToolTip from "component/tooltip.js"; -const FeaturedCategory = props => { - const { category, names } = props; +class FeaturedCategory extends React.PureComponent { + componentWillMount() { + this.setState({ + numItems: this.props.names.length, + canScrollPrevious: false, + canScrollNext: true, + }); + } - return ( -
-

- {category} - {category && - category.match(/^community/i) && - 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() { + const { category, names } = this.props; + const leftNavClassName = + "card-row__nav left-nav" + + (this.state.canScrollPrevious ? " can-scroll" : ""); + const rightNavClassName = + "card-row__nav right-nav" + + (this.state.canScrollNext ? " can-scroll" : ""); + + return ( +
+
+ + + +
+
+ + + +
+

+ {category} + {category && + category.match(/^community/i) && + } +

+
+ {names && + names.map(name => + )} - className="tooltip--header" - />} -

- {names && - names.map(name => - - )} -
- ); -}; + + + ); + } +} class DiscoverPage extends React.PureComponent { componentWillMount() { diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index d8416d226..a81470bfb 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -1,6 +1,7 @@ @import "../global"; $padding-card-horizontal: $spacing-vertical * 2/3; +$translate-card-hover: 10px; .card { margin-left: auto; @@ -94,10 +95,13 @@ $card-link-scaling: 1.1; position: relative; z-index: 1; box-shadow: $box-shadow-focus; - transform: scale($card-link-scaling); + transform: scale($card-link-scaling) translate3d($translate-card-hover, 0, 0); transform-origin: 50% 50%; overflow-x: visible; - overflow-y: visible; + overflow-y: visible +} +.card--link:hover ~ .card--link { + transform: translate3d($translate-card-hover * 2, 0, 0); } .card__media { @@ -183,26 +187,73 @@ $height-card-small: $spacing-vertical * 15; } .card-row { + + .card-row { + margin-top: $spacing-vertical * 1/3; + } +} +.card-row__items { > .card { vertical-align: top; display: inline-block; margin-right: $spacing-vertical / 3; } - + .card-row { - margin-top: $spacing-vertical * 1/3; + > .card:last-child { + margin-right: 0 } } .card-row--small { - overflow-x: auto; - overflow-y: hidden; + overflow: hidden; white-space: nowrap; + position: relative; /*hacky way to give space for hover */ - padding-left: 20px; - margin-left: -20px; - padding-right: 20px; - margin-right: -20px; + padding-right: 30px; +} +.card-row__items { + width: 100%; + overflow: hidden; + + /*hacky way to give space for hover */ + padding-top: 20px; + margin-top: -20px; + padding-right: 30px; + margin-right: -30px; } .card-row__header { margin-bottom: $spacing-vertical / 3; -} \ No newline at end of file +} + +.card-row__nav { + display: none; + position: absolute; + padding: 0 $spacing-vertical / 1.5; + top: ($font-size * 1.4 * $font-line-height) + ($spacing-vertical / 3); + height: ($width-card-small * 9 / 16) + ($font-size * 0.82 * $font-line-height * 4) + ($spacing-vertical * 4/3) +} +.card-row__nav .card-row__scroll-button { + background: $color-bg; + color: $color-help; + box-shadow: $box-shadow-layer; + padding: $spacing-vertical $spacing-vertical / 2; + position: absolute; + cursor: pointer; + left: 0; + top: 36%; + z-index: 2; + opacity: 0.5; + transition: transform 60ms ease-in-out; + + &:hover { + opacity: 1.0; + transform: scale($card-link-scaling * 1.1) + } +} +.card-row__nav.left-nav { + left: 0; +} +.card-row__nav.right-nav { + right: 0; +} +.card-row__nav.can-scroll { + display: block +} diff --git a/ui/scss/component/_header.scss b/ui/scss/component/_header.scss index 1343ddf86..5cc51541e 100644 --- a/ui/scss/component/_header.scss +++ b/ui/scss/component/_header.scss @@ -13,7 +13,7 @@ $color-header-active: darken($color-header, 20%); top: 0; left: 0; width: 100%; - z-index: 2; + z-index: 3; padding: $spacing-vertical / 2; box-sizing: border-box; }