about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/javascript/flavours/glitch/actions/app.js6
-rw-r--r--app/javascript/flavours/glitch/components/status.js15
-rw-r--r--app/javascript/flavours/glitch/containers/status_container.js16
-rw-r--r--app/javascript/flavours/glitch/features/ui/index.js114
-rw-r--r--app/javascript/flavours/glitch/reducers/meta.js11
-rw-r--r--app/javascript/flavours/glitch/util/is_mobile.js34
6 files changed, 110 insertions, 86 deletions
diff --git a/app/javascript/flavours/glitch/actions/app.js b/app/javascript/flavours/glitch/actions/app.js
new file mode 100644
index 000000000..de2d93e29
--- /dev/null
+++ b/app/javascript/flavours/glitch/actions/app.js
@@ -0,0 +1,6 @@
+export const APP_LAYOUT_CHANGE = 'APP_LAYOUT_CHANGE';
+
+export const changeLayout = layout => ({
+  type: APP_LAYOUT_CHANGE,
+  layout,
+});
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js
index e238456c5..d3b6bd7e9 100644
--- a/app/javascript/flavours/glitch/components/status.js
+++ b/app/javascript/flavours/glitch/components/status.js
@@ -99,8 +99,11 @@ class Status extends ImmutablePureComponent {
     onClick: PropTypes.func,
     scrollKey: PropTypes.string,
     deployPictureInPicture: PropTypes.func,
-    usingPiP: PropTypes.bool,
     settings: ImmutablePropTypes.map.isRequired,
+    pictureInPicture: PropTypes.shape({
+      inUse: PropTypes.bool,
+      available: PropTypes.bool,
+    }),
   };
 
   state = {
@@ -126,7 +129,7 @@ class Status extends ImmutablePureComponent {
     'hidden',
     'expanded',
     'unread',
-    'usingPiP',
+    'pictureInPicture',
   ]
 
   updateOnStates = [
@@ -503,7 +506,7 @@ class Status extends ImmutablePureComponent {
       hidden,
       unread,
       featured,
-      usingPiP,
+      pictureInPicture,
       ...other
     } = this.props;
     const { isCollapsed, forceFilter } = this.state;
@@ -595,7 +598,7 @@ class Status extends ImmutablePureComponent {
 
     attachments = status.get('media_attachments');
 
-    if (usingPiP) {
+    if (pictureInPicture.inUse) {
       media.push(<PictureInPicturePlaceholder width={this.props.cachedMediaWidth} />);
       mediaIcons.push('video-camera');
     } else if (attachments.size > 0) {
@@ -623,7 +626,7 @@ class Status extends ImmutablePureComponent {
                 width={this.props.cachedMediaWidth}
                 height={110}
                 cacheWidth={this.props.cacheMediaWidth}
-                deployPictureInPicture={this.handleDeployPictureInPicture}
+                deployPictureInPicture={pictureInPicture.available ? this.handleDeployPictureInPicture : undefined}
                 sensitive={status.get('sensitive')}
                 blurhash={attachment.get('blurhash')}
                 visible={this.state.showMedia}
@@ -652,7 +655,7 @@ class Status extends ImmutablePureComponent {
               onOpenVideo={this.handleOpenVideo}
               width={this.props.cachedMediaWidth}
               cacheWidth={this.props.cacheMediaWidth}
-              deployPictureInPicture={this.handleDeployPictureInPicture}
+              deployPictureInPicture={pictureInPicture.available ? this.handleDeployPictureInPicture : undefined}
               visible={this.state.showMedia}
               onToggleVisibility={this.handleToggleMediaVisibility}
             />)}
diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js
index 0ba2e712c..370308043 100644
--- a/app/javascript/flavours/glitch/containers/status_container.js
+++ b/app/javascript/flavours/glitch/containers/status_container.js
@@ -76,12 +76,16 @@ const makeMapStateToProps = () => {
     }
 
     return {
-      containerId : props.containerId || props.id,  //  Should match reblogStatus's id for reblogs
-      status      : status,
-      account     : account || props.account,
-      settings    : state.get('local_settings'),
-      prepend     : prepend || props.prepend,
-      usingPiP    : state.get('picture_in_picture').statusId === props.id,
+      containerId: props.containerId || props.id,  //  Should match reblogStatus's id for reblogs
+      status: status,
+      account: account || props.account,
+      settings: state.get('local_settings'),
+      prepend: prepend || props.prepend,
+
+      pictureInPicture: {
+        inUse: state.getIn(['meta', 'layout']) !== 'mobile' && state.get('picture_in_picture').statusId === props.id,
+        available: state.getIn(['meta', 'layout']) !== 'mobile',
+      },
     };
   };
 
diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js
index 2be6d9478..c861f5568 100644
--- a/app/javascript/flavours/glitch/features/ui/index.js
+++ b/app/javascript/flavours/glitch/features/ui/index.js
@@ -5,13 +5,14 @@ import LoadingBarContainer from './containers/loading_bar_container';
 import ModalContainer from './containers/modal_container';
 import { connect } from 'react-redux';
 import { Redirect, withRouter } from 'react-router-dom';
-import { isMobile } from 'flavours/glitch/util/is_mobile';
+import { layoutFromWindow } from 'flavours/glitch/util/is_mobile';
 import { debounce } from 'lodash';
 import { uploadCompose, resetCompose, changeComposeSpoilerness } from 'flavours/glitch/actions/compose';
 import { expandHomeTimeline } from 'flavours/glitch/actions/timelines';
 import { expandNotifications, notificationsSetVisibility } from 'flavours/glitch/actions/notifications';
 import { fetchRules } from 'flavours/glitch/actions/rules';
 import { clearHeight } from 'flavours/glitch/actions/height_cache';
+import { changeLayout } from 'flavours/glitch/actions/app';
 import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavours/glitch/actions/markers';
 import { WrappedSwitch, WrappedRoute } from 'flavours/glitch/util/react_router_helpers';
 import UploadArea from './components/upload_area';
@@ -66,10 +67,12 @@ const messages = defineMessages({
 });
 
 const mapStateToProps = state => ({
+  layout: state.getIn(['meta', 'layout']),
   hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
   hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
   canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4,
-  layout: state.getIn(['local_settings', 'layout']),
+  layout: state.getIn(['meta', 'layout']),
+  layout_local_setting: state.getIn(['local_settings', 'layout']),
   isWide: state.getIn(['local_settings', 'stretch']),
   navbarUnder: state.getIn(['local_settings', 'navbar_under']),
   dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
@@ -120,26 +123,13 @@ class SwitchingColumnsArea extends React.PureComponent {
 
   static propTypes = {
     children: PropTypes.node,
-    layout: PropTypes.string,
     location: PropTypes.object,
     navbarUnder: PropTypes.bool,
-    onLayoutChange: PropTypes.func.isRequired,
+    mobile: PropTypes.bool,
   };
 
-  state = {
-    mobile: isMobile(window.innerWidth, this.props.layout),
-  };
-
-  componentWillReceiveProps (nextProps) {
-    if (nextProps.layout !== this.props.layout) {
-      this.setState({ mobile: isMobile(window.innerWidth, nextProps.layout) });
-    }
-  }
-
   componentWillMount () {
-    window.addEventListener('resize', this.handleResize, { passive: true });
-
-    if (this.state.mobile) {
+    if (this.props.mobile) {
       document.body.classList.toggle('layout-single-column', true);
       document.body.classList.toggle('layout-multiple-columns', false);
     } else {
@@ -148,37 +138,14 @@ class SwitchingColumnsArea extends React.PureComponent {
     }
   }
 
-  componentDidUpdate (prevProps, prevState) {
+  componentDidUpdate (prevProps) {
     if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
       this.node.handleChildrenContentChange();
     }
 
-    if (prevState.mobile !== this.state.mobile) {
-      document.body.classList.toggle('layout-single-column', this.state.mobile);
-      document.body.classList.toggle('layout-multiple-columns', !this.state.mobile);
-    }
-  }
-
-  componentWillUnmount () {
-    window.removeEventListener('resize', this.handleResize);
-  }
-
-  handleLayoutChange = debounce(() => {
-    // The cached heights are no longer accurate, invalidate
-    this.props.onLayoutChange();
-  }, 500, {
-    trailing: true,
-  })
-
-  handleResize = () => {
-    const mobile = isMobile(window.innerWidth, this.props.layout);
-
-    if (mobile !== this.state.mobile) {
-      this.handleLayoutChange.cancel();
-      this.props.onLayoutChange();
-      this.setState({ mobile });
-    } else {
-      this.handleLayoutChange();
+    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);
     }
   }
 
@@ -189,12 +156,11 @@ class SwitchingColumnsArea extends React.PureComponent {
   }
 
   render () {
-    const { children, navbarUnder } = this.props;
-    const singleColumn = this.state.mobile;
-    const redirect = singleColumn ? <Redirect from='/' to='/home' exact /> : <Redirect from='/' to='/getting-started' exact />;
+    const { children, mobile, navbarUnder } = this.props;
+    const redirect = mobile ? <Redirect from='/' to='/home' exact /> : <Redirect from='/' to='/getting-started' exact />;
 
     return (
-      <ColumnsAreaContainer ref={this.setRef} singleColumn={singleColumn} navbarUnder={navbarUnder}>
+      <ColumnsAreaContainer ref={this.setRef} singleColumn={mobile} navbarUnder={navbarUnder}>
         <WrappedSwitch>
           {redirect}
           <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
@@ -256,7 +222,7 @@ class UI extends React.Component {
   static propTypes = {
     dispatch: PropTypes.func.isRequired,
     children: PropTypes.node,
-    layout: PropTypes.string,
+    layout_local_setting: PropTypes.string,
     isWide: PropTypes.bool,
     systemFontUi: PropTypes.bool,
     navbarUnder: PropTypes.bool,
@@ -272,6 +238,7 @@ class UI extends React.Component {
     unreadNotifications: PropTypes.number,
     showFaviconBadge: PropTypes.bool,
     moved: PropTypes.map,
+    layout: PropTypes.string.isRequired,
     firstLaunch: PropTypes.bool,
     username: PropTypes.string,
   };
@@ -293,11 +260,6 @@ class UI extends React.Component {
     }
   }
 
-  handleLayoutChange = () => {
-    // The cached heights are no longer accurate, invalidate
-    this.props.dispatch(clearHeight());
-  }
-
   handleDragEnter = (e) => {
     e.preventDefault();
 
@@ -378,8 +340,27 @@ class UI extends React.Component {
     }
   }
 
-  componentWillMount () {
+  handleLayoutChange = debounce(() => {
+    this.props.dispatch(clearHeight()); // The cached heights are no longer accurate, invalidate
+  }, 500, {
+    trailing: true,
+  });
+
+  handleResize = () => {
+    const layout = layoutFromWindow(this.props.layout_local_setting);
+
+    if (layout !== this.props.layout) {
+      this.handleLayoutChange.cancel();
+      this.props.dispatch(changeLayout(layout));
+    } else {
+      this.handleLayoutChange();
+    }
+  }
+
+  componentDidMount () {
     window.addEventListener('beforeunload', this.handleBeforeUnload, false);
+    window.addEventListener('resize', this.handleResize, { passive: true });
+
     document.addEventListener('dragenter', this.handleDragEnter, false);
     document.addEventListener('dragover', this.handleDragOver, false);
     document.addEventListener('drop', this.handleDrop, false);
@@ -403,9 +384,7 @@ class UI extends React.Component {
     this.props.dispatch(expandNotifications());
 
     setTimeout(() => this.props.dispatch(fetchRules()), 3000);
-  }
 
-  componentDidMount () {
     this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
       return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName);
     };
@@ -427,6 +406,19 @@ class UI extends React.Component {
     }
   }
 
+  componentWillReceiveProps (nextProps) {
+    if (nextProps.layout_local_setting !== this.props.layout_local_setting) {
+      const layout = layoutFromWindow(nextProps.layout_local_setting);
+
+      if (layout !== this.props.layout) {
+        this.handleLayoutChange.cancel();
+        this.props.dispatch(changeLayout(layout));
+      } else {
+        this.handleLayoutChange();
+      }
+    }
+  }
+
   componentDidUpdate (prevProps) {
     if (this.props.unreadNotifications != prevProps.unreadNotifications ||
         this.props.showFaviconBadge != prevProps.showFaviconBadge) {
@@ -446,6 +438,8 @@ class UI extends React.Component {
     }
 
     window.removeEventListener('beforeunload', this.handleBeforeUnload);
+    window.removeEventListener('resize', this.handleResize);
+
     document.removeEventListener('dragenter', this.handleDragEnter);
     document.removeEventListener('dragover', this.handleDragOver);
     document.removeEventListener('drop', this.handleDrop);
@@ -576,7 +570,7 @@ class UI extends React.Component {
 
   render () {
     const { draggingOver } = this.state;
-    const { children, layout, isWide, navbarUnder, location, dropdownMenuIsOpen, moved } = this.props;
+    const { children, isWide, navbarUnder, location, dropdownMenuIsOpen, layout, moved } = this.props;
 
     const columnsClass = layout => {
       switch (layout) {
@@ -632,11 +626,11 @@ class UI extends React.Component {
               )}}
             />
           </div>)}
-          <SwitchingColumnsArea location={location} layout={layout} navbarUnder={navbarUnder} onLayoutChange={this.handleLayoutChange}>
+          <SwitchingColumnsArea location={location} mobile={layout === 'mobile' || layout === 'single-column'} navbarUnder={navbarUnder}>
             {children}
           </SwitchingColumnsArea>
 
-          <PictureInPicture />
+          {layout !== 'mobile' && <PictureInPicture />}
           <NotificationsContainer />
           <LoadingBarContainer className='loading-bar' />
           <ModalContainer />
diff --git a/app/javascript/flavours/glitch/reducers/meta.js b/app/javascript/flavours/glitch/reducers/meta.js
index 0f3ab3b84..0364ec289 100644
--- a/app/javascript/flavours/glitch/reducers/meta.js
+++ b/app/javascript/flavours/glitch/reducers/meta.js
@@ -1,16 +1,25 @@
 import { STORE_HYDRATE } from 'flavours/glitch/actions/store';
+import { APP_LAYOUT_CHANGE } from 'flavours/glitch/actions/app';
 import { Map as ImmutableMap } from 'immutable';
+import { layoutFromWindow } from 'flavours/glitch/util/is_mobile';
 
 const initialState = ImmutableMap({
   streaming_api_base_url: null,
   access_token: null,
+  layout: layoutFromWindow(),
   permissions: '0',
 });
 
 export default function meta(state = initialState, action) {
   switch(action.type) {
   case STORE_HYDRATE:
-    return state.merge(action.state.get('meta')).set('permissions', action.state.getIn(['role', 'permissions']));
+    return state.merge(
+      action.state.get('meta'))
+        .set('permissions', action.state.getIn(['role', 'permissions']))
+        .set('layout', layoutFromWindow(action.state.getIn(['local_settings', 'layout']))
+      );
+  case APP_LAYOUT_CHANGE:
+    return state.set('layout', action.layout);
   default:
     return state;
   }
diff --git a/app/javascript/flavours/glitch/util/is_mobile.js b/app/javascript/flavours/glitch/util/is_mobile.js
index 7e584e8fa..c8517f592 100644
--- a/app/javascript/flavours/glitch/util/is_mobile.js
+++ b/app/javascript/flavours/glitch/util/is_mobile.js
@@ -3,14 +3,26 @@ import { forceSingleColumn } from 'flavours/glitch/util/initial_state';
 
 const LAYOUT_BREAKPOINT = 630;
 
-export function isMobile(width, columns) {
-  switch (columns) {
+export const isMobile = width => width <= LAYOUT_BREAKPOINT;
+
+export const layoutFromWindow = (layout_local_setting) => {
+  switch (layout_local_setting) {
   case 'multiple':
-    return false;
+    return 'multi-column';
   case 'single':
-    return true;
+    if (isMobile(window.innerWidth)) {
+      return 'mobile';
+    } else {
+      return 'single-column';
+    }
   default:
-    return forceSingleColumn || width <= LAYOUT_BREAKPOINT;
+    if (isMobile(window.innerWidth)) {
+      return 'mobile';
+    } else if (forceSingleColumn) {
+      return 'single-column';
+    } else {
+      return 'multi-column';
+    }
   }
 };
 
@@ -19,17 +31,13 @@ const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
 let userTouching = false;
 let listenerOptions = supportsPassiveEvents ? { passive: true } : false;
 
-function touchListener() {
+const touchListener = () => {
   userTouching = true;
   window.removeEventListener('touchstart', touchListener, listenerOptions);
-}
+};
 
 window.addEventListener('touchstart', touchListener, listenerOptions);
 
-export function isUserTouching() {
-  return userTouching;
-}
+export const isUserTouching = () => userTouching;
 
-export function isIOS() {
-  return iOS;
-};
+export const isIOS = () => iOS;