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 {
/>
-
-
-
-
-
+
+
+
@@ -453,7 +460,7 @@ class MovieDetails extends Component {
}
{
- !!studio &&
+ !!studio && !isSmallScreen &&