From f073f0c35c624179e8ac3d5f5b0d1dbeb2bfa434 Mon Sep 17 00:00:00 2001 From: Qstick Date: Fri, 3 Jul 2020 23:09:48 -0400 Subject: [PATCH] Fixed Movie Details Tweaks Fixes #4124 --- frontend/src/Components/Marquee.js | 179 ++++++++++++++++++++ frontend/src/Movie/Details/MovieDetails.css | 6 +- frontend/src/Movie/Details/MovieDetails.js | 79 +++++---- 3 files changed, 226 insertions(+), 38 deletions(-) create mode 100644 frontend/src/Components/Marquee.js diff --git a/frontend/src/Components/Marquee.js b/frontend/src/Components/Marquee.js new file mode 100644 index 000000000..25e72ba9e --- /dev/null +++ b/frontend/src/Components/Marquee.js @@ -0,0 +1,179 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +const FPS = 20; +const STEP = 1; +const TIMEOUT = 1 / FPS * 1000; + +class Marquee extends Component { + + static propTypes = { + text: PropTypes.string, + hoverToStop: PropTypes.bool, + loop: PropTypes.bool, + className: PropTypes.string + }; + + static defaultProps = { + text: '', + hoverToStop: true, + loop: false + }; + + state = { + animatedWidth: 0, + overflowWidth: 0, + direction: 0 + }; + + componentDidMount() { + this.measureText(); + + if (this.props.hoverToStop) { + this.startAnimation(); + } + } + + componentWillReceiveProps(nextProps) { + if (this.props.text.length !== nextProps.text.length) { + clearTimeout(this.marqueeTimer); + this.setState({ animatedWidth: 0, direction: 0 }); + } + } + + componentDidUpdate() { + this.measureText(); + + if (this.props.hoverToStop) { + this.startAnimation(); + } + } + + componentWillUnmount() { + clearTimeout(this.marqueeTimer); + } + + onHandleMouseEnter = () => { + if (this.props.hoverToStop) { + clearTimeout(this.marqueeTimer); + } else if (this.state.overflowWidth > 0) { + this.startAnimation(); + } + } + + onHandleMouseLeave = () => { + if (this.props.hoverToStop && this.state.overflowWidth > 0) { + this.startAnimation(); + } else { + clearTimeout(this.marqueeTimer); + this.setState({ animatedWidth: 0 }); + } + } + + startAnimation = () => { + clearTimeout(this.marqueeTimer); + const isLeading = this.state.animatedWidth === 0; + const timeout = isLeading ? 0 : TIMEOUT; + + const animate = () => { + const { overflowWidth } = this.state; + let animatedWidth = this.state.animatedWidth; + let direction = this.state.direction; + + if (direction === 0) { + animatedWidth = this.state.animatedWidth + STEP; + } else { + animatedWidth = this.state.animatedWidth - STEP; + } + + const isRoundOver = animatedWidth < 0; + const endOfText = animatedWidth > overflowWidth; + + if (endOfText) { + direction = direction === 1; + } + + if (isRoundOver) { + if (this.props.loop) { + direction = direction === 0; + } else { + return; + } + } + + this.setState({ animatedWidth, direction }); + this.marqueeTimer = setTimeout(animate, TIMEOUT); + }; + + this.marqueeTimer = setTimeout(animate, timeout); + } + + measureText = () => { + const container = this.container; + const node = this.text; + + if (container && node) { + const containerWidth = container.offsetWidth; + const textWidth = node.offsetWidth; + const overflowWidth = textWidth - containerWidth; + + if (overflowWidth !== this.state.overflowWidth) { + this.setState({ overflowWidth }); + } + } + } + + render() { + const style = { + position: 'relative', + right: this.state.animatedWidth, + whiteSpace: 'nowrap' + }; + + if (this.state.overflowWidth < 0) { + return ( +
{ + this.container = el; + }} + className={`ui-marquee ${this.props.className}`} + style={{ overflow: 'hidden' }} + > + { + this.text = el; + }} + style={style} + title={this.props.text} + > + {this.props.text} + +
+ ); + } + + return ( +
{ + this.container = el; + }} + className={`ui-marquee ${this.props.className}`.trim()} + style={{ overflow: 'hidden' }} + onMouseEnter={this.onHandleMouseEnter} + onMouseLeave={this.onHandleMouseLeave} + > + { + this.text = el; + }} + style={style} + title={this.props.text} + > + {this.props.text} + +
+ ); + } +} + +export default Marquee; diff --git a/frontend/src/Movie/Details/MovieDetails.css b/frontend/src/Movie/Details/MovieDetails.css index f6665805b..d78ba1e16 100644 --- a/frontend/src/Movie/Details/MovieDetails.css +++ b/frontend/src/Movie/Details/MovieDetails.css @@ -66,7 +66,7 @@ .title { font-weight: 300; font-size: 50px; - line-height: 50px; + line-height: 60px; } .toggleMonitoredContainer { @@ -112,6 +112,7 @@ .details { margin-bottom: 8px; + padding-left: 7px; font-weight: 300; font-size: 20px; } @@ -121,7 +122,7 @@ .rating, .year, .runtime { - margin-right: 15px; + margin-right: 14px; } .certification { @@ -156,6 +157,7 @@ .overview { flex: 1 0 auto; margin-top: 8px; + padding-left: 7px; min-height: 0; font-size: $intermediateFontSize; } diff --git a/frontend/src/Movie/Details/MovieDetails.js b/frontend/src/Movie/Details/MovieDetails.js index 39a4b6c3e..6b01d7a58 100644 --- a/frontend/src/Movie/Details/MovieDetails.js +++ b/frontend/src/Movie/Details/MovieDetails.js @@ -12,6 +12,7 @@ import HeartRating from 'Components/HeartRating'; import Icon from 'Components/Icon'; import IconButton from 'Components/Link/IconButton'; import InfoLabel from 'Components/InfoLabel'; +import Marquee from 'Components/Marquee'; import MovieStatusLabel from './MovieStatusLabel'; import Measure from 'Components/Measure'; import MonitorToggleButton from 'Components/MonitorToggleButton'; @@ -35,7 +36,6 @@ import MovieHistoryTable from 'Movie/History/MovieHistoryTable'; import MovieTitlesTable from './Titles/MovieTitlesTable'; import MovieCastPostersConnector from './Credits/Cast/MovieCastPostersConnector'; import MovieCrewPostersConnector from './Credits/Crew/MovieCrewPostersConnector'; -import MovieAlternateTitles from './MovieAlternateTitles'; import MovieDetailsLinks from './MovieDetailsLinks'; import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable'; import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector'; @@ -78,7 +78,8 @@ class MovieDetails extends Component { allCollapsed: false, expandedState: {}, selectedTabIndex: 0, - overviewHeight: 0 + overviewHeight: 0, + titleWidth: 0 }; } @@ -151,6 +152,10 @@ class MovieDetails extends Component { this.setState({ overviewHeight: height }); } + onTitleMeasure = ({ width }) => { + this.setState({ titleWidth: width }); + } + // // Render @@ -174,7 +179,6 @@ class MovieDetails extends Component { youTubeTrailerId, inCinemas, images, - alternateTitles, tags, isSaving, isRefreshing, @@ -199,6 +203,7 @@ class MovieDetails extends Component { isDeleteMovieModalOpen, isInteractiveImportModalOpen, overviewHeight, + titleWidth, selectedTabIndex } = this.state; @@ -275,41 +280,43 @@ class MovieDetails extends Component { />
-
-
-
- +
+
+
+ +
+ +
+ + + + +
+ + +
- -
- {title} -
- -
- - - -
- +
@@ -453,7 +460,7 @@ class MovieDetails extends Component { } { - !!studio && + !!studio && !isSmallScreen &&