mirror of
https://github.com/Radarr/Radarr.git
synced 2024-09-17 15:02:34 +02:00
parent
b7cf6f49d0
commit
f073f0c35c
179
frontend/src/Components/Marquee.js
Normal file
179
frontend/src/Components/Marquee.js
Normal file
@ -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 (
|
||||||
|
<div
|
||||||
|
ref={(el) => {
|
||||||
|
this.container = el;
|
||||||
|
}}
|
||||||
|
className={`ui-marquee ${this.props.className}`}
|
||||||
|
style={{ overflow: 'hidden' }}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
ref={(el) => {
|
||||||
|
this.text = el;
|
||||||
|
}}
|
||||||
|
style={style}
|
||||||
|
title={this.props.text}
|
||||||
|
>
|
||||||
|
{this.props.text}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={(el) => {
|
||||||
|
this.container = el;
|
||||||
|
}}
|
||||||
|
className={`ui-marquee ${this.props.className}`.trim()}
|
||||||
|
style={{ overflow: 'hidden' }}
|
||||||
|
onMouseEnter={this.onHandleMouseEnter}
|
||||||
|
onMouseLeave={this.onHandleMouseLeave}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
ref={(el) => {
|
||||||
|
this.text = el;
|
||||||
|
}}
|
||||||
|
style={style}
|
||||||
|
title={this.props.text}
|
||||||
|
>
|
||||||
|
{this.props.text}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Marquee;
|
@ -66,7 +66,7 @@
|
|||||||
.title {
|
.title {
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 50px;
|
font-size: 50px;
|
||||||
line-height: 50px;
|
line-height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggleMonitoredContainer {
|
.toggleMonitoredContainer {
|
||||||
@ -112,6 +112,7 @@
|
|||||||
|
|
||||||
.details {
|
.details {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
padding-left: 7px;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
@ -121,7 +122,7 @@
|
|||||||
.rating,
|
.rating,
|
||||||
.year,
|
.year,
|
||||||
.runtime {
|
.runtime {
|
||||||
margin-right: 15px;
|
margin-right: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.certification {
|
.certification {
|
||||||
@ -156,6 +157,7 @@
|
|||||||
.overview {
|
.overview {
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
padding-left: 7px;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
font-size: $intermediateFontSize;
|
font-size: $intermediateFontSize;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import HeartRating from 'Components/HeartRating';
|
|||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import InfoLabel from 'Components/InfoLabel';
|
import InfoLabel from 'Components/InfoLabel';
|
||||||
|
import Marquee from 'Components/Marquee';
|
||||||
import MovieStatusLabel from './MovieStatusLabel';
|
import MovieStatusLabel from './MovieStatusLabel';
|
||||||
import Measure from 'Components/Measure';
|
import Measure from 'Components/Measure';
|
||||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||||
@ -35,7 +36,6 @@ import MovieHistoryTable from 'Movie/History/MovieHistoryTable';
|
|||||||
import MovieTitlesTable from './Titles/MovieTitlesTable';
|
import MovieTitlesTable from './Titles/MovieTitlesTable';
|
||||||
import MovieCastPostersConnector from './Credits/Cast/MovieCastPostersConnector';
|
import MovieCastPostersConnector from './Credits/Cast/MovieCastPostersConnector';
|
||||||
import MovieCrewPostersConnector from './Credits/Crew/MovieCrewPostersConnector';
|
import MovieCrewPostersConnector from './Credits/Crew/MovieCrewPostersConnector';
|
||||||
import MovieAlternateTitles from './MovieAlternateTitles';
|
|
||||||
import MovieDetailsLinks from './MovieDetailsLinks';
|
import MovieDetailsLinks from './MovieDetailsLinks';
|
||||||
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
|
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
|
||||||
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
|
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
|
||||||
@ -78,7 +78,8 @@ class MovieDetails extends Component {
|
|||||||
allCollapsed: false,
|
allCollapsed: false,
|
||||||
expandedState: {},
|
expandedState: {},
|
||||||
selectedTabIndex: 0,
|
selectedTabIndex: 0,
|
||||||
overviewHeight: 0
|
overviewHeight: 0,
|
||||||
|
titleWidth: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,6 +152,10 @@ class MovieDetails extends Component {
|
|||||||
this.setState({ overviewHeight: height });
|
this.setState({ overviewHeight: height });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onTitleMeasure = ({ width }) => {
|
||||||
|
this.setState({ titleWidth: width });
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@ -174,7 +179,6 @@ class MovieDetails extends Component {
|
|||||||
youTubeTrailerId,
|
youTubeTrailerId,
|
||||||
inCinemas,
|
inCinemas,
|
||||||
images,
|
images,
|
||||||
alternateTitles,
|
|
||||||
tags,
|
tags,
|
||||||
isSaving,
|
isSaving,
|
||||||
isRefreshing,
|
isRefreshing,
|
||||||
@ -199,6 +203,7 @@ class MovieDetails extends Component {
|
|||||||
isDeleteMovieModalOpen,
|
isDeleteMovieModalOpen,
|
||||||
isInteractiveImportModalOpen,
|
isInteractiveImportModalOpen,
|
||||||
overviewHeight,
|
overviewHeight,
|
||||||
|
titleWidth,
|
||||||
selectedTabIndex
|
selectedTabIndex
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
@ -275,41 +280,43 @@ class MovieDetails extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={styles.info}>
|
<div className={styles.info}>
|
||||||
<div className={styles.titleRow}>
|
<Measure onMeasure={this.onTitleMeasure}>
|
||||||
<div className={styles.titleContainer}>
|
<div className={styles.titleRow}>
|
||||||
<div className={styles.toggleMonitoredContainer}>
|
<div className={styles.titleContainer}>
|
||||||
<MonitorToggleButton
|
<div className={styles.toggleMonitoredContainer}>
|
||||||
className={styles.monitorToggleButton}
|
<MonitorToggleButton
|
||||||
monitored={monitored}
|
className={styles.monitorToggleButton}
|
||||||
isSaving={isSaving}
|
monitored={monitored}
|
||||||
size={40}
|
isSaving={isSaving}
|
||||||
onPress={onMonitorTogglePress}
|
size={40}
|
||||||
|
onPress={onMonitorTogglePress}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.title} style={{ width: (titleWidth - 150) }}>
|
||||||
|
<Marquee text={title} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.movieNavigationButtons}>
|
||||||
|
<IconButton
|
||||||
|
className={styles.movieNavigationButton}
|
||||||
|
name={icons.ARROW_LEFT}
|
||||||
|
size={30}
|
||||||
|
title={`Go to ${previousMovie.title}`}
|
||||||
|
to={`/movie/${previousMovie.titleSlug}`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
className={styles.movieNavigationButton}
|
||||||
|
name={icons.ARROW_RIGHT}
|
||||||
|
size={30}
|
||||||
|
title={`Go to ${nextMovie.title}`}
|
||||||
|
to={`/movie/${nextMovie.titleSlug}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.title}>
|
|
||||||
{title}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</Measure>
|
||||||
<div className={styles.movieNavigationButtons}>
|
|
||||||
<IconButton
|
|
||||||
className={styles.movieNavigationButton}
|
|
||||||
name={icons.ARROW_LEFT}
|
|
||||||
size={30}
|
|
||||||
title={`Go to ${previousMovie.title}`}
|
|
||||||
to={`/movie/${previousMovie.titleSlug}`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<IconButton
|
|
||||||
className={styles.movieNavigationButton}
|
|
||||||
name={icons.ARROW_RIGHT}
|
|
||||||
size={30}
|
|
||||||
title={`Go to ${nextMovie.title}`}
|
|
||||||
to={`/movie/${nextMovie.titleSlug}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.details}>
|
<div className={styles.details}>
|
||||||
<div>
|
<div>
|
||||||
@ -453,7 +460,7 @@ class MovieDetails extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!studio &&
|
!!studio && !isSmallScreen &&
|
||||||
<InfoLabel
|
<InfoLabel
|
||||||
className={styles.detailsInfoLabel}
|
className={styles.detailsInfoLabel}
|
||||||
title="Studio"
|
title="Studio"
|
||||||
|
Loading…
Reference in New Issue
Block a user