~cytrogen/masto-fe

9bd012b7cb906e47c85add95d0eab562a45ad341 — Stanislas Signoud 2 years ago 0d61985
[Glitch] Change links in multi-column mode so tabs are open in single-column mode

Port 5fad7bd58a9c2541938f63e73a6ebdbfd0045903 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
A app/javascript/flavours/glitch/components/router.tsx => app/javascript/flavours/glitch/components/router.tsx +23 -0
@@ 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>;
};

M app/javascript/flavours/glitch/containers/mastodon.jsx => app/javascript/flavours/glitch/containers/mastodon.jsx +4 -3
@@ 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';



@@ 13,6 13,7 @@ import { checkDeprecatedLocalSettings } from 'flavours/glitch/actions/local_sett
import { hydrateStore } from 'flavours/glitch/actions/store';
import { connectUserStream } from 'flavours/glitch/actions/streaming';
import ErrorBoundary from 'flavours/glitch/components/error_boundary';
import { Router } from 'flavours/glitch/components/router';
import UI from 'flavours/glitch/features/ui';
import initialState, { title as siteTitle } from 'flavours/glitch/initial_state';
import { IntlProvider } from 'flavours/glitch/locales';


@@ 79,11 80,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>

M app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx => app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx +11 -0
@@ 5,6 5,7 @@ import { defineMessages, injectIntl } from 'react-intl';

import NavigationPortal from 'flavours/glitch/components/navigation_portal';
import { timelinePreview, trendsEnabled } from 'flavours/glitch/initial_state';
import { transientSingleColumn } from 'flavours/glitch/is_mobile';
import { preferencesLink } from 'flavours/glitch/utils/backend_links';

import ColumnLink from './column_link';


@@ 27,6 28,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' },
  app_settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' },
});



@@ 52,6 54,15 @@ class NavigationPanel extends Component {

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

        {signedIn && (
          <>
            <ColumnLink transparent to='/home' icon='home' text={intl.formatMessage(messages.home)} />

M app/javascript/flavours/glitch/features/ui/index.jsx => app/javascript/flavours/glitch/features/ui/index.jsx +14 -10
@@ 133,11 133,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 {


@@ 151,9 151,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);
    }
  }



@@ 164,16 164,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 />;


@@ 184,10 185,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} />


@@ 652,7 656,7 @@ class UI extends Component {

          <Header />

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


M app/javascript/flavours/glitch/features/ui/util/react_router_helpers.jsx => app/javascript/flavours/glitch/features/ui/util/react_router_helpers.jsx +13 -5
@@ 1,5 1,5 @@
import PropTypes from 'prop-types';
import * as React from 'react';
import { Component, PureComponent, cloneElement, Children } from 'react';

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';

// 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 () {
    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>
        {React.Children.map(children, child => React.cloneElement(child, { multiColumn }))}
      <Switch location={decklessLocation}>
        {Children.map(children, child => child ? cloneElement(child, { multiColumn }) : null)}
      </Switch>
    );
  }


@@ 32,7 40,7 @@ WrappedSwitch.propTypes = {
// Small Wraper to extract the params from the route and pass
// them to the rendered component, together with the content to
// be rendered inside (the children)
export class WrappedRoute extends React.Component {
export class WrappedRoute extends Component {

  static propTypes = {
    component: PropTypes.func.isRequired,

M app/javascript/flavours/glitch/initial_state.js => app/javascript/flavours/glitch/initial_state.js +7 -0
@@ 88,6 88,13 @@
 * @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
 * @property {Record<string, Account>} accounts

M app/javascript/flavours/glitch/is_mobile.ts => app/javascript/flavours/glitch/is_mobile.ts +6 -4
@@ 1,19 1,21 @@
import { supportsPassiveEvents } from 'detect-passive-events';

import { forceSingleColumn } from 'flavours/glitch/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';
  }
};