Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change links in multi-column mode so tabs are open in single-column mode #25893

Merged
merged 2 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 />;
Signez marked this conversation as resolved.
Show resolved Hide resolved
}
} 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