about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/features/ui
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch/features/ui')
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/audio_modal.js32
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/columns_area.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/media_modal.js83
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/modal_root.js13
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/video_modal.js29
-rw-r--r--app/javascript/flavours/glitch/features/ui/index.js11
6 files changed, 101 insertions, 69 deletions
diff --git a/app/javascript/flavours/glitch/features/ui/components/audio_modal.js b/app/javascript/flavours/glitch/features/ui/components/audio_modal.js
index f9d4bb2f3..fc98cc6af 100644
--- a/app/javascript/flavours/glitch/features/ui/components/audio_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/audio_modal.js
@@ -4,12 +4,10 @@ import PropTypes from 'prop-types';
 import Audio from 'flavours/glitch/features/audio';
 import { connect } from 'react-redux';
 import ImmutablePureComponent from 'react-immutable-pure-component';
-import { FormattedMessage } from 'react-intl';
-import classNames from 'classnames';
-import Icon from 'flavours/glitch/components/icon';
+import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
 
-const mapStateToProps = (state, { status }) => ({
-  account: state.getIn(['accounts', status.get('account')]),
+const mapStateToProps = (state, { statusId }) => ({
+  accountStaticAvatar: state.getIn(['accounts', state.getIn(['statuses', statusId, 'account']), 'avatar_static']),
 });
 
 export default @connect(mapStateToProps)
@@ -17,27 +15,21 @@ class AudioModal extends ImmutablePureComponent {
 
   static propTypes = {
     media: ImmutablePropTypes.map.isRequired,
-    status: ImmutablePropTypes.map,
+    statusId: PropTypes.string.isRequired,
+    accountStaticAvatar: PropTypes.string.isRequired,
     options: PropTypes.shape({
       autoPlay: PropTypes.bool,
     }),
-    account: ImmutablePropTypes.map,
     onClose: PropTypes.func.isRequired,
+    onChangeBackgroundColor: PropTypes.func.isRequired,
   };
 
   static contextTypes = {
     router: PropTypes.object,
   };
 
-  handleStatusClick = e => {
-    if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
-      e.preventDefault();
-      this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
-    }
-  }
-
   render () {
-    const { media, status, account } = this.props;
+    const { media, accountStaticAvatar, statusId, onClose } = this.props;
     const options = this.props.options || {};
 
     return (
@@ -48,7 +40,7 @@ class AudioModal extends ImmutablePureComponent {
             alt={media.get('description')}
             duration={media.getIn(['meta', 'original', 'duration'], 0)}
             height={150}
-            poster={media.get('preview_url') || account.get('avatar_static')}
+            poster={media.get('preview_url') || accountStaticAvatar}
             backgroundColor={media.getIn(['meta', 'colors', 'background'])}
             foregroundColor={media.getIn(['meta', 'colors', 'foreground'])}
             accentColor={media.getIn(['meta', 'colors', 'accent'])}
@@ -56,11 +48,9 @@ class AudioModal extends ImmutablePureComponent {
           />
         </div>
 
-        {status && (
-          <div className={classNames('media-modal__meta')}>
-            <a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
-          </div>
-        )}
+        <div className='media-modal__overlay'>
+          {statusId && <Footer statusId={statusId} withOpenButton onClose={onClose} />}
+        </div>
       </div>
     );
   }
diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js
index 4ea7b48fe..d4e0bedac 100644
--- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js
+++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js
@@ -47,7 +47,7 @@ const componentMap = {
   'DIRECTORY': Directory,
 };
 
-const shouldHideFAB = path => path.match(/^\/statuses\/|^\/search|^\/getting-started/);
+const shouldHideFAB = path => path.match(/^\/statuses\/|^\/search|^\/getting-started|^\/start/);
 
 const messages = defineMessages({
   publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.js b/app/javascript/flavours/glitch/features/ui/components/media_modal.js
index e37df7208..a8cbb837e 100644
--- a/app/javascript/flavours/glitch/features/ui/components/media_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.js
@@ -4,12 +4,14 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import Video from 'flavours/glitch/features/video';
 import classNames from 'classnames';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
 import IconButton from 'flavours/glitch/components/icon_button';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import ImageLoader from './image_loader';
 import Icon from 'flavours/glitch/components/icon';
 import GIFV from 'flavours/glitch/components/gifv';
+import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
+import { getAverageFromBlurhash } from 'flavours/glitch/blurhash';
 
 const messages = defineMessages({
   close: { id: 'lightbox.close', defaultMessage: 'Close' },
@@ -26,10 +28,14 @@ class MediaModal extends ImmutablePureComponent {
 
   static propTypes = {
     media: ImmutablePropTypes.list.isRequired,
-    status: ImmutablePropTypes.map,
+    statusId: PropTypes.string,
     index: PropTypes.number.isRequired,
     onClose: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
+    onChangeBackgroundColor: PropTypes.func.isRequired,
+    currentTime: PropTypes.number,
+    autoPlay: PropTypes.bool,
+    volume: PropTypes.number,
   };
 
   state = {
@@ -64,6 +70,7 @@ class MediaModal extends ImmutablePureComponent {
 
   handleChangeIndex = (e) => {
     const index = Number(e.currentTarget.getAttribute('data-index'));
+
     this.setState({
       index: index % this.props.media.size,
       zoomButtonHidden: true,
@@ -87,10 +94,12 @@ class MediaModal extends ImmutablePureComponent {
 
   componentDidMount () {
     window.addEventListener('keydown', this.handleKeyDown, false);
+    this._sendBackgroundColor();
   }
 
   componentWillUnmount () {
     window.removeEventListener('keydown', this.handleKeyDown);
+    this.props.onChangeBackgroundColor(null);
   }
 
   getIndex () {
@@ -106,30 +115,38 @@ class MediaModal extends ImmutablePureComponent {
   handleStatusClick = e => {
     if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
       e.preventDefault();
-      this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
+      this.context.router.history.push(`/statuses/${this.props.statusId}`);
+    }
+
+    this._sendBackgroundColor();
+  }
+
+  componentDidUpdate (prevProps, prevState) {
+    if (prevState.index !== this.state.index) {
+      this._sendBackgroundColor();
+    }
+  }
+
+  _sendBackgroundColor () {
+    const { media, onChangeBackgroundColor } = this.props;
+    const index = this.getIndex();
+    const blurhash = media.getIn([index, 'blurhash']);
+
+    if (blurhash) {
+      const backgroundColor = getAverageFromBlurhash(blurhash);
+      onChangeBackgroundColor(backgroundColor);
     }
   }
 
   render () {
-    const { media, status, intl, onClose } = this.props;
+    const { media, statusId, intl, onClose } = this.props;
     const { navigationHidden } = this.state;
 
     const index = this.getIndex();
-    let pagination = [];
 
     const leftNav  = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' fixedWidth /></button>;
     const rightNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav  media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' fixedWidth /></button>;
 
-    if (media.size > 1) {
-      pagination = media.map((item, i) => {
-        const classes = ['media-modal__button'];
-        if (i === index) {
-          classes.push('media-modal__button--active');
-        }
-        return (<li className='media-modal__page-dot' key={i}><button tabIndex='0' className={classes.join(' ')} onClick={this.handleChangeIndex} data-index={i}>{i + 1}</button></li>);
-      });
-    }
-
     const content = media.map((image) => {
       const width  = image.getIn(['meta', 'original', 'width']) || null;
       const height = image.getIn(['meta', 'original', 'height']) || null;
@@ -148,7 +165,7 @@ class MediaModal extends ImmutablePureComponent {
           />
         );
       } else if (image.get('type') === 'video') {
-        const { time } = this.props;
+        const { currentTime, autoPlay, volume } = this.props;
 
         return (
           <Video
@@ -157,7 +174,10 @@ class MediaModal extends ImmutablePureComponent {
             src={image.get('url')}
             width={image.get('width')}
             height={image.get('height')}
-            currentTime={time || 0}
+            frameRate={image.getIn(['meta', 'original', 'frame_rate'])}
+            currentTime={currentTime || 0}
+            autoPlay={autoPlay || false}
+            volume={volume || 1}
             onCloseVideo={onClose}
             detailed
             alt={image.get('description')}
@@ -197,13 +217,19 @@ class MediaModal extends ImmutablePureComponent {
       'media-modal__navigation--hidden': navigationHidden,
     });
 
+    let pagination;
+
+    if (media.size > 1) {
+      pagination = media.map((item, i) => (
+        <button key={i} className={classNames('media-modal__page-dot', { active: i === index })} data-index={i} onClick={this.handleChangeIndex}>
+          {i + 1}
+        </button>
+      ));
+    }
+
     return (
       <div className='modal-root__modal media-modal'>
-        <div
-          className='media-modal__closer'
-          role='presentation'
-          onClick={onClose}
-        >
+        <div className='media-modal__closer' role='presentation' onClick={onClose} >
           <ReactSwipeableViews
             style={swipeableViewsStyle}
             containerStyle={containerStyle}
@@ -221,15 +247,10 @@ class MediaModal extends ImmutablePureComponent {
           {leftNav}
           {rightNav}
 
-          {status && (
-            <div className={classNames('media-modal__meta', { 'media-modal__meta--shifted': media.size > 1 })}>
-              <a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
-            </div>
-          )}
-
-          <ul className='media-modal__pagination'>
-            {pagination}
-          </ul>
+          <div className='media-modal__overlay'>
+            {pagination && <ul className='media-modal__pagination'>{pagination}</ul>}
+            {statusId && <Footer statusId={statusId} withOpenButton onClose={onClose} />}
+          </div>
         </div>
       </div>
     );
diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.js b/app/javascript/flavours/glitch/features/ui/components/modal_root.js
index 488daf0cc..0fd70de34 100644
--- a/app/javascript/flavours/glitch/features/ui/components/modal_root.js
+++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.js
@@ -55,6 +55,10 @@ export default class ModalRoot extends React.PureComponent {
     onClose: PropTypes.func.isRequired,
   };
 
+  state = {
+    backgroundColor: null,
+  };
+
   getSnapshotBeforeUpdate () {
     return { visible: !!this.props.type };
   }
@@ -69,6 +73,10 @@ export default class ModalRoot extends React.PureComponent {
     }
   }
 
+  setBackgroundColor = color => {
+    this.setState({ backgroundColor: color });
+  }
+
   renderLoading = modalId => () => {
     return ['MEDIA', 'VIDEO', 'BOOST', 'FAVOURITE', 'DOODLE', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
   }
@@ -81,13 +89,14 @@ export default class ModalRoot extends React.PureComponent {
 
   render () {
     const { type, props, onClose } = this.props;
+    const { backgroundColor } = this.state;
     const visible = !!type;
 
     return (
-      <Base onClose={onClose} noEsc={props ? props.noEsc : false}>
+      <Base backgroundColor={backgroundColor} onClose={onClose} noEsc={props ? props.noEsc : false}>
         {visible && (
           <BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}>
-            {(SpecificComponent) => <SpecificComponent {...props} onClose={onClose} />}
+            {(SpecificComponent) => <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={onClose} />}
           </BundleContainer>
         )}
       </Base>
diff --git a/app/javascript/flavours/glitch/features/ui/components/video_modal.js b/app/javascript/flavours/glitch/features/ui/components/video_modal.js
index b0a4f3f03..6b6e615a6 100644
--- a/app/javascript/flavours/glitch/features/ui/components/video_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/video_modal.js
@@ -3,9 +3,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import Video from 'flavours/glitch/features/video';
 import ImmutablePureComponent from 'react-immutable-pure-component';
-import { FormattedMessage } from 'react-intl';
-import classNames from 'classnames';
-import Icon from 'flavours/glitch/components/icon';
+import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
+import { getAverageFromBlurhash } from 'flavours/glitch/blurhash';
 
 export default class VideoModal extends ImmutablePureComponent {
 
@@ -15,24 +14,28 @@ export default class VideoModal extends ImmutablePureComponent {
 
   static propTypes = {
     media: ImmutablePropTypes.map.isRequired,
-    status: ImmutablePropTypes.map,
+    statusId: PropTypes.string,
     options: PropTypes.shape({
       startTime: PropTypes.number,
       autoPlay: PropTypes.bool,
       defaultVolume: PropTypes.number,
     }),
     onClose: PropTypes.func.isRequired,
+    onChangeBackgroundColor: PropTypes.func.isRequired,
   };
 
-  handleStatusClick = e => {
-    if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
-      e.preventDefault();
-      this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
+  componentDidMount () {
+    const { media, onChangeBackgroundColor, onClose } = this.props;
+
+    const backgroundColor = getAverageFromBlurhash(media.get('blurhash'));
+
+    if (backgroundColor) {
+      onChangeBackgroundColor(backgroundColor);
     }
   }
 
   render () {
-    const { media, status, onClose } = this.props;
+    const { media, statusId, onClose } = this.props;
     const options = this.props.options || {};
 
     return (
@@ -52,11 +55,9 @@ export default class VideoModal extends ImmutablePureComponent {
           />
         </div>
 
-        {status && (
-          <div className={classNames('media-modal__meta')}>
-            <a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
-          </div>
-        )}
+        <div className='media-modal__overlay'>
+          {statusId && <Footer statusId={statusId} withOpenButton onClose={onClose} />}
+        </div>
       </div>
     );
   }
diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js
index 61a34fd2b..1149eb14e 100644
--- a/app/javascript/flavours/glitch/features/ui/index.js
+++ b/app/javascript/flavours/glitch/features/ui/index.js
@@ -50,9 +50,11 @@ import {
   Search,
   GettingStartedMisc,
   Directory,
+  FollowRecommendations,
 } from 'flavours/glitch/util/async-components';
 import { HotKeys } from 'react-hotkeys';
 import { me } from 'flavours/glitch/util/initial_state';
+import { closeOnboarding, INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding';
 import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
 
 // Dummy import, to make sure that <Status /> ends up in the application bundle.
@@ -75,6 +77,7 @@ const mapStateToProps = state => ({
   showFaviconBadge: state.getIn(['local_settings', 'notifications', 'favicon_badge']),
   hicolorPrivacyIcons: state.getIn(['local_settings', 'hicolor_privacy_icons']),
   moved: state.getIn(['accounts', me, 'moved']) && state.getIn(['accounts', state.getIn(['accounts', me, 'moved'])]),
+  firstLaunch: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION,
 });
 
 const keyMap = {
@@ -207,6 +210,7 @@ class SwitchingColumnsArea extends React.PureComponent {
           <WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
           <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
 
+          <WrappedRoute path='/start' component={FollowRecommendations} content={children} />
           <WrappedRoute path='/search' component={Search} content={children} />
           <WrappedRoute path='/directory' component={Directory} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 
@@ -260,6 +264,7 @@ class UI extends React.Component {
     unreadNotifications: PropTypes.number,
     showFaviconBadge: PropTypes.bool,
     moved: PropTypes.map,
+    firstLaunch: PropTypes.bool,
   };
 
   state = {
@@ -378,6 +383,12 @@ class UI extends React.Component {
 
     this.favicon = new Favico({ animation:"none" });
 
+    // On first launch, redirect to the follow recommendations page
+    if (this.props.firstLaunch) {
+      this.context.router.history.replace('/start');
+      this.props.dispatch(closeOnboarding());
+    }
+
     this.props.dispatch(fetchMarkers());
     this.props.dispatch(expandHomeTimeline());
     this.props.dispatch(expandNotifications());