From 47b433915bb83df3a3a969907a4962bfa9d84f65 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 11 Oct 2021 14:00:59 -0500 Subject: [PATCH] Placeholder: display placeholder statuses while timelines are loading --- app/soapbox/components/scrollable_list.js | 10 ++++- app/soapbox/components/status_list.js | 12 +++++- .../components/placeholder_avatar.js | 35 ++++++++++++++++ .../components/placeholder_display_name.js | 27 +++++++++++++ .../components/placeholder_material_status.js | 16 ++++++++ .../components/placeholder_status.js | 40 +++++++++++++++++++ .../components/placeholder_status_content.js | 23 +++++++++++ app/soapbox/features/placeholder/utils.js | 16 ++++++++ app/styles/basics.scss | 9 +++++ app/styles/components/columns.scss | 3 +- app/styles/components/profile_hover_card.scss | 1 - 11 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 app/soapbox/features/placeholder/components/placeholder_avatar.js create mode 100644 app/soapbox/features/placeholder/components/placeholder_display_name.js create mode 100644 app/soapbox/features/placeholder/components/placeholder_material_status.js create mode 100644 app/soapbox/features/placeholder/components/placeholder_status.js create mode 100644 app/soapbox/features/placeholder/components/placeholder_status_content.js create mode 100644 app/soapbox/features/placeholder/utils.js diff --git a/app/soapbox/components/scrollable_list.js b/app/soapbox/components/scrollable_list.js index 3576960db..0e6c1991b 100644 --- a/app/soapbox/components/scrollable_list.js +++ b/app/soapbox/components/scrollable_list.js @@ -29,6 +29,8 @@ export default class ScrollableList extends PureComponent { children: PropTypes.node, onScrollToTop: PropTypes.func, onScroll: PropTypes.func, + placeholderComponent: PropTypes.node, + placeholderCount: PropTypes.number, }; state = { @@ -222,7 +224,13 @@ export default class ScrollableList extends PureComponent { } renderLoading = () => { - const { prepend } = this.props; + const { prepend, placeholderComponent: Placeholder, placeholderCount } = this.props; + + if (Placeholder && placeholderCount > 0) { + return Array(placeholderCount).fill().map((_, i) => ( + + )); + } return (
diff --git a/app/soapbox/components/status_list.js b/app/soapbox/components/status_list.js index f5808a648..a186a8073 100644 --- a/app/soapbox/components/status_list.js +++ b/app/soapbox/components/status_list.js @@ -9,6 +9,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import LoadGap from './load_gap'; import ScrollableList from './scrollable_list'; import TimelineQueueButtonHeader from './timeline_queue_button_header'; +import PlaceholderMaterialStatus from 'soapbox/features/placeholder/components/placeholder_material_status'; const messages = defineMessages({ queue: { id: 'status_list.queue_label', defaultMessage: 'Click to see {count} new {count, plural, one {post} other {posts}}' }, @@ -213,7 +214,16 @@ export default class StatusList extends ImmutablePureComponent { count={totalQueuedItemsCount} message={messages.queue} />, - + {this.renderScrollableContent()} , ]; diff --git a/app/soapbox/features/placeholder/components/placeholder_avatar.js b/app/soapbox/features/placeholder/components/placeholder_avatar.js new file mode 100644 index 000000000..8c9360321 --- /dev/null +++ b/app/soapbox/features/placeholder/components/placeholder_avatar.js @@ -0,0 +1,35 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +export default class Avatar extends React.PureComponent { + + static propTypes = { + size: PropTypes.number, + style: PropTypes.object, + inline: PropTypes.bool, + }; + + static defaultProps = { + inline: false, + }; + + render() { + const { size, inline } = this.props; + + // : TODO : remove inline and change all avatars to be sized using css + const style = !size ? {} : { + width: `${size}px`, + height: `${size}px`, + }; + + return ( +
+ ); + } + +} diff --git a/app/soapbox/features/placeholder/components/placeholder_display_name.js b/app/soapbox/features/placeholder/components/placeholder_display_name.js new file mode 100644 index 000000000..dd5c2ad47 --- /dev/null +++ b/app/soapbox/features/placeholder/components/placeholder_display_name.js @@ -0,0 +1,27 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { randomIntFromInterval, generateText } from '../utils'; + +export default class DisplayName extends React.Component { + + static propTypes = { + maxLength: PropTypes.number.isRequired, + minLength: PropTypes.number.isRequired, + } + + render() { + const { maxLength, minLength } = this.props; + const length = randomIntFromInterval(maxLength, minLength); + + return ( + + + + {generateText(length)} + + + + ); + } + +} diff --git a/app/soapbox/features/placeholder/components/placeholder_material_status.js b/app/soapbox/features/placeholder/components/placeholder_material_status.js new file mode 100644 index 000000000..f55d9d086 --- /dev/null +++ b/app/soapbox/features/placeholder/components/placeholder_material_status.js @@ -0,0 +1,16 @@ +import React from 'react'; +import PlaceholderStatus from './placeholder_status'; + +export default class PlaceholderMaterialStatus extends React.Component { + + render() { + return ( +
+
+ +
+
+ ); + } + +} diff --git a/app/soapbox/features/placeholder/components/placeholder_status.js b/app/soapbox/features/placeholder/components/placeholder_status.js new file mode 100644 index 000000000..1fea821ca --- /dev/null +++ b/app/soapbox/features/placeholder/components/placeholder_status.js @@ -0,0 +1,40 @@ +import React from 'react'; +import PlaceholderAvatar from './placeholder_avatar'; +import PlaceholderDisplayName from './placeholder_display_name'; +import PlaceholderStatusContent from './placeholder_status_content'; + +export default class PlaceholderStatus extends React.Component { + + shouldComponentUpdate() { + // Re-rendering this will just cause the random lengths to jump around. + // There's basically no reason to ever do it. + return false; + } + + render() { + return ( +
+
+
+
+
+
+ +
+ + + +
+
+ + + + {/* TODO */} + {/* */} +
+
+
+ ); + } + +} diff --git a/app/soapbox/features/placeholder/components/placeholder_status_content.js b/app/soapbox/features/placeholder/components/placeholder_status_content.js new file mode 100644 index 000000000..cf47c80d1 --- /dev/null +++ b/app/soapbox/features/placeholder/components/placeholder_status_content.js @@ -0,0 +1,23 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { randomIntFromInterval, generateText } from '../utils'; + +export default class PlaceholderStatusContent extends React.Component { + + static propTypes = { + maxLength: PropTypes.number.isRequired, + minLength: PropTypes.number.isRequired, + } + + render() { + const { maxLength, minLength } = this.props; + const length = randomIntFromInterval(maxLength, minLength); + + return( +
+ {generateText(length)} +
+ ); + } + +} diff --git a/app/soapbox/features/placeholder/utils.js b/app/soapbox/features/placeholder/utils.js new file mode 100644 index 000000000..f2001a61d --- /dev/null +++ b/app/soapbox/features/placeholder/utils.js @@ -0,0 +1,16 @@ +export const PLACEHOLDER_CHAR = '█'; + +export const generateText = length => { + let text = ''; + + for (let i = 0; i < length; i++) { + text += PLACEHOLDER_CHAR; + } + + return text; +}; + +// https://stackoverflow.com/a/7228322/8811886 +export const randomIntFromInterval = (min, max) => { + return Math.floor(Math.random() * (max - min + 1) + min); +}; diff --git a/app/styles/basics.scss b/app/styles/basics.scss index 4694adbfd..26f98bbf8 100644 --- a/app/styles/basics.scss +++ b/app/styles/basics.scss @@ -204,3 +204,12 @@ noscript { .greentext { color: #789922; } + +.placeholder-status { + .status__display-name strong, + .status__content { + letter-spacing: -1px; + color: var(--brand-color); + opacity: 0.1; + } +} diff --git a/app/styles/components/columns.scss b/app/styles/components/columns.scss index 2f9abd13b..726f9f40b 100644 --- a/app/styles/components/columns.scss +++ b/app/styles/components/columns.scss @@ -881,7 +881,8 @@ } // Make MaterialStatus flush against SubNavigation -.sub-navigation ~ .slist .item-list > article:first-child .material-status__status { +.sub-navigation ~ .slist .item-list > article:first-child .material-status__status, +.sub-navigation ~ .material-status .material-status__status { border-top-left-radius: 0; border-top-right-radius: 0; } diff --git a/app/styles/components/profile_hover_card.scss b/app/styles/components/profile_hover_card.scss index a30895897..e90cd64ad 100644 --- a/app/styles/components/profile_hover_card.scss +++ b/app/styles/components/profile_hover_card.scss @@ -1,6 +1,5 @@ .display-name__account { position: relative; - cursor: pointer; } .display-name .profile-hover-card {