Merge commit '853a68cd5479ffc5f7a14a93a065db8f08bfe424' into develop
autoPlayGif fixes
This commit is contained in:
commit
b70ec4e0e3
18 changed files with 236 additions and 206 deletions
|
@ -2,30 +2,36 @@
|
||||||
|
|
||||||
exports[`<Avatar /> Autoplay renders an animated avatar 1`] = `
|
exports[`<Avatar /> Autoplay renders an animated avatar 1`] = `
|
||||||
<div
|
<div
|
||||||
className="account__avatar"
|
className="account__avatar still-image"
|
||||||
onMouseEnter={[Function]}
|
|
||||||
onMouseLeave={[Function]}
|
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"backgroundImage": "url(/animated/alice.gif)",
|
|
||||||
"height": "100px",
|
"height": "100px",
|
||||||
"width": "100px",
|
"width": "100px",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
onLoad={[Function]}
|
||||||
|
src="/animated/alice.gif"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<Avatar /> Still renders a still avatar 1`] = `
|
exports[`<Avatar /> Still renders a still avatar 1`] = `
|
||||||
<div
|
<div
|
||||||
className="account__avatar"
|
className="account__avatar still-image"
|
||||||
onMouseEnter={[Function]}
|
|
||||||
onMouseLeave={[Function]}
|
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"backgroundImage": "url(/static/alice.jpg)",
|
|
||||||
"height": "100px",
|
"height": "100px",
|
||||||
"width": "100px",
|
"width": "100px",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
onLoad={[Function]}
|
||||||
|
src="/animated/alice.gif"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -5,20 +5,24 @@ exports[`<AvatarOverlay renders a overlay avatar 1`] = `
|
||||||
className="account__avatar-overlay"
|
className="account__avatar-overlay"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="account__avatar-overlay-base"
|
className="account__avatar-overlay-base still-image"
|
||||||
style={
|
style={Object {}}
|
||||||
Object {
|
>
|
||||||
"backgroundImage": "url(/static/alice.jpg)",
|
<img
|
||||||
}
|
alt=""
|
||||||
}
|
onLoad={[Function]}
|
||||||
/>
|
src="/animated/alice.gif"
|
||||||
<div
|
|
||||||
className="account__avatar-overlay-overlay"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"backgroundImage": "url(/static/eve.jpg)",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="account__avatar-overlay-overlay still-image"
|
||||||
|
style={Object {}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
onLoad={[Function]}
|
||||||
|
src="/animated/eve.gif"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
|
||||||
import { fromJS } from 'immutable';
|
import { fromJS } from 'immutable';
|
||||||
import { Avatar } from '../avatar';
|
import { createComponent } from 'soapbox/test_helpers';
|
||||||
|
import Avatar from '../avatar';
|
||||||
|
|
||||||
describe('<Avatar />', () => {
|
describe('<Avatar />', () => {
|
||||||
const account = fromJS({
|
const account = fromJS({
|
||||||
|
@ -16,7 +16,7 @@ describe('<Avatar />', () => {
|
||||||
|
|
||||||
describe('Autoplay', () => {
|
describe('Autoplay', () => {
|
||||||
it('renders an animated avatar', () => {
|
it('renders an animated avatar', () => {
|
||||||
const component = renderer.create(<Avatar account={account} animate size={size} />);
|
const component = createComponent(<Avatar account={account} animate size={size} />);
|
||||||
const tree = component.toJSON();
|
const tree = component.toJSON();
|
||||||
|
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
|
@ -25,7 +25,7 @@ describe('<Avatar />', () => {
|
||||||
|
|
||||||
describe('Still', () => {
|
describe('Still', () => {
|
||||||
it('renders a still avatar', () => {
|
it('renders a still avatar', () => {
|
||||||
const component = renderer.create(<Avatar account={account} size={size} />);
|
const component = createComponent(<Avatar account={account} size={size} />);
|
||||||
const tree = component.toJSON();
|
const tree = component.toJSON();
|
||||||
|
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
|
||||||
import { fromJS } from 'immutable';
|
import { fromJS } from 'immutable';
|
||||||
import { AvatarOverlay } from '../avatar_overlay';
|
import { createComponent } from 'soapbox/test_helpers';
|
||||||
|
import AvatarOverlay from '../avatar_overlay';
|
||||||
|
|
||||||
describe('<AvatarOverlay', () => {
|
describe('<AvatarOverlay', () => {
|
||||||
const account = fromJS({
|
const account = fromJS({
|
||||||
|
@ -21,7 +21,7 @@ describe('<AvatarOverlay', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a overlay avatar', () => {
|
it('renders a overlay avatar', () => {
|
||||||
const component = renderer.create(<AvatarOverlay account={account} friend={friend} />);
|
const component = createComponent(<AvatarOverlay account={account} friend={friend} />);
|
||||||
const tree = component.toJSON();
|
const tree = component.toJSON();
|
||||||
|
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
|
|
|
@ -1,54 +1,25 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import classNames from 'classnames';
|
||||||
|
import StillImage from 'soapbox/components/still_image';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
export default class Avatar extends React.PureComponent {
|
||||||
animate: getSettings(state).get('autoPlayGif'),
|
|
||||||
});
|
|
||||||
|
|
||||||
export class Avatar extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
inline: PropTypes.bool,
|
inline: PropTypes.bool,
|
||||||
animate: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
inline: false,
|
inline: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
|
||||||
hovering: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMouseEnter = () => {
|
|
||||||
if (this.props.animate) return;
|
|
||||||
this.setState({ hovering: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseLeave = () => {
|
|
||||||
if (this.props.animate) return;
|
|
||||||
this.setState({ hovering: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { account, size, animate, inline } = this.props;
|
const { account, size, inline } = this.props;
|
||||||
if (!account) return null;
|
if (!account) return null;
|
||||||
const { hovering } = this.state;
|
|
||||||
|
|
||||||
const src = account.get('avatar');
|
|
||||||
const staticSrc = account.get('avatar_static');
|
|
||||||
|
|
||||||
let className = 'account__avatar';
|
|
||||||
|
|
||||||
if (inline) {
|
|
||||||
className = className + ' account__avatar-inline';
|
|
||||||
}
|
|
||||||
|
|
||||||
// : TODO : remove inline and change all avatars to be sized using css
|
// : TODO : remove inline and change all avatars to be sized using css
|
||||||
const style = !size ? {} : {
|
const style = !size ? {} : {
|
||||||
|
@ -56,22 +27,14 @@ export class Avatar extends React.PureComponent {
|
||||||
height: `${size}px`,
|
height: `${size}px`,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (hovering || animate) {
|
|
||||||
style.backgroundImage = `url(${src})`;
|
|
||||||
} else {
|
|
||||||
style.backgroundImage = `url(${staticSrc})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<StillImage
|
||||||
className={className}
|
className={classNames('account__avatar', { 'account__avatar-inline': inline })}
|
||||||
onMouseEnter={this.handleMouseEnter}
|
|
||||||
onMouseLeave={this.handleMouseLeave}
|
|
||||||
style={style}
|
style={style}
|
||||||
|
src={account.get('avatar')}
|
||||||
|
alt=''
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps)(Avatar);
|
|
||||||
|
|
|
@ -1,24 +1,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import StillImage from 'soapbox/components/still_image';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
export default class AvatarComposite extends React.PureComponent {
|
||||||
animate: getSettings(state).get('autoPlayGif'),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
|
||||||
class AvatarComposite extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: ImmutablePropTypes.list.isRequired,
|
accounts: ImmutablePropTypes.list.isRequired,
|
||||||
animate: PropTypes.bool,
|
|
||||||
size: PropTypes.number.isRequired,
|
size: PropTypes.number.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
renderItem(account, size, index) {
|
renderItem(account, size, index) {
|
||||||
const { animate } = this.props;
|
|
||||||
|
|
||||||
let width = 50;
|
let width = 50;
|
||||||
let height = 100;
|
let height = 100;
|
||||||
|
@ -76,12 +68,10 @@ class AvatarComposite extends React.PureComponent {
|
||||||
bottom: bottom,
|
bottom: bottom,
|
||||||
width: `${width}%`,
|
width: `${width}%`,
|
||||||
height: `${height}%`,
|
height: `${height}%`,
|
||||||
backgroundSize: 'cover',
|
|
||||||
backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={account.get('id')} style={style} />
|
<StillImage key={account.get('id')} src={account.get('avatar')} style={style} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,40 +1,23 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import StillImage from 'soapbox/components/still_image';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
export default class AvatarOverlay extends React.PureComponent {
|
||||||
animate: getSettings(state).get('autoPlayGif'),
|
|
||||||
});
|
|
||||||
|
|
||||||
export class AvatarOverlay extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: ImmutablePropTypes.map.isRequired,
|
account: ImmutablePropTypes.map.isRequired,
|
||||||
friend: ImmutablePropTypes.map.isRequired,
|
friend: ImmutablePropTypes.map.isRequired,
|
||||||
animate: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { account, friend, animate } = this.props;
|
const { account, friend } = this.props;
|
||||||
|
|
||||||
const baseStyle = {
|
|
||||||
backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const overlayStyle = {
|
|
||||||
backgroundImage: `url(${friend.get(animate ? 'avatar' : 'avatar_static')})`,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='account__avatar-overlay'>
|
<div className='account__avatar-overlay'>
|
||||||
<div className='account__avatar-overlay-base' style={baseStyle} />
|
<StillImage src={account.get('avatar')} className='account__avatar-overlay-base' />
|
||||||
<div className='account__avatar-overlay-overlay' style={overlayStyle} />
|
<StillImage src={friend.get('avatar')} className='account__avatar-overlay-overlay' />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps)(AvatarOverlay);
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { decode } from 'blurhash';
|
||||||
import { isPanoramic, isPortrait, isNonConformingRatio, minimumAspectRatio, maximumAspectRatio } from '../utils/media_aspect_ratio';
|
import { isPanoramic, isPortrait, isNonConformingRatio, minimumAspectRatio, maximumAspectRatio } from '../utils/media_aspect_ratio';
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
import { Map as ImmutableMap } from 'immutable';
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
|
import StillImage from 'soapbox/components/still_image';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
|
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
|
||||||
|
@ -60,8 +61,7 @@ class Item extends React.PureComponent {
|
||||||
|
|
||||||
hoverToPlay() {
|
hoverToPlay() {
|
||||||
const { attachment, autoPlayGif } = this.props;
|
const { attachment, autoPlayGif } = this.props;
|
||||||
return !autoPlayGif &&
|
return !autoPlayGif && attachment.get('type') === 'gifv';
|
||||||
(attachment.get('type') === 'gifv' || attachment.getIn(['pleroma', 'mime_type']) === 'image/gif');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick = (e) => {
|
handleClick = (e) => {
|
||||||
|
@ -72,7 +72,7 @@ class Item extends React.PureComponent {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
} else {
|
} else {
|
||||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||||
if (!this.canvas && this.hoverToPlay()) {
|
if (this.hoverToPlay()) {
|
||||||
e.target.pause();
|
e.target.pause();
|
||||||
e.target.currentTime = 0;
|
e.target.currentTime = 0;
|
||||||
}
|
}
|
||||||
|
@ -112,23 +112,12 @@ class Item extends React.PureComponent {
|
||||||
this.canvas = c;
|
this.canvas = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
setImageRef = i => {
|
|
||||||
this.image = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleImageLoad = () => {
|
handleImageLoad = () => {
|
||||||
this.setState({ loaded: true });
|
this.setState({ loaded: true });
|
||||||
if (this.hoverToPlay()) {
|
|
||||||
const image = this.image;
|
|
||||||
const canvas = this.canvas;
|
|
||||||
canvas.width = image.naturalWidth;
|
|
||||||
canvas.height = image.naturalHeight;
|
|
||||||
canvas.getContext('2d').drawImage(image, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { attachment, standalone, displayWidth, visible, dimensions, autoPlayGif } = this.props;
|
const { attachment, standalone, visible, dimensions, autoPlayGif } = this.props;
|
||||||
|
|
||||||
let width = 100;
|
let width = 100;
|
||||||
let height = '100%';
|
let height = '100%';
|
||||||
|
@ -162,39 +151,17 @@ class Item extends React.PureComponent {
|
||||||
);
|
);
|
||||||
} else if (attachment.get('type') === 'image') {
|
} else if (attachment.get('type') === 'image') {
|
||||||
const previewUrl = attachment.get('preview_url');
|
const previewUrl = attachment.get('preview_url');
|
||||||
const previewWidth = attachment.getIn(['meta', 'small', 'width']);
|
|
||||||
|
|
||||||
const originalUrl = attachment.get('url');
|
const originalUrl = attachment.get('url');
|
||||||
const originalWidth = attachment.getIn(['meta', 'original', 'width']);
|
|
||||||
|
|
||||||
const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';
|
|
||||||
|
|
||||||
const srcSet = hasSize ? `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w` : null;
|
|
||||||
const sizes = hasSize && (displayWidth > 0) ? `${displayWidth * (width / 100)}px` : null;
|
|
||||||
|
|
||||||
const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;
|
|
||||||
const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0;
|
|
||||||
const x = ((focusX / 2) + .5) * 100;
|
|
||||||
const y = ((focusY / -2) + .5) * 100;
|
|
||||||
|
|
||||||
thumbnail = (
|
thumbnail = (
|
||||||
<a
|
<a
|
||||||
className={classNames('media-gallery__item-thumbnail', { 'media-gallery__item-thumbnail--play-on-hover': this.hoverToPlay() })}
|
className='media-gallery__item-thumbnail'
|
||||||
href={attachment.get('remote_url') || originalUrl}
|
href={attachment.get('remote_url') || originalUrl}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
target='_blank'
|
target='_blank'
|
||||||
>
|
>
|
||||||
<img
|
<StillImage src={previewUrl} alt={attachment.get('description')} />
|
||||||
src={previewUrl}
|
|
||||||
srcSet={srcSet}
|
|
||||||
sizes={sizes}
|
|
||||||
alt={attachment.get('description')}
|
|
||||||
title={attachment.get('description')}
|
|
||||||
style={{ objectPosition: `${x}% ${y}%` }}
|
|
||||||
onLoad={this.handleImageLoad}
|
|
||||||
ref={this.setImageRef}
|
|
||||||
/>
|
|
||||||
{this.hoverToPlay() && <canvas ref={this.setCanvasRef} style={{ objectPosition: `${x}% ${y}%` }} />}
|
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
} else if (attachment.get('type') === 'gifv') {
|
} else if (attachment.get('type') === 'gifv') {
|
||||||
|
|
63
app/soapbox/components/still_image.js
Normal file
63
app/soapbox/components/still_image.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
autoPlayGif: getSettings(state).get('autoPlayGif'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps)
|
||||||
|
class StillImage extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
alt: PropTypes.string,
|
||||||
|
autoPlayGif: PropTypes.bool.isRequired,
|
||||||
|
className: PropTypes.node,
|
||||||
|
src: PropTypes.string.isRequired,
|
||||||
|
style: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
alt: '',
|
||||||
|
className: '',
|
||||||
|
style: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
hoverToPlay() {
|
||||||
|
const { autoPlayGif, src } = this.props;
|
||||||
|
return !autoPlayGif && (src.endsWith('.gif') || src.startsWith('blob:'));
|
||||||
|
}
|
||||||
|
|
||||||
|
setCanvasRef = c => {
|
||||||
|
this.canvas = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
setImageRef = i => {
|
||||||
|
this.img = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleImageLoad = () => {
|
||||||
|
if (this.hoverToPlay()) {
|
||||||
|
const img = this.img;
|
||||||
|
const canvas = this.canvas;
|
||||||
|
canvas.width = img.naturalWidth;
|
||||||
|
canvas.height = img.naturalHeight;
|
||||||
|
canvas.getContext('2d').drawImage(img, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { alt, className, src, style } = this.props;
|
||||||
|
const hoverToPlay = this.hoverToPlay();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(className, 'still-image', { 'still-image--play-on-hover': hoverToPlay })} style={style}>
|
||||||
|
<img src={src} alt={alt} ref={this.setImageRef} onLoad={this.handleImageLoad} />
|
||||||
|
{hoverToPlay && <canvas ref={this.setCanvasRef} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ import { NavLink } from 'react-router-dom';
|
||||||
import DropdownMenuContainer from 'soapbox/containers/dropdown_menu_container';
|
import DropdownMenuContainer from 'soapbox/containers/dropdown_menu_container';
|
||||||
import ProfileInfoPanel from '../../ui/components/profile_info_panel';
|
import ProfileInfoPanel from '../../ui/components/profile_info_panel';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import StillImage from 'soapbox/components/still_image';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||||
|
@ -54,7 +54,6 @@ const mapStateToProps = state => {
|
||||||
return {
|
return {
|
||||||
me,
|
me,
|
||||||
isStaff: isStaff(state.getIn(['accounts', me])),
|
isStaff: isStaff(state.getIn(['accounts', me])),
|
||||||
autoPlayGif: getSettings(state).get('autoPlayGif'),
|
|
||||||
version: parseVersion(state.getIn(['instance', 'version'])),
|
version: parseVersion(state.getIn(['instance', 'version'])),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -70,7 +69,6 @@ class Header extends ImmutablePureComponent {
|
||||||
onBlock: PropTypes.func.isRequired,
|
onBlock: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
autoPlayGif: PropTypes.bool,
|
|
||||||
isStaff: PropTypes.bool.isRequired,
|
isStaff: PropTypes.bool.isRequired,
|
||||||
version: PropTypes.object,
|
version: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
@ -226,7 +224,7 @@ class Header extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { account, intl, username, me, autoPlayGif } = this.props;
|
const { account, intl, username, me } = this.props;
|
||||||
const { isSmallScreen } = this.state;
|
const { isSmallScreen } = this.state;
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
|
@ -252,8 +250,7 @@ class Header extends ImmutablePureComponent {
|
||||||
const actionBtn = this.getActionBtn();
|
const actionBtn = this.getActionBtn();
|
||||||
const menu = this.makeMenu();
|
const menu = this.makeMenu();
|
||||||
|
|
||||||
const headerImgSrc = autoPlayGif ? account.get('header') : account.get('header_static');
|
const headerMissing = (account.get('header').indexOf('/headers/original/missing.png') > -1);
|
||||||
const headerMissing = (headerImgSrc.indexOf('/headers/original/missing.png') > -1);
|
|
||||||
|
|
||||||
const avatarSize = isSmallScreen ? 90 : 200;
|
const avatarSize = isSmallScreen ? 90 : 200;
|
||||||
|
|
||||||
|
@ -264,7 +261,7 @@ class Header extends ImmutablePureComponent {
|
||||||
{info}
|
{info}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<img src={headerImgSrc} alt='' className='parallax' />
|
<StillImage src={account.get('header')} alt='' className='parallax' />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='account__header__bar'>
|
<div className='account__header__bar'>
|
||||||
|
|
|
@ -8,6 +8,7 @@ import classNames from 'classnames';
|
||||||
import { decode } from 'blurhash';
|
import { decode } from 'blurhash';
|
||||||
import { isIOS } from 'soapbox/is_mobile';
|
import { isIOS } from 'soapbox/is_mobile';
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
|
import StillImage from 'soapbox/components/still_image';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
autoPlayGif: getSettings(state).get('autoPlayGif'),
|
autoPlayGif: getSettings(state).get('autoPlayGif'),
|
||||||
|
@ -113,12 +114,10 @@ class MediaItem extends ImmutablePureComponent {
|
||||||
const y = ((focusY / -2) + .5) * 100;
|
const y = ((focusY / -2) + .5) * 100;
|
||||||
|
|
||||||
thumbnail = (
|
thumbnail = (
|
||||||
<img
|
<StillImage
|
||||||
src={attachment.get('preview_url')}
|
src={attachment.get('preview_url')}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
title={attachment.get('description')}
|
|
||||||
style={{ objectPosition: `${x}% ${y}%` }}
|
style={{ objectPosition: `${x}% ${y}%` }}
|
||||||
onLoad={this.handleImageLoad}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (['gifv', 'video'].indexOf(attachment.get('type')) !== -1) {
|
} else if (['gifv', 'video'].indexOf(attachment.get('type')) !== -1) {
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { acctFull } from 'soapbox/utils/accounts';
|
import { acctFull } from 'soapbox/utils/accounts';
|
||||||
|
import StillImage from 'soapbox/components/still_image';
|
||||||
|
|
||||||
const ProfilePreview = ({ account }) => (
|
const ProfilePreview = ({ account }) => (
|
||||||
<div className='card h-card'>
|
<div className='card h-card'>
|
||||||
<a target='_blank' rel='noopener' href={account.get('url')}>
|
<a target='_blank' rel='noopener' href={account.get('url')}>
|
||||||
<div className='card__img'>
|
<div className='card__img'>
|
||||||
<img alt='' src={account.get('header')} />
|
<StillImage alt='' src={account.get('header')} />
|
||||||
</div>
|
</div>
|
||||||
<div className='card__bar'>
|
<div className='card__bar'>
|
||||||
<div className='avatar'>
|
<div className='avatar'>
|
||||||
<img alt='' className='u-photo' src={account.get('avatar')} width='48' height='48' />
|
<StillImage alt='' className='u-photo' src={account.get('avatar')} width='48' height='48' />
|
||||||
</div>
|
</div>
|
||||||
<div className='display-name'>
|
<div className='display-name'>
|
||||||
<span style={{ display: 'none' }}>{account.get('username')}</span>
|
<span style={{ display: 'none' }}>{account.get('username')}</span>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import Avatar from 'soapbox/components/avatar';
|
import Avatar from 'soapbox/components/avatar';
|
||||||
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
||||||
import { acctFull } from 'soapbox/utils/accounts';
|
import { acctFull } from 'soapbox/utils/accounts';
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import StillImage from 'soapbox/components/still_image';
|
||||||
|
|
||||||
class UserPanel extends ImmutablePureComponent {
|
class UserPanel extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ class UserPanel extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { account, intl, domain, autoPlayGif } = this.props;
|
const { account, intl, domain } = this.props;
|
||||||
if (!account) return null;
|
if (!account) return null;
|
||||||
const displayNameHtml = { __html: account.get('display_name_html') };
|
const displayNameHtml = { __html: account.get('display_name_html') };
|
||||||
const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
|
const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
|
||||||
|
@ -30,7 +30,7 @@ class UserPanel extends ImmutablePureComponent {
|
||||||
<div className='user-panel__container'>
|
<div className='user-panel__container'>
|
||||||
|
|
||||||
<div className='user-panel__header'>
|
<div className='user-panel__header'>
|
||||||
<img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' />
|
<StillImage src={account.get('header')} alt='' />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='user-panel__profile'>
|
<div className='user-panel__profile'>
|
||||||
|
@ -91,7 +91,6 @@ const mapStateToProps = state => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
account: getAccount(state, me),
|
account: getAccount(state, me),
|
||||||
autoPlayGif: getSettings(state).get('autoPlayGif'),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
border-radius: 4px 4px 0 0;
|
border-radius: 4px 4px 0 0;
|
||||||
|
|
||||||
img {
|
.still-image {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
height: 48px;
|
height: 48px;
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
|
|
||||||
img {
|
.still-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -58,6 +58,7 @@
|
||||||
@import 'components/navigation-bar';
|
@import 'components/navigation-bar';
|
||||||
@import 'components/promo-panel';
|
@import 'components/promo-panel';
|
||||||
@import 'components/drawer';
|
@import 'components/drawer';
|
||||||
|
@import 'components/still-image';
|
||||||
@import 'components/timeline-queue-header';
|
@import 'components/timeline-queue-header';
|
||||||
@import 'components/badge';
|
@import 'components/badge';
|
||||||
@import 'components/trends';
|
@import 'components/trends';
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
background: var(--accent-color--med);
|
background: var(--accent-color--med);
|
||||||
@media screen and (max-width: 895px) {height: 225px;}
|
@media screen and (max-width: 895px) {height: 225px;}
|
||||||
&--none {height: 125px;}
|
&--none {height: 125px;}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -29,6 +28,23 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.still-image--play-on-hover::before {
|
||||||
|
content: 'GIF';
|
||||||
|
position: absolute;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
background: var(--foreground-color);
|
||||||
|
top: 6px;
|
||||||
|
left: 6px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.9;
|
||||||
|
transition: opacity 0.1s ease;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__bar {
|
&__bar {
|
||||||
|
@ -58,6 +74,23 @@
|
||||||
background-size: 200px 200px;
|
background-size: 200px 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.still-image--play-on-hover::before {
|
||||||
|
content: 'GIF';
|
||||||
|
position: absolute;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
background: var(--foreground-color);
|
||||||
|
bottom: 15%;
|
||||||
|
left: 15%;
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.9;
|
||||||
|
transition: opacity 0.1s ease;
|
||||||
|
line-height: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 895px) {
|
@media screen and (max-width: 895px) {
|
||||||
top: -45px;
|
top: -45px;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
|
|
|
@ -28,19 +28,12 @@
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
&,
|
&,
|
||||||
img,
|
.still-image {
|
||||||
canvas {
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
img,
|
.still-image--play-on-hover::before {
|
||||||
canvas {
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--play-on-hover {
|
|
||||||
&::before {
|
|
||||||
content: 'GIF';
|
content: 'GIF';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
|
@ -56,20 +49,6 @@
|
||||||
transition: opacity 0.1s ease;
|
transition: opacity 0.1s ease;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
img,
|
|
||||||
&:hover::before {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover img {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-gallery__preview {
|
.media-gallery__preview {
|
||||||
|
@ -82,6 +61,23 @@
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
|
|
||||||
|
.still-image--play-on-hover::before {
|
||||||
|
content: 'GIF';
|
||||||
|
position: absolute;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
background: var(--foreground-color);
|
||||||
|
bottom: 6px;
|
||||||
|
left: 6px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.9;
|
||||||
|
transition: opacity 0.1s ease;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
&--hidden {
|
&--hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
28
app/styles/components/still-image.scss
Normal file
28
app/styles/components/still-image.scss
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
.still-image {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img,
|
||||||
|
canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
object-fit: cover;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--play-on-hover {
|
||||||
|
img {
|
||||||
|
position: absolute;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover img {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover canvas {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue