Skip to content

Commit

Permalink
Change links in multi-column mode so tabs are open in single-column m…
Browse files Browse the repository at this point in the history
  • Loading branch information
Signez authored and jsgoldstein committed Jul 21, 2023
1 parent 9e60485 commit bdeb89b
Show file tree
Hide file tree
Showing 12 changed files with 77 additions and 19 deletions.
23 changes: 23 additions & 0 deletions app/javascript/mastodon/components/router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { PropsWithChildren } from 'react';
import React from 'react';

import type { History } from 'history';
import { createBrowserHistory } from 'history';
import { Router as OriginalRouter } from 'react-router';

import { layoutFromWindow } from 'mastodon/is_mobile';

const browserHistory = createBrowserHistory();
const originalPush = browserHistory.push.bind(browserHistory);

browserHistory.push = (path: string, state: History.LocationState) => {
if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) {
originalPush(`/deck${path}`, state);
} else {
originalPush(path, state);
}
};

export const Router: React.FC<PropsWithChildren> = ({ children }) => {
return <OriginalRouter history={browserHistory}>{children}</OriginalRouter>;
};
7 changes: 4 additions & 3 deletions app/javascript/mastodon/containers/mastodon.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import { PureComponent } from 'react';

import { Helmet } from 'react-helmet';
import { BrowserRouter, Route } from 'react-router-dom';
import { Route } from 'react-router-dom';

import { Provider as ReduxProvider } from 'react-redux';

Expand All @@ -12,6 +12,7 @@ import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis';
import { hydrateStore } from 'mastodon/actions/store';
import { connectUserStream } from 'mastodon/actions/streaming';
import ErrorBoundary from 'mastodon/components/error_boundary';
import { Router } from 'mastodon/components/router';
import UI from 'mastodon/features/ui';
import initialState, { title as siteTitle } from 'mastodon/initial_state';
import { IntlProvider } from 'mastodon/locales';
Expand Down Expand Up @@ -75,11 +76,11 @@ export default class Mastodon extends PureComponent {
<IntlProvider>
<ReduxProvider store={store}>
<ErrorBoundary>
<BrowserRouter>
<Router>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} />
</ScrollContext>
</BrowserRouter>
</Router>

<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
import { WordmarkLogo } from 'mastodon/components/logo';
import NavigationPortal from 'mastodon/components/navigation_portal';
import { timelinePreview, trendsEnabled } from 'mastodon/initial_state';
import { transientSingleColumn } from 'mastodon/is_mobile';

import ColumnLink from './column_link';
import DisabledAccountBanner from './disabled_account_banner';
Expand All @@ -29,6 +30,7 @@ const messages = defineMessages({
followsAndFollowers: { id: 'navigation_bar.follows_and_followers', defaultMessage: 'Follows and followers' },
about: { id: 'navigation_bar.about', defaultMessage: 'About' },
search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
advancedInterface: { id: 'navigation_bar.advanced_interface', defaultMessage: 'Open in advanced web interface' },
});

class NavigationPanel extends Component {
Expand All @@ -54,6 +56,12 @@ class NavigationPanel extends Component {
<div className='navigation-panel'>
<div className='navigation-panel__logo'>
<Link to='/' className='column-link column-link--logo'><WordmarkLogo /></Link>

{transientSingleColumn && (
<a href={`/deck${location.pathname}`} className='button button--block'>
{intl.formatMessage(messages.advancedInterface)}
</a>
)}
<hr />
</div>

Expand Down
24 changes: 14 additions & 10 deletions app/javascript/mastodon/features/ui/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,11 @@ class SwitchingColumnsArea extends PureComponent {
static propTypes = {
children: PropTypes.node,
location: PropTypes.object,
mobile: PropTypes.bool,
singleColumn: PropTypes.bool,
};

UNSAFE_componentWillMount () {
if (this.props.mobile) {
if (this.props.singleColumn) {
document.body.classList.toggle('layout-single-column', true);
document.body.classList.toggle('layout-multiple-columns', false);
} else {
Expand All @@ -144,9 +144,9 @@ class SwitchingColumnsArea extends PureComponent {
this.node.handleChildrenContentChange();
}

if (prevProps.mobile !== this.props.mobile) {
document.body.classList.toggle('layout-single-column', this.props.mobile);
document.body.classList.toggle('layout-multiple-columns', !this.props.mobile);
if (prevProps.singleColumn !== this.props.singleColumn) {
document.body.classList.toggle('layout-single-column', this.props.singleColumn);
document.body.classList.toggle('layout-multiple-columns', !this.props.singleColumn);
}
}

Expand All @@ -157,16 +157,17 @@ class SwitchingColumnsArea extends PureComponent {
};

render () {
const { children, mobile } = this.props;
const { children, singleColumn } = this.props;
const { signedIn } = this.context.identity;
const pathName = this.props.location.pathname;

let redirect;

if (signedIn) {
if (mobile) {
if (singleColumn) {
redirect = <Redirect from='/' to='/home' exact />;
} else {
redirect = <Redirect from='/' to='/getting-started' exact />;
redirect = <Redirect from='/' to='/deck/getting-started' exact />;
}
} else if (singleUserMode && owner && initialState?.accounts[owner]) {
redirect = <Redirect from='/' to={`/@${initialState.accounts[owner].username}`} exact />;
Expand All @@ -177,10 +178,13 @@ class SwitchingColumnsArea extends PureComponent {
}

return (
<ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}>
<ColumnsAreaContainer ref={this.setRef} singleColumn={singleColumn}>
<WrappedSwitch>
{redirect}

{singleColumn ? <Redirect from='/deck' to='/home' exact /> : null}
{singleColumn && pathName.startsWith('/deck/') ? <Redirect from={pathName} to={pathName.slice(5)} /> : null}

<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
<WrappedRoute path='/about' component={About} content={children} />
Expand Down Expand Up @@ -573,7 +577,7 @@ class UI extends PureComponent {
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
<Header />

<SwitchingColumnsArea location={location} mobile={layout === 'mobile' || layout === 'single-column'}>
<SwitchingColumnsArea location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
{children}
</SwitchingColumnsArea>

Expand Down
12 changes: 10 additions & 2 deletions app/javascript/mastodon/features/ui/util/react_router_helpers.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@ import BundleContainer from '../containers/bundle_container';

// Small wrapper to pass multiColumn to the route components
export class WrappedSwitch extends PureComponent {
static contextTypes = {
router: PropTypes.object,
};

render () {
const { multiColumn, children } = this.props;
const { location } = this.context.router.route;

const decklessLocation = multiColumn && location.pathname.startsWith('/deck')
? {...location, pathname: location.pathname.slice(5)}
: location;

return (
<Switch>
{Children.map(children, child => cloneElement(child, { multiColumn }))}
<Switch location={decklessLocation}>
{Children.map(children, child => child ? cloneElement(child, { multiColumn }) : null)}
</Switch>
);
}
Expand Down
7 changes: 7 additions & 0 deletions app/javascript/mastodon/initial_state.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ const element = document.getElementById('initial-state');
/** @type {InitialState | undefined} */
const initialState = element?.textContent && JSON.parse(element.textContent);

/** @type {string} */
const initialPath = document.querySelector("head meta[name=initialPath]")?.getAttribute("content") ?? '';
/** @type {boolean} */
export const hasMultiColumnPath = initialPath === '/'
|| initialPath === '/getting-started'
|| initialPath.startsWith('/deck');

/**
* @template {keyof InitialStateMeta} K
* @param {K} prop
Expand Down
10 changes: 6 additions & 4 deletions app/javascript/mastodon/is_mobile.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { supportsPassiveEvents } from 'detect-passive-events';

import { forceSingleColumn } from './initial_state';
import { forceSingleColumn, hasMultiColumnPath } from './initial_state';

const LAYOUT_BREAKPOINT = 630;

export const isMobile = (width: number) => width <= LAYOUT_BREAKPOINT;

export const transientSingleColumn = !forceSingleColumn && !hasMultiColumnPath;

export type LayoutType = 'mobile' | 'single-column' | 'multi-column';
export const layoutFromWindow = (): LayoutType => {
if (isMobile(window.innerWidth)) {
return 'mobile';
} else if (forceSingleColumn) {
return 'single-column';
} else {
} else if (!forceSingleColumn && !transientSingleColumn) {
return 'multi-column';
} else {
return 'single-column';
}
};

Expand Down
1 change: 1 addition & 0 deletions app/javascript/mastodon/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@
"mute_modal.hide_notifications": "Hide notifications from this user?",
"mute_modal.indefinite": "Indefinite",
"navigation_bar.about": "About",
"navigation_bar.advanced_interface": "Open in advanced web interface",
"navigation_bar.blocks": "Blocked users",
"navigation_bar.bookmarks": "Bookmarks",
"navigation_bar.community_timeline": "Local timeline",
Expand Down
1 change: 1 addition & 0 deletions app/javascript/mastodon/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@
"mute_modal.hide_notifications": "Masquer les notifications de cette personne ?",
"mute_modal.indefinite": "Indéfinie",
"navigation_bar.about": "À propos",
"navigation_bar.advanced_interface": "Ouvrir dans l’interface avancée",
"navigation_bar.blocks": "Comptes bloqués",
"navigation_bar.bookmarks": "Marque-pages",
"navigation_bar.community_timeline": "Fil public local",
Expand Down
1 change: 1 addition & 0 deletions app/views/shared/_web_app.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
= preload_pack_asset 'features/compose.js', crossorigin: 'anonymous'
= preload_pack_asset 'features/home_timeline.js', crossorigin: 'anonymous'
= preload_pack_asset 'features/notifications.js', crossorigin: 'anonymous'
%meta{ name: 'initialPath', content: request.path }

%meta{ name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key }

Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
/mutes
/followed_tags
/statuses/(*any)
/deck/(*any)
).freeze

root 'home#index'
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"react-overlays": "^5.2.1",
"react-redux": "^8.0.4",
"react-redux-loading-bar": "^5.0.4",
"react-router": "^4.3.1",
"react-router-dom": "^4.1.1",
"react-router-scroll-4": "^1.0.0-beta.1",
"react-select": "^5.7.3",
Expand Down

0 comments on commit bdeb89b

Please sign in to comment.