From e2b561e3a521ff893943c0e9e32952e35934ca54 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 4 Oct 2022 20:13:23 +0200 Subject: [PATCH] Fix logged-out web UI on smaller screens (#19263) --- .../components/not_signed_in_indicator.js | 12 + .../mastodon/features/about/index.js | 34 +++ .../mastodon/features/compose/index.js | 93 +++--- .../mastodon/features/explore/index.js | 29 +- .../features/getting_started/index.js | 102 ++----- .../mastodon/features/home_timeline/index.js | 30 +- .../features/keyboard_shortcuts/index.js | 13 +- .../mastodon/features/notifications/index.js | 61 ++-- .../features/ui/components/columns_area.js | 84 +---- .../features/ui/components/compose_panel.js | 2 +- .../mastodon/features/ui/components/header.js | 53 ++++ .../features/ui/components/link_footer.js | 45 +-- .../ui/components/navigation_panel.js | 16 +- .../features/ui/components/tabs_bar.js | 86 ------ app/javascript/mastodon/features/ui/index.js | 5 + .../features/ui/util/async-components.js | 4 + app/javascript/mastodon/locales/en.json | 6 +- .../styles/mastodon/components.scss | 286 +++++++++++------- app/javascript/styles/mastodon/variables.scss | 2 +- 19 files changed, 489 insertions(+), 474 deletions(-) create mode 100644 app/javascript/mastodon/components/not_signed_in_indicator.js create mode 100644 app/javascript/mastodon/features/about/index.js create mode 100644 app/javascript/mastodon/features/ui/components/header.js delete mode 100644 app/javascript/mastodon/features/ui/components/tabs_bar.js diff --git a/app/javascript/mastodon/components/not_signed_in_indicator.js b/app/javascript/mastodon/components/not_signed_in_indicator.js new file mode 100644 index 000000000..b440c6be2 --- /dev/null +++ b/app/javascript/mastodon/components/not_signed_in_indicator.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +const NotSignedInIndicator = () => ( +
+
+ +
+
+); + +export default NotSignedInIndicator; diff --git a/app/javascript/mastodon/features/about/index.js b/app/javascript/mastodon/features/about/index.js new file mode 100644 index 000000000..c48d81d9a --- /dev/null +++ b/app/javascript/mastodon/features/about/index.js @@ -0,0 +1,34 @@ +import React from 'react'; +import { defineMessages, injectIntl } from 'react-intl'; +import PropTypes from 'prop-types'; +import Column from 'mastodon/components/column'; +import LinkFooter from 'mastodon/features/ui/components/link_footer'; +import { Helmet } from 'react-helmet'; +import { title } from 'mastodon/initial_state'; + +const messages = defineMessages({ + title: { id: 'column.about', defaultMessage: 'About' }, +}); + +export default @injectIntl +class About extends React.PureComponent { + + static propTypes = { + intl: PropTypes.object.isRequired, + }; + + render () { + const { intl } = this.props; + + return ( + + + + + {intl.formatMessage(messages.title)} - {title} + + + ); + } + +} diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index 711a55904..c27556a0e 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -17,6 +17,7 @@ import elephantUIPlane from '../../../images/elephant_ui_plane.svg'; import { mascot } from '../../initial_state'; import Icon from 'mastodon/components/icon'; import { logOut } from 'mastodon/utils/log_out'; +import Column from 'mastodon/components/column'; const messages = defineMessages({ start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, @@ -92,57 +93,59 @@ class Compose extends React.PureComponent { render () { const { multiColumn, showSearch, isSearchPage, intl } = this.props; - let header = ''; - if (multiColumn) { const { columns } = this.props; - header = ( - + + return ( +
+ + + {(multiColumn || isSearchPage) && } + +
+ {!isSearchPage &&
+ + + + +
+ +
+
} + + + {({ x }) => ( +
+ +
+ )} +
+
+
); } return ( -
- {header} - - {(multiColumn || isSearchPage) && } - -
- {!isSearchPage &&
- - - - -
- -
-
} - - - {({ x }) => ( -
- -
- )} -
-
-
+ + + + ); } diff --git a/app/javascript/mastodon/features/explore/index.js b/app/javascript/mastodon/features/explore/index.js index e1d1eb563..21bb6e828 100644 --- a/app/javascript/mastodon/features/explore/index.js +++ b/app/javascript/mastodon/features/explore/index.js @@ -30,13 +30,13 @@ class Explore extends React.PureComponent { static contextTypes = { router: PropTypes.object, + identity: PropTypes.object, }; static propTypes = { intl: PropTypes.object.isRequired, multiColumn: PropTypes.bool, isSearching: PropTypes.bool, - layout: PropTypes.string, }; handleHeaderClick = () => { @@ -48,22 +48,21 @@ class Explore extends React.PureComponent { } render () { - const { intl, multiColumn, isSearching, layout } = this.props; + const { intl, multiColumn, isSearching } = this.props; + const { signedIn } = this.context.identity; return ( - {layout === 'mobile' ? ( -
- -
- ) : ( - - )} + + +
+ +
{isSearching ? ( @@ -74,7 +73,7 @@ class Explore extends React.PureComponent { - + {signedIn && }
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index 65cee7498..d998127a2 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -1,19 +1,20 @@ import React from 'react'; -import Column from '../ui/components/column'; +import Column from 'mastodon/components/column'; +import ColumnHeader from 'mastodon/components/column_header'; import ColumnLink from '../ui/components/column_link'; import ColumnSubheading from '../ui/components/column_subheading'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { me, showTrends } from '../../initial_state'; +import { me, title, showTrends } from '../../initial_state'; import { fetchFollowRequests } from 'mastodon/actions/accounts'; import { List as ImmutableList } from 'immutable'; import NavigationContainer from '../compose/containers/navigation_container'; -import Icon from 'mastodon/components/icon'; import LinkFooter from 'mastodon/features/ui/components/link_footer'; import TrendsContainer from './containers/trends_container'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' }, @@ -40,7 +41,6 @@ const messages = defineMessages({ const mapStateToProps = state => ({ myAccount: state.getIn(['accounts', me]), - columns: state.getIn(['settings', 'columns']), unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size, }); @@ -58,20 +58,18 @@ const badgeDisplay = (number, limit) => { } }; -const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2); - export default @connect(mapStateToProps, mapDispatchToProps) @injectIntl class GettingStarted extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object.isRequired, + identity: PropTypes.object, }; static propTypes = { intl: PropTypes.object.isRequired, - myAccount: ImmutablePropTypes.map.isRequired, - columns: ImmutablePropTypes.list, + myAccount: ImmutablePropTypes.map, multiColumn: PropTypes.bool, fetchFollowRequests: PropTypes.func.isRequired, unreadFollowRequests: PropTypes.number, @@ -79,10 +77,10 @@ class GettingStarted extends ImmutablePureComponent { }; componentDidMount () { - const { fetchFollowRequests, multiColumn } = this.props; + const { fetchFollowRequests } = this.props; + const { signedIn } = this.context.identity; - if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) { - this.context.router.history.replace('/home'); + if (!signedIn) { return; } @@ -90,91 +88,57 @@ class GettingStarted extends ImmutablePureComponent { } render () { - const { intl, myAccount, columns, multiColumn, unreadFollowRequests } = this.props; + const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props; + const { signedIn } = this.context.identity; const navItems = []; - let height = (multiColumn) ? 0 : 60; - - if (multiColumn) { - navItems.push( - , - ); - height += 34; - } navItems.push( + , , + , + , ); - height += 48; - - if (multiColumn) { - navItems.push( - , - , - ); - - height += 48*2; + if (signedIn) { navItems.push( , - ); - - height += 34; - } - - if (multiColumn && !columns.find(item => item.get('id') === 'HOME')) { - navItems.push( , + , + , + , + , ); - height += 48; - } - navItems.push( - , - , - , - , - ); + if (myAccount.get('locked') || unreadFollowRequests > 0) { + navItems.push(); + } - height += 48*4; - - if (myAccount.get('locked') || unreadFollowRequests > 0) { - navItems.push(); - height += 48; - } - - if (!multiColumn) { navItems.push( , , ); - - height += 34 + 48; } return ( - - {multiColumn &&
-

- -

-
} + + {(signedIn && !multiColumn) ? : } -
-
- {!multiColumn && } +
+
{navItems}
{!multiColumn &&
} - +
- {multiColumn && showTrends && } + {(multiColumn && showTrends) && } + + + {intl.formatMessage(messages.menu)} - {title} + ); } diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js index dc440f2fe..aac92244d 100644 --- a/app/javascript/mastodon/features/home_timeline/index.js +++ b/app/javascript/mastodon/features/home_timeline/index.js @@ -13,6 +13,9 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/an import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container'; import classNames from 'classnames'; import IconWithBadge from 'mastodon/components/icon_with_badge'; +import NotSignedInIndicator from 'mastodon/components/not_signed_in_indicator'; +import { Helmet } from 'react-helmet'; +import { title } from 'mastodon/initial_state'; const messages = defineMessages({ title: { id: 'column.home', defaultMessage: 'Home' }, @@ -32,6 +35,10 @@ export default @connect(mapStateToProps) @injectIntl class HomeTimeline extends React.PureComponent { + static contextTypes = { + identity: PropTypes.object, + }; + static propTypes = { dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, @@ -113,6 +120,7 @@ class HomeTimeline extends React.PureComponent { render () { const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props; const pinned = !!columnId; + const { signedIn } = this.context.identity; let announcementsButton = null; @@ -147,14 +155,20 @@ class HomeTimeline extends React.PureComponent { - }} />} - bindToDocument={!multiColumn} - /> + {signedIn ? ( + }} />} + bindToDocument={!multiColumn} + /> + ) : } + + + {intl.formatMessage(messages.title)} - {title} + ); } diff --git a/app/javascript/mastodon/features/keyboard_shortcuts/index.js b/app/javascript/mastodon/features/keyboard_shortcuts/index.js index 8f1631d82..2a32577ba 100644 --- a/app/javascript/mastodon/features/keyboard_shortcuts/index.js +++ b/app/javascript/mastodon/features/keyboard_shortcuts/index.js @@ -1,9 +1,9 @@ import React from 'react'; -import Column from '../ui/components/column'; -import ColumnBackButtonSlim from '../../components/column_back_button_slim'; +import Column from 'mastodon/components/column'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import ColumnHeader from 'mastodon/components/column_header'; const messages = defineMessages({ heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' }, @@ -21,8 +21,13 @@ class KeyboardShortcuts extends ImmutablePureComponent { const { intl, multiColumn } = this.props; return ( - - + + +
diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js index a6a277d7e..d9f8101c3 100644 --- a/app/javascript/mastodon/features/notifications/index.js +++ b/app/javascript/mastodon/features/notifications/index.js @@ -26,6 +26,9 @@ import LoadGap from '../../components/load_gap'; import Icon from 'mastodon/components/icon'; import compareId from 'mastodon/compare_id'; import NotificationsPermissionBanner from './components/notifications_permission_banner'; +import NotSignedInIndicator from 'mastodon/components/not_signed_in_indicator'; +import { Helmet } from 'react-helmet'; +import { title } from 'mastodon/initial_state'; const messages = defineMessages({ title: { id: 'column.notifications', defaultMessage: 'Notifications' }, @@ -69,6 +72,10 @@ export default @connect(mapStateToProps) @injectIntl class Notifications extends React.PureComponent { + static contextTypes = { + identity: PropTypes.object, + }; + static propTypes = { columnId: PropTypes.string, notifications: ImmutablePropTypes.list.isRequired, @@ -178,10 +185,11 @@ class Notifications extends React.PureComponent { const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props; const pinned = !!columnId; const emptyMessage = ; + const { signedIn } = this.context.identity; let scrollableContent = null; - const filterBarContainer = showFilterBar + const filterBarContainer = (signedIn && showFilterBar) ? () : null; @@ -211,26 +219,32 @@ class Notifications extends React.PureComponent { this.scrollableContent = scrollableContent; - const scrollContainer = ( - } - alwaysPrepend - emptyMessage={emptyMessage} - onLoadMore={this.handleLoadOlder} - onLoadPending={this.handleLoadPending} - onScrollToTop={this.handleScrollToTop} - onScroll={this.handleScroll} - bindToDocument={!multiColumn} - > - {scrollableContent} - - ); + let scrollContainer; + + if (signedIn) { + scrollContainer = ( + } + alwaysPrepend + emptyMessage={emptyMessage} + onLoadMore={this.handleLoadOlder} + onLoadPending={this.handleLoadPending} + onScrollToTop={this.handleScrollToTop} + onScroll={this.handleScroll} + bindToDocument={!multiColumn} + > + {scrollableContent} + + ); + } else { + scrollContainer = ; + } let extraButton = null; @@ -262,8 +276,13 @@ class Notifications extends React.PureComponent { > + {filterBarContainer} {scrollContainer} + + + {intl.formatMessage(messages.title)} - {title} + ); } diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index 83e10e003..cc1bc83e0 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -3,13 +3,7 @@ import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; - -import ReactSwipeableViews from 'react-swipeable-views'; -import TabsBar, { links, getIndex, getLink } from './tabs_bar'; import { Link } from 'react-router-dom'; - -import { disableSwiping } from 'mastodon/initial_state'; - import BundleContainer from '../containers/bundle_container'; import ColumnLoading from './column_loading'; import DrawerLoading from './drawer_loading'; @@ -71,20 +65,13 @@ class ColumnsArea extends ImmutablePureComponent { children: PropTypes.node, }; - // Corresponds to (max-width: 600px + (285px * 1) + (10px * 1)) in SCSS - mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 895px)'); + // Corresponds to (max-width: $no-gap-breakpoint + 285px - 1px) in SCSS + mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 1174px)'); state = { - shouldAnimate: false, renderComposePanel: !(this.mediaQuery && this.mediaQuery.matches), } - componentWillReceiveProps() { - if (typeof this.pendingIndex !== 'number' && this.lastIndex !== getIndex(this.context.router.history.location.pathname)) { - this.setState({ shouldAnimate: false }); - } - } - componentDidMount() { if (!this.props.singleColumn) { this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); @@ -99,10 +86,7 @@ class ColumnsArea extends ImmutablePureComponent { this.setState({ renderComposePanel: !this.mediaQuery.matches }); } - this.lastIndex = getIndex(this.context.router.history.location.pathname); this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl'); - - this.setState({ shouldAnimate: true }); } componentWillUpdate(nextProps) { @@ -115,13 +99,6 @@ class ColumnsArea extends ImmutablePureComponent { if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) { this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); } - - const newIndex = getIndex(this.context.router.history.location.pathname); - - if (this.lastIndex !== newIndex) { - this.lastIndex = newIndex; - this.setState({ shouldAnimate: true }); - } } componentWillUnmount () { @@ -149,31 +126,6 @@ class ColumnsArea extends ImmutablePureComponent { this.setState({ renderComposePanel: !e.matches }); } - handleSwipe = (index) => { - this.pendingIndex = index; - - const nextLinkTranslationId = links[index].props['data-preview-title-id']; - const currentLinkSelector = '.tabs-bar__link.active'; - const nextLinkSelector = `.tabs-bar__link[data-preview-title-id="${nextLinkTranslationId}"]`; - - // HACK: Remove the active class from the current link and set it to the next one - // React-router does this for us, but too late, feeling laggy. - document.querySelector(currentLinkSelector).classList.remove('active'); - document.querySelector(nextLinkSelector).classList.add('active'); - - if (!this.state.shouldAnimate && typeof this.pendingIndex === 'number') { - this.context.router.history.push(getLink(this.pendingIndex)); - this.pendingIndex = null; - } - } - - handleAnimationEnd = () => { - if (typeof this.pendingIndex === 'number') { - this.context.router.history.push(getLink(this.pendingIndex)); - this.pendingIndex = null; - } - } - handleWheel = () => { if (typeof this._interruptScrollAnimation !== 'function') { return; @@ -186,22 +138,6 @@ class ColumnsArea extends ImmutablePureComponent { this.node = node; } - renderView = (link, index) => { - const columnIndex = getIndex(this.context.router.history.location.pathname); - const title = this.props.intl.formatMessage({ id: link.props['data-preview-title-id'] }); - const icon = link.props['data-preview-icon']; - - const view = (index === columnIndex) ? - React.cloneElement(this.props.children) : - ; - - return ( -
- {view} -
- ); - } - renderLoading = columnId => () => { return columnId === 'COMPOSE' ? : ; } @@ -212,22 +148,12 @@ class ColumnsArea extends ImmutablePureComponent { render () { const { columns, children, singleColumn, isModalOpen, intl } = this.props; - const { shouldAnimate, renderComposePanel } = this.state; + const { renderComposePanel } = this.state; const { signedIn } = this.context.identity; - const columnIndex = getIndex(this.context.router.history.location.pathname); - if (singleColumn) { const floatingActionButton = (!signedIn || shouldHideFAB(this.context.router.history.location.pathname)) ? null : ; - const content = columnIndex !== -1 ? ( - - {links.map(this.renderView)} - - ) : ( -
{children}
- ); - return (
@@ -237,8 +163,8 @@ class ColumnsArea extends ImmutablePureComponent {
- - {content} +
+
{children}
diff --git a/app/javascript/mastodon/features/ui/components/compose_panel.js b/app/javascript/mastodon/features/ui/components/compose_panel.js index 1c128188f..655c202fa 100644 --- a/app/javascript/mastodon/features/ui/components/compose_panel.js +++ b/app/javascript/mastodon/features/ui/components/compose_panel.js @@ -46,7 +46,7 @@ class ComposePanel extends React.PureComponent { )} - +
); } diff --git a/app/javascript/mastodon/features/ui/components/header.js b/app/javascript/mastodon/features/ui/components/header.js new file mode 100644 index 000000000..cddab820c --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/header.js @@ -0,0 +1,53 @@ +import React from 'react'; +import Logo from 'mastodon/components/logo'; +import { Link } from 'react-router-dom'; +import { FormattedMessage } from 'react-intl'; +import { registrationsOpen, me } from 'mastodon/initial_state'; +import Avatar from 'mastodon/components/avatar'; +import Permalink from 'mastodon/components/permalink'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; + +const Account = connect(state => ({ + account: state.getIn(['accounts', me]), +}))(({ account }) => ( + + {account.get('acct')} + + +)); + +export default class Header extends React.PureComponent { + + static contextTypes = { + identity: PropTypes.object, + }; + + render () { + const { signedIn } = this.context.identity; + + let content; + + if (signedIn) { + content = ; + } else { + content = ( + + + + + ); + } + + return ( +
+ + +
+ {content} +
+
+ ); + } + +} diff --git a/app/javascript/mastodon/features/ui/components/link_footer.js b/app/javascript/mastodon/features/ui/components/link_footer.js index dd05d03dd..2b092a182 100644 --- a/app/javascript/mastodon/features/ui/components/link_footer.js +++ b/app/javascript/mastodon/features/ui/components/link_footer.js @@ -3,7 +3,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { Link } from 'react-router-dom'; -import { limitedFederationMode, version, repository, source_url, profile_directory as profileDirectory } from 'mastodon/initial_state'; +import { version, repository, source_url, profile_directory as profileDirectory } from 'mastodon/initial_state'; import { logOut } from 'mastodon/utils/log_out'; import { openModal } from 'mastodon/actions/modal'; import { PERMISSION_INVITE_USERS } from 'mastodon/permissions'; @@ -33,7 +33,6 @@ class LinkFooter extends React.PureComponent { }; static propTypes = { - withHotkeys: PropTypes.bool, onLogout: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; @@ -48,40 +47,26 @@ class LinkFooter extends React.PureComponent { } render () { - const { withHotkeys } = this.props; const { signedIn, permissions } = this.context.identity; const items = []; - if ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) { - items.push(); - } - - if (signedIn && withHotkeys) { - items.push(); - } - - if (signedIn) { - items.push(); - } - - if (!limitedFederationMode) { - items.push(); - } + items.push(); + items.push(); + items.push(); + items.push(); + items.push(); + items.push(); if (profileDirectory) { - items.push(); + items.push(); } - items.push(); - items.push(); - if (signedIn) { - items.push(); - } + if ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) { + items.push(); + } - items.push(); - - if (signedIn) { + items.push(); items.push(); } @@ -93,9 +78,9 @@ class LinkFooter extends React.PureComponent {

{repository} (v{version}) }} + id='getting_started.free_software_notice' + defaultMessage='Mastodon is free, open source software. You can view the source code, contribute or report issues at {repository}.' + values={{ repository: {repository} (v{version}) }} />

diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.js b/app/javascript/mastodon/features/ui/components/navigation_panel.js index 00ae04761..7c7c9056f 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.js +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.js @@ -23,9 +23,10 @@ export default class NavigationPanel extends React.Component { return (
- - -
+
+ +
+
{signedIn && ( @@ -40,10 +41,10 @@ export default class NavigationPanel extends React.Component { {!signedIn && ( - +

- +
)} {signedIn && ( @@ -62,6 +63,11 @@ export default class NavigationPanel extends React.Component {
)} +
+
+ +
+ {showTrends && (
diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js deleted file mode 100644 index 55668cab6..000000000 --- a/app/javascript/mastodon/features/ui/components/tabs_bar.js +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { NavLink, withRouter } from 'react-router-dom'; -import { FormattedMessage, injectIntl } from 'react-intl'; -import { debounce } from 'lodash'; -import { isUserTouching } from '../../../is_mobile'; -import Icon from 'mastodon/components/icon'; -import NotificationsCounterIcon from './notifications_counter_icon'; - -export const links = [ - , - , - , - , - , - , -]; - -export function getIndex (path) { - return links.findIndex(link => link.props.to === path); -} - -export function getLink (index) { - return links[index].props.to; -} - -export default @injectIntl -@withRouter -class TabsBar extends React.PureComponent { - - static propTypes = { - intl: PropTypes.object.isRequired, - history: PropTypes.object.isRequired, - } - - setRef = ref => { - this.node = ref; - } - - handleClick = (e) => { - // Only apply optimization for touch devices, which we assume are slower - // We thus avoid the 250ms delay for non-touch devices and the lag for touch devices - if (isUserTouching()) { - e.preventDefault(); - e.persist(); - - requestAnimationFrame(() => { - const tabs = Array(...this.node.querySelectorAll('.tabs-bar__link')); - const currentTab = tabs.find(tab => tab.classList.contains('active')); - const nextTab = tabs.find(tab => tab.contains(e.target)); - const { props: { to } } = links[Array(...this.node.childNodes).indexOf(nextTab)]; - - - if (currentTab !== nextTab) { - if (currentTab) { - currentTab.classList.remove('active'); - } - - const listener = debounce(() => { - nextTab.removeEventListener('transitionend', listener); - this.props.history.push(to); - }, 50); - - nextTab.addEventListener('transitionend', listener); - nextTab.classList.add('active'); - } - }); - } - - } - - render () { - const { intl: { formatMessage } } = this.props; - - return ( -
- - -
-
- ); - } - -} diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 5825db1e4..4e37908c8 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -51,10 +51,12 @@ import { Directory, Explore, FollowRecommendations, + About, } from './util/async-components'; import { me, title } from '../../initial_state'; import { closeOnboarding, INTRODUCTION_VERSION } from 'mastodon/actions/onboarding'; import { Helmet } from 'react-helmet'; +import Header from './components/header'; // Dummy import, to make sure that ends up in the application bundle. // Without this it ends up in ~8 very commonly used bundles. @@ -170,6 +172,7 @@ class SwitchingColumnsArea extends React.PureComponent { + @@ -559,6 +562,8 @@ class UI extends React.PureComponent { return (
+
+ {children} diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index 29b06206a..5907e0772 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -165,3 +165,7 @@ export function Explore () { export function FilterModal () { return import(/*webpackChunkName: "modals/filter_modal" */'../components/filter_modal'); } + +export function About () { + return import(/*webpackChunkName: "features/about" */'../../about'); +} diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 463ec7bed..81a41b3fc 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -222,7 +222,7 @@ "follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.", "generic.saved": "Saved", "getting_started.developers": "Developers", - "getting_started.directory": "Profile directory", + "getting_started.directory": "Directory", "getting_started.documentation": "Documentation", "getting_started.heading": "Getting started", "getting_started.invite": "Invite people", @@ -310,7 +310,7 @@ "mute_modal.duration": "Duration", "mute_modal.hide_notifications": "Hide notifications from this user?", "mute_modal.indefinite": "Indefinite", - "navigation_bar.apps": "Mobile apps", + "navigation_bar.apps": "Get the app", "navigation_bar.blocks": "Blocked users", "navigation_bar.bookmarks": "Bookmarks", "navigation_bar.community_timeline": "Local timeline", @@ -324,7 +324,7 @@ "navigation_bar.filters": "Muted words", "navigation_bar.follow_requests": "Follow requests", "navigation_bar.follows_and_followers": "Follows and followers", - "navigation_bar.info": "About this server", + "navigation_bar.info": "About", "navigation_bar.keyboard_shortcuts": "Hotkeys", "navigation_bar.lists": "Lists", "navigation_bar.logout": "Logout", diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 87ec6bb8a..e831fce53 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2191,27 +2191,62 @@ a.account__display-name { &__main { box-sizing: border-box; width: 100%; - max-width: 600px; flex: 0 0 auto; display: flex; flex-direction: column; @media screen and (min-width: $no-gap-breakpoint) { padding: 0 10px; + max-width: 600px; } } } } +$ui-header-height: 55px; + +.ui__header { + display: none; + box-sizing: border-box; + height: $ui-header-height; + position: sticky; + top: 0; + z-index: 2; + justify-content: space-between; + align-items: center; + + &__logo { + display: inline-flex; + padding: 15px; + + .logo { + height: $ui-header-height - 30px; + width: auto; + } + } + + &__links { + display: flex; + align-items: center; + gap: 10px; + padding: 0 10px; + + .button { + flex: 0 0 auto; + } + } +} + .tabs-bar__wrapper { background: darken($ui-base-color, 8%); position: sticky; - top: 0; + top: $ui-header-height; z-index: 2; padding-top: 0; @media screen and (min-width: $no-gap-breakpoint) { padding-top: 10px; + top: 0; } .tabs-bar { @@ -2419,100 +2454,98 @@ a.account__display-name { padding-top: 0; } - @media screen and (min-width: 630px) { - .detailed-status { - padding: 15px; + .detailed-status { + padding: 15px; - .media-gallery, - .video-player, - .audio-player { - margin-top: 15px; - } + .media-gallery, + .video-player, + .audio-player { + margin-top: 15px; + } + } + + .account__header__bar { + padding: 5px 10px; + } + + .navigation-bar, + .compose-form { + padding: 15px; + } + + .compose-form .compose-form__publish .compose-form__publish-button-wrapper { + padding-top: 15px; + } + + .notification__report { + padding: 15px 15px 15px (48px + 15px * 2); + min-height: 48px + 2px; + + &__avatar { + left: 15px; + top: 17px; + } + } + + .status { + padding: 15px 15px 15px (48px + 15px * 2); + min-height: 48px + 2px; + + &__avatar { + left: 15px; + top: 17px; } - .account__header__bar { - padding: 5px 10px; + &__content { + padding-top: 5px; } - .navigation-bar, - .compose-form { - padding: 15px; - } - - .compose-form .compose-form__publish .compose-form__publish-button-wrapper { + &__prepend { + margin-left: 48px + 15px * 2; padding-top: 15px; } - .notification__report { - padding: 15px 15px 15px (48px + 15px * 2); - min-height: 48px + 2px; + &__prepend-icon-wrapper { + left: -32px; + } - &__avatar { - left: 15px; - top: 17px; - } + .media-gallery, + &__action-bar, + .video-player, + .audio-player { + margin-top: 10px; + } + } + + .account { + padding: 15px 10px; + + &__header__bio { + margin: 0 -10px; + } + } + + .notification { + &__message { + margin-left: 48px + 15px * 2; + padding-top: 15px; + } + + &__favourite-icon-wrapper { + left: -32px; } .status { - padding: 15px 15px 15px (48px + 15px * 2); - min-height: 48px + 2px; - - &__avatar { - left: 15px; - top: 17px; - } - - &__content { - padding-top: 5px; - } - - &__prepend { - margin-left: 48px + 15px * 2; - padding-top: 15px; - } - - &__prepend-icon-wrapper { - left: -32px; - } - - .media-gallery, - &__action-bar, - .video-player, - .audio-player { - margin-top: 10px; - } + padding-top: 8px; } .account { - padding: 15px 10px; - - &__header__bio { - margin: 0 -10px; - } + padding-top: 8px; } - .notification { - &__message { - margin-left: 48px + 15px * 2; - padding-top: 15px; - } - - &__favourite-icon-wrapper { - left: -32px; - } - - .status { - padding-top: 8px; - } - - .account { - padding-top: 8px; - } - - .account__avatar-wrapper { - margin-left: 17px; - margin-right: 15px; - } + .account__avatar-wrapper { + margin-left: 17px; + margin-right: 15px; } } } @@ -2554,19 +2587,7 @@ a.account__display-name { .search { margin-bottom: 10px; } -} -@media screen and (max-width: 600px + (285px * 1) + (10px * 1)) { - .columns-area__panels__pane--compositional { - display: none; - } - - .with-fab .scrollable .item-list:last-child { - padding-bottom: 5.25rem; - } -} - -@media screen and (min-width: 600px + (285px * 1) + (10px * 1)) { .floating-action-button, .tabs-bar__link.optional { display: none; @@ -2575,18 +2596,76 @@ a.account__display-name { .search-page .search { display: none; } -} -@media screen and (max-width: 600px + (285px * 2) + (10px * 2)) { - .columns-area__panels__pane--navigational { + .navigation-panel__legal { display: none; } } -@media screen and (min-width: 600px + (285px * 2) + (10px * 2)) { - .tabs-bar { +@media screen and (max-width: $no-gap-breakpoint - 1px) { + .with-fab .scrollable .item-list:last-child { + padding-bottom: 5.25rem; + } + + .columns-area__panels__main { + width: calc(100% - 55px); + } + + .columns-area__panels { + min-height: calc(100vh - $ui-header-height); + } + + .columns-area__panels__pane--navigational { + min-width: 55px; + + .columns-area__panels__pane__inner { + width: 55px; + } + + .navigation-panel { + margin: 0; + background: $ui-base-color; + border-left: 1px solid lighten($ui-base-color, 8%); + height: 100vh; + } + + .column-link span, + .navigation-panel__sign-in-banner, + .navigation-panel__logo, + .getting-started__trends { + display: none; + } + + .column-link__icon { + font-size: 18px; + } + } + + .ui__header { + display: flex; + background: $ui-base-color; + border-bottom: 1px solid lighten($ui-base-color, 8%); + } + + .column-header, + .column-back-button, + .scrollable { + border-radius: 0 !important; + } +} + +.explore__search-header { + display: none; +} + +@media screen and (max-width: $no-gap-breakpoint + 285px - 1px) { + .columns-area__panels__pane--compositional { display: none; } + + .explore__search-header { + display: flex; + } } .icon-with-badge { @@ -7360,7 +7439,7 @@ noscript { path:first-child { fill: rgba($highlight-text-color, 0.25) !important; - fill-opacity: 1 !important; + fill-opacity: 100% !important; } path:last-child { @@ -7832,10 +7911,9 @@ noscript { } .explore__search-header { - background: $ui-base-color; - display: flex; - align-items: flex-start; + background: darken($ui-base-color, 4%); justify-content: center; + align-items: center; padding: 15px; .search { @@ -7844,14 +7922,8 @@ noscript { } .search__input { - border-radius: 4px; - color: $inverted-text-color; - background: $simple-background-color; + border: 1px solid lighten($ui-base-color, 8%); padding: 10px; - - &::placeholder { - color: $dark-text-color; - } } .search .fa { diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss index be2c900ea..775a12e68 100644 --- a/app/javascript/styles/mastodon/variables.scss +++ b/app/javascript/styles/mastodon/variables.scss @@ -53,7 +53,7 @@ $media-modal-media-max-width: 100%; // put margins on top and bottom of image to avoid the screen covered by image. $media-modal-media-max-height: 80%; -$no-gap-breakpoint: 415px; +$no-gap-breakpoint: 890px; $font-sans-serif: 'mastodon-font-sans-serif' !default; $font-display: 'mastodon-font-display' !default;