Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- `.env.production.sample`:
  Upstream changed it completely.
  Changed ours to merge upstream's new structure, but
  keeping most of the information.
This commit is contained in:
Thibaut Girka
2020-07-05 19:35:56 +02:00
32 changed files with 433 additions and 416 deletions

View File

@ -353,7 +353,9 @@ class Status extends ImmutablePureComponent {
src={attachment.get('url')}
alt={attachment.get('description')}
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
blurhash={attachment.get('blurhash')}
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
width={this.props.cachedMediaWidth}
height={110}

View File

@ -5,131 +5,12 @@ import { formatTime } from 'mastodon/features/video';
import Icon from 'mastodon/components/icon';
import classNames from 'classnames';
import { throttle } from 'lodash';
import { encode, decode } from 'blurhash';
import { getPointerPosition, fileNameFromURL } from 'mastodon/features/video';
import { debounce } from 'lodash';
const digitCharacters = [
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'#',
'$',
'%',
'*',
'+',
',',
'-',
'.',
':',
';',
'=',
'?',
'@',
'[',
']',
'^',
'_',
'{',
'|',
'}',
'~',
];
const decode83 = (str) => {
let value = 0;
let c, digit;
for (let i = 0; i < str.length; i++) {
c = str[i];
digit = digitCharacters.indexOf(c);
value = value * 83 + digit;
}
return value;
};
const decodeRGB = int => ({
r: Math.max(0, (int >> 16)),
g: Math.max(0, (int >> 8) & 255),
b: Math.max(0, (int & 255)),
});
const luma = ({ r, g, b }) => 0.2126 * r + 0.7152 * g + 0.0722 * b;
const adjustColor = ({ r, g, b }, lumaThreshold = 100) => {
let delta;
if (luma({ r, g, b }) >= lumaThreshold) {
delta = -80;
} else {
delta = 80;
}
return {
r: r + delta,
g: g + delta,
b: b + delta,
};
const hex2rgba = (hex, alpha = 1) => {
const [r, g, b] = hex.match(/\w\w/g).map(x => parseInt(x, 16));
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
};
const messages = defineMessages({
@ -157,7 +38,9 @@ class Audio extends React.PureComponent {
fullscreen: PropTypes.bool,
intl: PropTypes.object.isRequired,
cacheWidth: PropTypes.func,
blurhash: PropTypes.string,
backgroundColor: PropTypes.string,
foregroundColor: PropTypes.string,
accentColor: PropTypes.string,
};
state = {
@ -169,7 +52,6 @@ class Audio extends React.PureComponent {
muted: false,
volume: 0.5,
dragging: false,
color: { r: 255, g: 255, b: 255 },
};
setPlayerRef = c => {
@ -207,10 +89,6 @@ class Audio extends React.PureComponent {
}
}
setBlurhashCanvasRef = c => {
this.blurhashCanvas = c;
}
setCanvasRef = c => {
this.canvas = c;
@ -222,41 +100,13 @@ class Audio extends React.PureComponent {
componentDidMount () {
window.addEventListener('scroll', this.handleScroll);
window.addEventListener('resize', this.handleResize, { passive: true });
if (!this.props.blurhash) {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => this.handlePosterLoad(img);
img.src = this.props.poster;
} else {
this._setColorScheme();
this._decodeBlurhash();
}
}
componentDidUpdate (prevProps, prevState) {
if (prevProps.poster !== this.props.poster && !this.props.blurhash) {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => this.handlePosterLoad(img);
img.src = this.props.poster;
if (prevProps.src !== this.props.src || this.state.width !== prevState.width || this.state.height !== prevState.height) {
this._clear();
this._draw();
}
if (prevState.blurhash !== this.state.blurhash || prevProps.blurhash !== this.props.blurhash) {
this._setColorScheme();
this._decodeBlurhash();
}
this._clear();
this._draw();
}
_decodeBlurhash () {
const context = this.blurhashCanvas.getContext('2d');
const pixels = decode(this.props.blurhash || this.state.blurhash, 32, 32);
const outputImageData = new ImageData(pixels, 32, 32);
context.putImageData(outputImageData, 0, 0);
}
componentWillUnmount () {
@ -425,31 +275,6 @@ class Audio extends React.PureComponent {
this.analyser = analyser;
}
handlePosterLoad = image => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = image.width;
canvas.height = image.height;
context.drawImage(image, 0, 0);
const inputImageData = context.getImageData(0, 0, image.width, image.height);
const blurhash = encode(inputImageData.data, image.width, image.height, 4, 4);
this.setState({ blurhash });
}
_setColorScheme () {
const blurhash = this.props.blurhash || this.state.blurhash;
const averageColor = decodeRGB(decode83(blurhash.slice(2, 6)));
this.setState({
color: adjustColor(averageColor),
darkText: luma(averageColor) >= 165,
});
}
handleDownload = () => {
fetch(this.props.src).then(res => res.blob()).then(blob => {
const element = document.createElement('a');
@ -609,8 +434,8 @@ class Audio extends React.PureComponent {
const gradient = this.canvasContext.createLinearGradient(dx1, dy1, dx2, dy2);
const mainColor = `rgb(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b})`;
const lastColor = `rgba(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b}, 0)`;
const mainColor = this._getAccentColor();
const lastColor = hex2rgba(mainColor, 0);
gradient.addColorStop(0, mainColor);
gradient.addColorStop(0.6, mainColor);
@ -632,17 +457,25 @@ class Audio extends React.PureComponent {
return Math.floor(this._getRadius() + (PADDING * this._getScaleCoefficient()));
}
_getColor () {
return `rgb(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b})`;
_getAccentColor () {
return this.props.accentColor || '#ffffff';
}
_getBackgroundColor () {
return this.props.backgroundColor || '#000000';
}
_getForegroundColor () {
return this.props.foregroundColor || '#ffffff';
}
render () {
const { src, intl, alt, editable } = this.props;
const { paused, muted, volume, currentTime, duration, buffer, darkText, dragging } = this.state;
const { paused, muted, volume, currentTime, duration, buffer, dragging } = this.state;
const progress = (currentTime / duration) * 100;
return (
<div className={classNames('audio-player', { editable, 'with-light-background': darkText })} ref={this.setPlayerRef} style={{ width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<div className={classNames('audio-player', { editable })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<audio
src={src}
ref={this.setAudioRef}
@ -654,24 +487,15 @@ class Audio extends React.PureComponent {
/>
<canvas
className='audio-player__background'
onClick={this.togglePlay}
width='32'
height='32'
style={{ width: this.state.width, height: this.state.height, position: 'absolute', top: 0, left: 0 }}
ref={this.setBlurhashCanvasRef}
aria-label={alt}
title={alt}
role='button'
tabIndex='0'
/>
<canvas
className='audio-player__canvas'
width={this.state.width}
height={this.state.height}
style={{ width: '100%', position: 'absolute', top: 0, left: 0, pointerEvents: 'none' }}
style={{ width: '100%', position: 'absolute', top: 0, left: 0 }}
ref={this.setCanvasRef}
onClick={this.togglePlay}
title={alt}
aria-label={alt}
/>
<img
@ -684,12 +508,12 @@ class Audio extends React.PureComponent {
<div className='video-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}>
<div className='video-player__seek__buffer' style={{ width: `${buffer}%` }} />
<div className='video-player__seek__progress' style={{ width: `${progress}%`, backgroundColor: this._getColor() }} />
<div className='video-player__seek__progress' style={{ width: `${progress}%`, backgroundColor: this._getAccentColor() }} />
<span
className={classNames('video-player__seek__handle', { active: dragging })}
tabIndex='0'
style={{ left: `${progress}%`, backgroundColor: this._getColor() }}
style={{ left: `${progress}%`, backgroundColor: this._getAccentColor() }}
/>
</div>
@ -700,12 +524,12 @@ class Audio extends React.PureComponent {
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
<div className={classNames('video-player__volume', { active: this.state.hovered })} ref={this.setVolumeRef} onMouseDown={this.handleVolumeMouseDown}>
<div className='video-player__volume__current' style={{ width: `${volume * 100}%`, backgroundColor: this._getColor() }} />
<div className='video-player__volume__current' style={{ width: `${volume * 100}%`, backgroundColor: this._getAccentColor() }} />
<span
className={classNames('video-player__volume__handle')}
tabIndex='0'
style={{ left: `${volume * 100}%`, backgroundColor: this._getColor() }}
style={{ left: `${volume * 100}%`, backgroundColor: this._getAccentColor() }}
/>
</div>

View File

@ -126,7 +126,9 @@ class DetailedStatus extends ImmutablePureComponent {
alt={attachment.get('description')}
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
blurhash={attachment.get('blurhash')}
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
height={150}
/>
);

View File

@ -59,8 +59,11 @@ export default class AudioModal extends ImmutablePureComponent {
src={media.get('url')}
alt={media.get('description')}
duration={media.getIn(['meta', 'original', 'duration'], 0)}
height={135}
preload
height={150}
poster={media.get('preview_url') || status.getIn(['account', 'avatar_static'])}
backgroundColor={media.getIn(['meta', 'colors', 'background'])}
foregroundColor={media.getIn(['meta', 'colors', 'foreground'])}
accentColor={media.getIn(['meta', 'colors', 'accent'])}
/>
</div>

View File

@ -17,6 +17,7 @@ import CharacterCounter from 'mastodon/features/compose/components/character_cou
import { length } from 'stringz';
import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
import GIFV from 'mastodon/components/gifv';
import { me } from 'mastodon/initial_state';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
@ -26,6 +27,7 @@ const messages = defineMessages({
const mapStateToProps = (state, { id }) => ({
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
account: state.getIn(['accounts', me]),
});
const mapDispatchToProps = (dispatch, { id }) => ({
@ -78,6 +80,7 @@ class FocalPointModal extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
account: ImmutablePropTypes.map.isRequired,
onClose: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
@ -233,7 +236,7 @@ class FocalPointModal extends ImmutablePureComponent {
}
render () {
const { media, intl, onClose } = this.props;
const { media, intl, account, onClose } = this.props;
const { x, y, dragging, description, dirty, detecting, progress } = this.state;
const width = media.getIn(['meta', 'original', 'width']) || null;
@ -325,7 +328,10 @@ class FocalPointModal extends ImmutablePureComponent {
src={media.get('url')}
duration={media.getIn(['meta', 'original', 'duration'], 0)}
height={150}
preload
poster={media.get('preview_url') || account.get('avatar_static')}
backgroundColor={media.getIn(['meta', 'colors', 'background'])}
foregroundColor={media.getIn(['meta', 'colors', 'foreground'])}
accentColor={media.getIn(['meta', 'colors', 'accent'])}
editable
/>
)}

View File

@ -113,7 +113,7 @@
"confirmations.block.confirm": "Block",
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.delete.message": "Are you sure you want to delete this toot?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.domain_block.confirm": "Block entire domain",
@ -124,7 +124,7 @@
"confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"confirmations.redraft.confirm": "Delete & redraft",
"confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
"confirmations.redraft.message": "Are you sure you want to delete this toot and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
"confirmations.reply.confirm": "Reply",
"confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
"confirmations.unfollow.confirm": "Unfollow",
@ -137,7 +137,7 @@
"directory.local": "From {domain} only",
"directory.new_arrivals": "New arrivals",
"directory.recently_active": "Recently active",
"embed.instructions": "Embed this status on your website by copying the code below.",
"embed.instructions": "Embed this toot on your website by copying the code below.",
"embed.preview": "Here is what it will look like:",
"emoji_button.activity": "Activity",
"emoji_button.custom": "Custom",
@ -166,7 +166,7 @@
"empty_column.hashtag": "There is nothing in this hashtag yet.",
"empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
"empty_column.home.public_timeline": "the public timeline",
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
"empty_column.list": "There is nothing in this list yet. When members of this list post new toots, they will appear here.",
"empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
"empty_column.mutes": "You haven't muted any users yet.",
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
@ -223,12 +223,12 @@
"keyboard_shortcuts.back": "to navigate back",
"keyboard_shortcuts.blocked": "to open blocked users list",
"keyboard_shortcuts.boost": "to boost",
"keyboard_shortcuts.column": "to focus a status in one of the columns",
"keyboard_shortcuts.column": "to focus a toot in one of the columns",
"keyboard_shortcuts.compose": "to focus the compose textarea",
"keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.direct": "to open direct messages column",
"keyboard_shortcuts.down": "to move down in the list",
"keyboard_shortcuts.enter": "to open status",
"keyboard_shortcuts.enter": "to open toot",
"keyboard_shortcuts.favourite": "to favourite",
"keyboard_shortcuts.favourites": "to open favourites list",
"keyboard_shortcuts.federated": "to open federated timeline",
@ -269,7 +269,7 @@
"lists.subheading": "Your lists",
"load_pending": "{count, plural, one {# new item} other {# new items}}",
"loading_indicator.label": "Loading...",
"media_gallery.toggle_visible": "Hide media",
"media_gallery.toggle_visible": "Hide {number, plural, one {image} other {images}}",
"missing_indicator.label": "Not found",
"missing_indicator.sublabel": "This resource could not be found",
"mute_modal.hide_notifications": "Hide notifications from this user?",
@ -297,13 +297,13 @@
"navigation_bar.preferences": "Preferences",
"navigation_bar.public_timeline": "Federated timeline",
"navigation_bar.security": "Security",
"notification.favourite": "{name} favourited your status",
"notification.favourite": "{name} favourited your toot",
"notification.follow": "{name} followed you",
"notification.follow_request": "{name} has requested to follow you",
"notification.mention": "{name} mentioned you",
"notification.own_poll": "Your poll has ended",
"notification.poll": "A poll you have voted in has ended",
"notification.reblog": "{name} boosted your status",
"notification.reblog": "{name} boosted your toot",
"notifications.clear": "Clear notifications",
"notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
"notifications.column_settings.alert": "Desktop notifications",
@ -334,7 +334,7 @@
"poll.voted": "You voted for this answer",
"poll_button.add_poll": "Add a poll",
"poll_button.remove_poll": "Remove poll",
"privacy.change": "Adjust status privacy",
"privacy.change": "Adjust toot privacy",
"privacy.direct.long": "Visible for mentioned users only",
"privacy.direct.short": "Direct",
"privacy.private.long": "Visible for followers only",
@ -361,9 +361,9 @@
"report.target": "Reporting {target}",
"search.placeholder": "Search",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.full_text": "Simple text returns toots you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.status": "toot",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.accounts": "People",
@ -372,7 +372,7 @@
"search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"status.admin_account": "Open moderation interface for @{name}",
"status.admin_status": "Open this status in the moderation interface",
"status.admin_status": "Open this toot in the moderation interface",
"status.block": "Block @{name}",
"status.bookmark": "Bookmark",
"status.cancel_reblog_private": "Unboost",
@ -390,7 +390,7 @@
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this status",
"status.open": "Expand this toot",
"status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.read_more": "Read more",
@ -433,7 +433,7 @@
"trends.trending_now": "Trending now",
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
"upload_area.title": "Drag & drop to upload",
"upload_button.label": "Add media ({formats})",
"upload_button.label": "Add images, a video or an audio file",
"upload_error.limit": "File upload limit exceeded.",
"upload_error.poll": "File upload not allowed with polls.",
"upload_form.audio_description": "Describe for people with hearing loss",

View File

@ -5314,36 +5314,31 @@ a.status-card.compact:hover {
.video-player__volume::before,
.video-player__seek::before {
background: rgba($white, 0.15);
background: currentColor;
opacity: 0.15;
}
&.with-light-background {
color: $black;
.video-player__seek__buffer {
background: currentColor;
opacity: 0.2;
}
.video-player__volume::before,
.video-player__seek::before {
background: rgba($black, 0.15);
.video-player__buttons button {
color: currentColor;
opacity: 0.75;
&:active,
&:hover,
&:focus {
color: currentColor;
opacity: 1;
}
}
.video-player__seek__buffer {
background: rgba($black, 0.2);
}
.video-player__buttons button {
color: rgba($black, 0.75);
&:active,
&:hover,
&:focus {
color: $black;
}
}
.video-player__time-sep,
.video-player__time-total,
.video-player__time-current {
color: $black;
}
.video-player__time-sep,
.video-player__time-total,
.video-player__time-current {
color: currentColor;
}
.video-player__seek::before,