From f665901e3c0930fb8b3741f6bc6f6a15dd0343f6 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 6 Oct 2019 22:11:17 +0200 Subject: Fix performance of home feed regeneration (#12084) Fetching statuses from all followed accounts at once takes too long within Postgres. Fetching them one by one and merging in Ruby could be a lot less resource-intensive Because the query for dynamically fetching the home timeline is so heavy, we can no longer offer it when the home timeline is missing --- app/javascript/mastodon/actions/timelines.js | 2 +- .../mastodon/components/missing_indicator.js | 23 +++++++++++------ .../mastodon/components/regeneration_indicator.js | 18 +++++++++++++ app/javascript/mastodon/components/status_list.js | 15 ++--------- .../mastodon/features/generic_not_found/index.js | 2 +- app/javascript/styles/mastodon/components.scss | 30 ++++++++-------------- app/javascript/styles/mastodon/introduction.scss | 3 ++- 7 files changed, 49 insertions(+), 44 deletions(-) create mode 100644 app/javascript/mastodon/components/regeneration_indicator.js (limited to 'app/javascript') diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 7eeba2aa7..bc2ac5e82 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -97,7 +97,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) { api(getState).get(path, { params }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); - dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems)); + dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems)); done(); }).catch(error => { dispatch(expandTimelineFail(timelineId, error, isLoadingMore)); diff --git a/app/javascript/mastodon/components/missing_indicator.js b/app/javascript/mastodon/components/missing_indicator.js index 70d8c3b98..7b0101bab 100644 --- a/app/javascript/mastodon/components/missing_indicator.js +++ b/app/javascript/mastodon/components/missing_indicator.js @@ -1,17 +1,24 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; +import illustration from 'mastodon/../images/elephant_ui_disappointed.svg'; +import classNames from 'classnames'; -const MissingIndicator = () => ( -
-
-
+const MissingIndicator = ({ fullPage }) => ( +
+
+ +
-
- - -
+
+ +
); +MissingIndicator.propTypes = { + fullPage: PropTypes.bool, +}; + export default MissingIndicator; diff --git a/app/javascript/mastodon/components/regeneration_indicator.js b/app/javascript/mastodon/components/regeneration_indicator.js new file mode 100644 index 000000000..faf88c6b5 --- /dev/null +++ b/app/javascript/mastodon/components/regeneration_indicator.js @@ -0,0 +1,18 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import illustration from 'mastodon/../images/elephant_ui_working.svg'; + +const MissingIndicator = () => ( +
+
+ +
+ +
+ + +
+
+); + +export default MissingIndicator; diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index 745e6422d..e1b370c91 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -1,12 +1,12 @@ import { debounce } from 'lodash'; import React from 'react'; -import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import StatusContainer from '../containers/status_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; import LoadGap from './load_gap'; import ScrollableList from './scrollable_list'; +import RegenerationIndicator from 'mastodon/components/regeneration_indicator'; export default class StatusList extends ImmutablePureComponent { @@ -81,18 +81,7 @@ export default class StatusList extends ImmutablePureComponent { const { isLoading, isPartial } = other; if (isPartial) { - return ( -
-
-
- -
- - -
-
-
- ); + return ; } let scrollableContent = (isLoading || statusIds.size > 0) ? ( diff --git a/app/javascript/mastodon/features/generic_not_found/index.js b/app/javascript/mastodon/features/generic_not_found/index.js index 0290be47f..41cd61a5f 100644 --- a/app/javascript/mastodon/features/generic_not_found/index.js +++ b/app/javascript/mastodon/features/generic_not_found/index.js @@ -4,7 +4,7 @@ import MissingIndicator from '../../components/missing_indicator'; const GenericNotFound = () => ( - + ); diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index a6675b8ed..d9182ade9 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3127,37 +3127,27 @@ a.status-card.compact:hover { cursor: default; display: flex; flex: 1 1 auto; + flex-direction: column; align-items: center; justify-content: center; padding: 20px; - & > div { - width: 100%; - background: transparent; - padding-top: 0; - } - &__figure { - background: url('../images/elephant_ui_working.svg') no-repeat center 0; - width: 100%; - height: 160px; - background-size: contain; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + &, + img { + display: block; + width: auto; + height: 160px; + margin: 0; + } } - &.missing-indicator { + &--without-header { padding-top: 20px + 48px; - - .regeneration-indicator__figure { - background-image: url('../images/elephant_ui_disappointed.svg'); - } } &__label { - margin-top: 200px; + margin-top: 30px; strong { display: block; diff --git a/app/javascript/styles/mastodon/introduction.scss b/app/javascript/styles/mastodon/introduction.scss index 222d8f60e..b44ae7306 100644 --- a/app/javascript/styles/mastodon/introduction.scss +++ b/app/javascript/styles/mastodon/introduction.scss @@ -3,9 +3,10 @@ flex-direction: column; justify-content: center; align-items: center; + height: 100vh; + background: $ui-base-color; @media screen and (max-width: 920px) { - background: darken($ui-base-color, 8%); display: block !important; } -- cgit From 95f21ab87f3d459311c411cf429ac051a668218f Mon Sep 17 00:00:00 2001 From: trwnh Date: Sun, 6 Oct 2019 21:33:31 -0500 Subject: Add missing back button header for invalid account (#12094) Should fix #6786 --- app/javascript/mastodon/features/account_timeline/index.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js index 69bab1e86..8d0cbe5a1 100644 --- a/app/javascript/mastodon/features/account_timeline/index.js +++ b/app/javascript/mastodon/features/account_timeline/index.js @@ -83,6 +83,7 @@ class AccountTimeline extends ImmutablePureComponent { if (!isAccount) { return ( + ); -- cgit From 538db85d3cc6d8fcb3c0a89f7eef069a686c19f4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 9 Oct 2019 03:45:05 +0200 Subject: Remove `lang` attribute from individual statuses (#12124) Fix #10930 --- app/javascript/mastodon/components/status_content.js | 8 ++++---- app/views/statuses/_detailed_status.html.haml | 2 +- app/views/statuses/_simple_status.html.haml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index c171e7a66..4ce9ec49f 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -216,14 +216,14 @@ export default class StatusContent extends React.PureComponent { return (
{mentionsPlaceholder} -
+
{!hidden && !!status.get('poll') && }
@@ -231,7 +231,7 @@ export default class StatusContent extends React.PureComponent { } else if (this.props.onClick) { const output = [
-
+
{!!status.get('poll') && }
, @@ -245,7 +245,7 @@ export default class StatusContent extends React.PureComponent { } else { return (
-
+
{!!status.get('poll') && }
diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml index 12f03ccdd..5cee84ada 100644 --- a/app/views/statuses/_detailed_status.html.haml +++ b/app/views/statuses/_detailed_status.html.haml @@ -20,7 +20,7 @@ %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }< %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}  %button.status__content__spoiler-link= t('statuses.show_more') - .e-content{ lang: status.language, style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" } + .e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" } = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay) - if status.preloadable_poll = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml index ca3c8fdd5..a68fe1022 100644 --- a/app/views/statuses/_simple_status.html.haml +++ b/app/views/statuses/_simple_status.html.haml @@ -24,7 +24,7 @@ %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }< %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}  %button.status__content__spoiler-link= t('statuses.show_more') - .e-content{ lang: status.language, style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" } + .e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" } = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay) - if status.preloadable_poll = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do -- cgit From 6ebd74f4fa640b9616ebb2730d97722799c6ed56 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 10 Oct 2019 05:21:38 +0200 Subject: Fix media editing modal changing dimensions when image loads (#12131) --- .../mastodon/components/extended_video_player.js | 63 ------------------ app/javascript/mastodon/components/gifv.js | 75 ++++++++++++++++++++++ .../features/ui/components/focal_point_modal.js | 36 ++++++++++- .../mastodon/features/ui/components/media_modal.js | 6 +- app/javascript/styles/mastodon/components.scss | 3 +- 5 files changed, 113 insertions(+), 70 deletions(-) delete mode 100644 app/javascript/mastodon/components/extended_video_player.js create mode 100644 app/javascript/mastodon/components/gifv.js (limited to 'app/javascript') diff --git a/app/javascript/mastodon/components/extended_video_player.js b/app/javascript/mastodon/components/extended_video_player.js deleted file mode 100644 index 009c0d559..000000000 --- a/app/javascript/mastodon/components/extended_video_player.js +++ /dev/null @@ -1,63 +0,0 @@ -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 ( -
-
- ); - } - -} diff --git a/app/javascript/mastodon/components/gifv.js b/app/javascript/mastodon/components/gifv.js new file mode 100644 index 000000000..83cfae49c --- /dev/null +++ b/app/javascript/mastodon/components/gifv.js @@ -0,0 +1,75 @@ +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 ( +
+ {loading && ( + + )} + +
+ ); + } + +} 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 1ab79a21d..3694ab904 100644 --- a/app/javascript/mastodon/features/ui/components/focal_point_modal.js +++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.js @@ -16,6 +16,7 @@ import UploadProgress from 'mastodon/features/compose/components/upload_progress 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' }, @@ -41,6 +42,36 @@ const removeExtraLineBreaks = str => str.replace(/\n\n/g, '******') 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 ; + } else { + return ; + } + } + +} + export default @connect(mapStateToProps, mapDispatchToProps) @injectIntl class FocalPointModal extends ImmutablePureComponent { @@ -60,6 +91,7 @@ class FocalPointModal extends ImmutablePureComponent { description: '', dirty: false, progress: 0, + loading: true, }; componentWillMount () { @@ -242,8 +274,8 @@ class FocalPointModal extends ImmutablePureComponent {
{focals && (
- {media.get('type') === 'image' && } - {media.get('type') === 'gifv' &&