diff options
Diffstat (limited to 'app/javascript')
7 files changed, 84 insertions, 252 deletions
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 827b69500..f9f6736e6 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -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} diff --git a/app/javascript/mastodon/features/audio/index.js b/app/javascript/mastodon/features/audio/index.js index 99926e52a..686709ac3 100644 --- a/app/javascript/mastodon/features/audio/index.js +++ b/app/javascript/mastodon/features/audio/index.js @@ -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 (prevState.blurhash !== this.state.blurhash || prevProps.blurhash !== this.props.blurhash) { - this._setColorScheme(); - this._decodeBlurhash(); + if (prevProps.src !== this.props.src || this.state.width !== prevState.width || this.state.height !== prevState.height) { + this._clear(); + this._draw(); } - - 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> diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index f7d0c9bd4..b1ae0b2cc 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -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} /> ); diff --git a/app/javascript/mastodon/features/ui/components/audio_modal.js b/app/javascript/mastodon/features/ui/components/audio_modal.js index 2300453d7..dc033434e 100644 --- a/app/javascript/mastodon/features/ui/components/audio_modal.js +++ b/app/javascript/mastodon/features/ui/components/audio_modal.js @@ -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> diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.js b/app/javascript/mastodon/features/ui/components/focal_point_modal.js index 7d1509f71..8112e3b9e 100644 --- a/app/javascript/mastodon/features/ui/components/focal_point_modal.js +++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.js @@ -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 /> )} diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 9d89e31e8..2a6808b05 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -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", diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index d483af220..fe9f98d95 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -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__volume::before, - .video-player__seek::before { - background: rgba($black, 0.15); - } - - .video-player__seek__buffer { - background: rgba($black, 0.2); - } + .video-player__seek__buffer { + background: currentColor; + opacity: 0.2; + } - .video-player__buttons button { - color: rgba($black, 0.75); + .video-player__buttons button { + color: currentColor; + opacity: 0.75; - &:active, - &:hover, - &:focus { - color: $black; - } + &:active, + &:hover, + &:focus { + color: currentColor; + opacity: 1; } + } - .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, |