[Glitch] Change links in multi-column mode so tabs are open in single-column mode

Port 5fad7bd58a to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
Stanislas Signoud 2023-07-13 17:18:09 +02:00 committed by Claire
parent 0d61985713
commit 9bd012b7cb
7 changed files with 78 additions and 22 deletions

View file

@ -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 'flavours/glitch/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>;
};

View file

@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { Helmet } from 'react-helmet'; 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'; import { Provider as ReduxProvider } from 'react-redux';
@ -13,6 +13,7 @@ import { checkDeprecatedLocalSettings } from 'flavours/glitch/actions/local_sett
import { hydrateStore } from 'flavours/glitch/actions/store'; import { hydrateStore } from 'flavours/glitch/actions/store';
import { connectUserStream } from 'flavours/glitch/actions/streaming'; import { connectUserStream } from 'flavours/glitch/actions/streaming';
import ErrorBoundary from 'flavours/glitch/components/error_boundary'; import ErrorBoundary from 'flavours/glitch/components/error_boundary';
import { Router } from 'flavours/glitch/components/router';
import UI from 'flavours/glitch/features/ui'; import UI from 'flavours/glitch/features/ui';
import initialState, { title as siteTitle } from 'flavours/glitch/initial_state'; import initialState, { title as siteTitle } from 'flavours/glitch/initial_state';
import { IntlProvider } from 'flavours/glitch/locales'; import { IntlProvider } from 'flavours/glitch/locales';
@ -79,11 +80,11 @@ export default class Mastodon extends PureComponent {
<IntlProvider> <IntlProvider>
<ReduxProvider store={store}> <ReduxProvider store={store}>
<ErrorBoundary> <ErrorBoundary>
<BrowserRouter> <Router>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}> <ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} /> <Route path='/' component={UI} />
</ScrollContext> </ScrollContext>
</BrowserRouter> </Router>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} /> <Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary> </ErrorBoundary>

View file

@ -5,6 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import NavigationPortal from 'flavours/glitch/components/navigation_portal'; import NavigationPortal from 'flavours/glitch/components/navigation_portal';
import { timelinePreview, trendsEnabled } from 'flavours/glitch/initial_state'; import { timelinePreview, trendsEnabled } from 'flavours/glitch/initial_state';
import { transientSingleColumn } from 'flavours/glitch/is_mobile';
import { preferencesLink } from 'flavours/glitch/utils/backend_links'; import { preferencesLink } from 'flavours/glitch/utils/backend_links';
import ColumnLink from './column_link'; import ColumnLink from './column_link';
@ -27,6 +28,7 @@ const messages = defineMessages({
followsAndFollowers: { id: 'navigation_bar.follows_and_followers', defaultMessage: 'Follows and followers' }, followsAndFollowers: { id: 'navigation_bar.follows_and_followers', defaultMessage: 'Follows and followers' },
about: { id: 'navigation_bar.about', defaultMessage: 'About' }, about: { id: 'navigation_bar.about', defaultMessage: 'About' },
search: { id: 'navigation_bar.search', defaultMessage: 'Search' }, search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
advancedInterface: { id: 'navigation_bar.advanced_interface', defaultMessage: 'Open in advanced web interface' },
app_settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' }, app_settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' },
}); });
@ -52,6 +54,15 @@ class NavigationPanel extends Component {
return ( return (
<div className='navigation-panel'> <div className='navigation-panel'>
{transientSingleColumn && (
<>
<a href={`/deck${location.pathname}`} className='button button--block'>
{intl.formatMessage(messages.advancedInterface)}
</a>
<hr />
</>
)}
{signedIn && ( {signedIn && (
<> <>
<ColumnLink transparent to='/home' icon='home' text={intl.formatMessage(messages.home)} /> <ColumnLink transparent to='/home' icon='home' text={intl.formatMessage(messages.home)} />

View file

@ -133,11 +133,11 @@ class SwitchingColumnsArea extends PureComponent {
static propTypes = { static propTypes = {
children: PropTypes.node, children: PropTypes.node,
location: PropTypes.object, location: PropTypes.object,
mobile: PropTypes.bool, singleColumn: PropTypes.bool,
}; };
UNSAFE_componentWillMount () { UNSAFE_componentWillMount () {
if (this.props.mobile) { if (this.props.singleColumn) {
document.body.classList.toggle('layout-single-column', true); document.body.classList.toggle('layout-single-column', true);
document.body.classList.toggle('layout-multiple-columns', false); document.body.classList.toggle('layout-multiple-columns', false);
} else { } else {
@ -151,9 +151,9 @@ class SwitchingColumnsArea extends PureComponent {
this.node.handleChildrenContentChange(); this.node.handleChildrenContentChange();
} }
if (prevProps.mobile !== this.props.mobile) { if (prevProps.singleColumn !== this.props.singleColumn) {
document.body.classList.toggle('layout-single-column', this.props.mobile); document.body.classList.toggle('layout-single-column', this.props.singleColumn);
document.body.classList.toggle('layout-multiple-columns', !this.props.mobile); document.body.classList.toggle('layout-multiple-columns', !this.props.singleColumn);
} }
} }
@ -164,16 +164,17 @@ class SwitchingColumnsArea extends PureComponent {
}; };
render () { render () {
const { children, mobile } = this.props; const { children, singleColumn } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.context.identity;
const pathName = this.props.location.pathname;
let redirect; let redirect;
if (signedIn) { if (signedIn) {
if (mobile) { if (singleColumn) {
redirect = <Redirect from='/' to='/home' exact />; redirect = <Redirect from='/' to='/home' exact />;
} else { } else {
redirect = <Redirect from='/' to='/getting-started' exact />; redirect = <Redirect from='/' to='/deck/getting-started' exact />;
} }
} else if (singleUserMode && owner && initialState?.accounts[owner]) { } else if (singleUserMode && owner && initialState?.accounts[owner]) {
redirect = <Redirect from='/' to={`/@${initialState.accounts[owner].username}`} exact />; redirect = <Redirect from='/' to={`/@${initialState.accounts[owner].username}`} exact />;
@ -184,10 +185,13 @@ class SwitchingColumnsArea extends PureComponent {
} }
return ( return (
<ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}> <ColumnsAreaContainer ref={this.setRef} singleColumn={singleColumn}>
<WrappedSwitch> <WrappedSwitch>
{redirect} {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='/getting-started' component={GettingStarted} content={children} />
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} /> <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
<WrappedRoute path='/about' component={About} content={children} /> <WrappedRoute path='/about' component={About} content={children} />
@ -652,7 +656,7 @@ class UI extends Component {
<Header /> <Header />
<SwitchingColumnsArea location={location} mobile={layout === 'mobile' || layout === 'single-column'}> <SwitchingColumnsArea location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
{children} {children}
</SwitchingColumnsArea> </SwitchingColumnsArea>

View file

@ -1,5 +1,5 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as React from 'react'; import { Component, PureComponent, cloneElement, Children } from 'react';
import { Switch, Route } from 'react-router-dom'; import { Switch, Route } from 'react-router-dom';
@ -10,14 +10,22 @@ import ColumnLoading from 'flavours/glitch/features/ui/components/column_loading
import BundleContainer from 'flavours/glitch/features/ui/containers/bundle_container'; import BundleContainer from 'flavours/glitch/features/ui/containers/bundle_container';
// Small wrapper to pass multiColumn to the route components // Small wrapper to pass multiColumn to the route components
export class WrappedSwitch extends React.PureComponent { export class WrappedSwitch extends PureComponent {
static contextTypes = {
router: PropTypes.object,
};
render () { render () {
const { multiColumn, children } = this.props; 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 ( return (
<Switch> <Switch location={decklessLocation}>
{React.Children.map(children, child => React.cloneElement(child, { multiColumn }))} {Children.map(children, child => child ? cloneElement(child, { multiColumn }) : null)}
</Switch> </Switch>
); );
} }
@ -32,7 +40,7 @@ WrappedSwitch.propTypes = {
// Small Wraper to extract the params from the route and pass // Small Wraper to extract the params from the route and pass
// them to the rendered component, together with the content to // them to the rendered component, together with the content to
// be rendered inside (the children) // be rendered inside (the children)
export class WrappedRoute extends React.Component { export class WrappedRoute extends Component {
static propTypes = { static propTypes = {
component: PropTypes.func.isRequired, component: PropTypes.func.isRequired,

View file

@ -88,6 +88,13 @@
* @property {string} default_content_type * @property {string} default_content_type
*/ */
/** @type {string} */
const initialPath = document.querySelector("head meta[name=initialPath]")?.getAttribute("content") ?? '';
/** @type {boolean} */
export const hasMultiColumnPath = initialPath === '/'
|| initialPath === '/getting-started'
|| initialPath.startsWith('/deck');
/** /**
* @typedef InitialState * @typedef InitialState
* @property {Record<string, Account>} accounts * @property {Record<string, Account>} accounts

View file

@ -1,19 +1,21 @@
import { supportsPassiveEvents } from 'detect-passive-events'; import { supportsPassiveEvents } from 'detect-passive-events';
import { forceSingleColumn } from 'flavours/glitch/initial_state'; import { forceSingleColumn, hasMultiColumnPath } from './initial_state';
const LAYOUT_BREAKPOINT = 630; const LAYOUT_BREAKPOINT = 630;
export const isMobile = (width: number) => width <= LAYOUT_BREAKPOINT; export const isMobile = (width: number) => width <= LAYOUT_BREAKPOINT;
export const transientSingleColumn = !forceSingleColumn && !hasMultiColumnPath;
export type LayoutType = 'mobile' | 'single-column' | 'multi-column'; export type LayoutType = 'mobile' | 'single-column' | 'multi-column';
export const layoutFromWindow = (): LayoutType => { export const layoutFromWindow = (): LayoutType => {
if (isMobile(window.innerWidth)) { if (isMobile(window.innerWidth)) {
return 'mobile'; return 'mobile';
} else if (forceSingleColumn) { } else if (!forceSingleColumn && !transientSingleColumn) {
return 'single-column';
} else {
return 'multi-column'; return 'multi-column';
} else {
return 'single-column';
} }
}; };