+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class ExtendedVideoPlayer extends React.PureComponent {
-
- static propTypes = {
- src: PropTypes.string.isRequired,
- alt: PropTypes.string,
- width: PropTypes.number,
- height: PropTypes.number,
- time: PropTypes.number,
- controls: PropTypes.bool.isRequired,
- muted: PropTypes.bool.isRequired,
- onClick: PropTypes.func,
- };
-
- handleLoadedData = () => {
- if (this.props.time) {
- this.video.currentTime = this.props.time;
- }
- }
-
- componentDidMount () {
- this.video.addEventListener('loadeddata', this.handleLoadedData);
- }
-
- componentWillUnmount () {
- this.video.removeEventListener('loadeddata', this.handleLoadedData);
- }
-
- setRef = (c) => {
- this.video = c;
- }
-
- handleClick = e => {
- e.stopPropagation();
- const handler = this.props.onClick;
- if (handler) handler();
- }
-
- render () {
- const { src, muted, controls, alt } = this.props;
-
- return (
- <div className='extended-video-player'>
- <video
- ref={this.setRef}
- src={src}
- autoPlay
- role='button'
- tabIndex='0'
- aria-label={alt}
- title={alt}
- muted={muted}
- controls={controls}
- loop={!controls}
- onClick={this.handleClick}
- />
- </div>
- );
- }
-
-}
--- /dev/null
+import React from 'react';
+import PropTypes from 'prop-types';
+
+export default class GIFV extends React.PureComponent {
+
+ static propTypes = {
+ src: PropTypes.string.isRequired,
+ alt: PropTypes.string,
+ width: PropTypes.number,
+ height: PropTypes.number,
+ onClick: PropTypes.func,
+ };
+
+ state = {
+ loading: true,
+ };
+
+ handleLoadedData = () => {
+ this.setState({ loading: false });
+ }
+
+ componentWillReceiveProps (nextProps) {
+ if (nextProps.src !== this.props.src) {
+ this.setState({ loading: true });
+ }
+ }
+
+ handleClick = e => {
+ const { onClick } = this.props;
+
+ if (onClick) {
+ e.stopPropagation();
+ onClick();
+ }
+ }
+
+ render () {
+ const { src, width, height, alt } = this.props;
+ const { loading } = this.state;
+
+ return (
+ <div className='gifv' style={{ position: 'relative' }}>
+ {loading && (
+ <canvas
+ width={width}
+ height={height}
+ role='button'
+ tabIndex='0'
+ aria-label={alt}
+ title={alt}
+ onClick={this.handleClick}
+ />
+ )}
+
+ <video
+ src={src}
+ width={width}
+ height={height}
+ role='button'
+ tabIndex='0'
+ aria-label={alt}
+ title={alt}
+ muted
+ loop
+ autoPlay
+ playsInline
+ onClick={this.handleClick}
+ onLoadedData={this.handleLoadedData}
+ style={{ position: loading ? 'absolute' : 'static', top: 0, left: 0 }}
+ />
+ </div>
+ );
+ }
+
+}
import CharacterCounter from 'mastodon/features/compose/components/character_counter';
import { length } from 'stringz';
import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
+import GIFV from 'mastodon/components/gifv';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
const assetHost = process.env.CDN_HOST || '';
+class ImageLoader extends React.PureComponent {
+
+ static propTypes = {
+ src: PropTypes.string.isRequired,
+ width: PropTypes.number,
+ height: PropTypes.number,
+ };
+
+ state = {
+ loading: true,
+ };
+
+ componentDidMount() {
+ const image = new Image();
+ image.addEventListener('load', () => this.setState({ loading: false }));
+ image.src = this.props.src;
+ }
+
+ render () {
+ const { loading } = this.state;
+
+ if (loading) {
+ return <canvas width={this.props.width} height={this.props.height} />;
+ } else {
+ return <img {...this.props} alt='' />;
+ }
+ }
+
+}
+
export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class FocalPointModal extends ImmutablePureComponent {
description: '',
dirty: false,
progress: 0,
+ loading: true,
};
componentWillMount () {
<div className='focal-point-modal__content'>
{focals && (
<div className={classNames('focal-point', { dragging })} ref={this.setRef} onMouseDown={this.handleMouseDown} onTouchStart={this.handleTouchStart}>
- {media.get('type') === 'image' && <img src={media.get('url')} width={width} height={height} alt='' />}
- {media.get('type') === 'gifv' && <video src={media.get('url')} width={width} height={height} loop muted autoPlay />}
+ {media.get('type') === 'image' && <ImageLoader src={media.get('url')} width={width} height={height} alt='' />}
+ {media.get('type') === 'gifv' && <GIFV src={media.get('url')} width={width} height={height} />}
<div className='focal-point__preview'>
<strong><FormattedMessage id='upload_modal.preview_label' defaultMessage='Preview ({ratio})' values={{ ratio: '16:9' }} /></strong>
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Video from 'mastodon/features/video';
-import ExtendedVideoPlayer from 'mastodon/components/extended_video_player';
import classNames from 'classnames';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import IconButton from 'mastodon/components/icon_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImageLoader from './image_loader';
import Icon from 'mastodon/components/icon';
+import GIFV from 'mastodon/components/gifv';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
);
} else if (image.get('type') === 'gifv') {
return (
- <ExtendedVideoPlayer
+ <GIFV
src={image.get('url')}
- muted
- controls={false}
width={width}
height={height}
key={image.get('preview_url')}
background: $base-shadow-color;
img,
- video {
+ video,
+ canvas {
display: block;
max-height: 80vh;
width: 100%;