about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/features
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch/features')
-rw-r--r--app/javascript/flavours/glitch/features/account_gallery/index.js9
-rw-r--r--app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js28
-rw-r--r--app/javascript/flavours/glitch/features/status/index.js8
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/media_modal.js174
-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.js20
-rw-r--r--app/javascript/flavours/glitch/features/video/index.js5
7 files changed, 193 insertions, 64 deletions
diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.js
index 81203e3f8..2a43d1ed2 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/index.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/index.js
@@ -114,15 +114,18 @@ class AccountGallery extends ImmutablePureComponent {
   }
 
   handleOpenMedia = attachment => {
+    const { dispatch } = this.props;
+    const statusId = attachment.getIn(['status', 'id']);
+
     if (attachment.get('type') === 'video') {
-      this.props.dispatch(openModal('VIDEO', { media: attachment, status: attachment.get('status'), options: { autoPlay: true } }));
+      dispatch(openModal('VIDEO', { media: attachment, statusId, options: { autoPlay: true } }));
     } else if (attachment.get('type') === 'audio') {
-      this.props.dispatch(openModal('AUDIO', { media: attachment, status: attachment.get('status'), options: { autoPlay: true } }));
+      dispatch(openModal('AUDIO', { media: attachment, statusId, options: { autoPlay: true } }));
     } else {
       const media = attachment.getIn(['status', 'media_attachments']);
       const index = media.findIndex(x => x.get('id') === attachment.get('id'));
 
-      this.props.dispatch(openModal('MEDIA', { media, index, status: attachment.get('status') }));
+      dispatch(openModal('MEDIA', { media, index, statusId }));
     }
   }
 
diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js
index d8989ec61..fcb2df527 100644
--- a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js
+++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js
@@ -23,6 +23,7 @@ const messages = defineMessages({
   favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
   replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
   replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
+  open: { id: 'status.open', defaultMessage: 'Expand this status' },
 });
 
 const makeMapStateToProps = () => {
@@ -52,11 +53,19 @@ class Footer extends ImmutablePureComponent {
     dispatch: PropTypes.func.isRequired,
     askReplyConfirmation: PropTypes.bool,
     showReplyCount: PropTypes.bool,
+    withOpenButton: PropTypes.bool,
+    onClose: PropTypes.func,
   };
 
   _performReply = () => {
-    const { dispatch, status } = this.props;
-    dispatch(replyCompose(status, this.context.router.history));
+    const { dispatch, status, onClose } = this.props;
+    const { router } = this.context;
+
+    if (onClose) {
+      onClose();
+    }
+
+    dispatch(replyCompose(status, router.history));
   };
 
   handleReplyClick = () => {
@@ -100,8 +109,20 @@ class Footer extends ImmutablePureComponent {
     }
   };
 
+  handleOpenClick = e => {
+    const { router } = this.context;
+
+    if (e.button !== 0 || !router) {
+      return;
+    }
+
+    const { status } = this.props;
+
+    router.history.push(`/statuses/${status.get('id')}`);
+  }
+
   render () {
-    const { status, intl, showReplyCount } = this.props;
+    const { status, intl, showReplyCount, withOpenButton } = this.props;
 
     const publicStatus  = ['public', 'unlisted'].includes(status.get('visibility'));
     const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
@@ -156,6 +177,7 @@ class Footer extends ImmutablePureComponent {
         {replyButton}
         <IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate}  active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
         <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
+        {withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' onClick={this.handleOpenClick} />}
       </div>
     );
   }
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index 21e441407..513a6227f 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -316,11 +316,11 @@ class Status extends ImmutablePureComponent {
   }
 
   handleOpenMedia = (media, index) => {
-    this.props.dispatch(openModal('MEDIA', { media, index }));
+    this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index }));
   }
 
   handleOpenVideo = (media, options) => {
-    this.props.dispatch(openModal('VIDEO', { media, options }));
+    this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, options }));
   }
 
   handleHotkeyOpenMedia = e => {
@@ -329,9 +329,7 @@ class Status extends ImmutablePureComponent {
     e.preventDefault();
 
     if (status.get('media_attachments').size > 0) {
-      if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
-        // TODO: toggle play/paused?
-      } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
+      if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
         this.handleOpenVideo(status.getIn(['media_attachments', 0]), { startTime: 0 });
       } else {
         this.handleOpenMedia(status.get('media_attachments'), 0);
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..7b07c557f 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,13 @@ 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';
 
 const messages = defineMessages({
   close: { id: 'lightbox.close', defaultMessage: 'Close' },
@@ -17,6 +18,111 @@ const messages = defineMessages({
   next: { id: 'lightbox.next', defaultMessage: 'Next' },
 });
 
+const digitCharacters = [
+  '0',
+  '1',
+  '2',
+  '3',
+  '4',
+  '5',
+  '6',
+  '7',
+  '8',
+  '9',
+  'A',
+  'B',
+  'C',
+  'D',
+  'E',
+  'F',
+  'G',
+  'H',
+  'I',
+  'J',
+  'K',
+  'L',
+  'M',
+  'N',
+  'O',
+  'P',
+  'Q',
+  'R',
+  'S',
+  'T',
+  'U',
+  'V',
+  'W',
+  'X',
+  'Y',
+  'Z',
+  'a',
+  'b',
+  'c',
+  'd',
+  'e',
+  'f',
+  'g',
+  'h',
+  'i',
+  'j',
+  'k',
+  'l',
+  'm',
+  'n',
+  'o',
+  'p',
+  'q',
+  'r',
+  's',
+  't',
+  'u',
+  'v',
+  'w',
+  'x',
+  'y',
+  'z',
+  '#',
+  '$',
+  '%',
+  '*',
+  '+',
+  ',',
+  '-',
+  '.',
+  ':',
+  ';',
+  '=',
+  '?',
+  '@',
+  '[',
+  ']',
+  '^',
+  '_',
+  '{',
+  '|',
+  '}',
+  '~',
+];
+
+const decode83 = (str) => {
+  let value = 0;
+  let c, digit;
+
+  for (let i = 0; i < str.length; i++) {
+    c = str[i];
+    digit = digitCharacters.indexOf(c);
+    value = value * 83 + digit;
+  }
+
+  return value;
+};
+
+const decodeRGB = int => ({
+  r: Math.max(0, (int >> 16)),
+  g: Math.max(0, (int >> 8) & 255),
+  b: Math.max(0, (int & 255)),
+});
+
 export default @injectIntl
 class MediaModal extends ImmutablePureComponent {
 
@@ -26,10 +132,11 @@ 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,
   };
 
   state = {
@@ -64,6 +171,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 +195,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 +216,35 @@ 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 backgroundColor = decodeRGB(decode83(media.getIn([index, 'blurhash']).slice(2, 6)));
+
+    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;
@@ -197,13 +312,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 +342,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..62273f252 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,6 @@ 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';
 
 export default class VideoModal extends ImmutablePureComponent {
 
@@ -15,7 +12,7 @@ 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,
@@ -24,15 +21,8 @@ export default class VideoModal extends ImmutablePureComponent {
     onClose: 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')}`);
-    }
-  }
-
   render () {
-    const { media, status, onClose } = this.props;
+    const { media, onClose } = this.props;
     const options = this.props.options || {};
 
     return (
@@ -51,12 +41,6 @@ export default class VideoModal extends ImmutablePureComponent {
             alt={media.get('description')}
           />
         </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>
     );
   }
diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js
index a81311c67..0d267c3a1 100644
--- a/app/javascript/flavours/glitch/features/video/index.js
+++ b/app/javascript/flavours/glitch/features/video/index.js
@@ -120,7 +120,6 @@ class Video extends React.PureComponent {
     deployPictureInPicture: PropTypes.func,
     preventPlayback: PropTypes.bool,
     blurhash: PropTypes.string,
-    link: PropTypes.node,
     autoPlay: PropTypes.bool,
     volume: PropTypes.number,
     muted: PropTypes.bool,
@@ -548,7 +547,7 @@ class Video extends React.PureComponent {
   }
 
   render () {
-    const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive, link, editable, blurhash } = this.props;
+    const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive, editable, blurhash } = this.props;
     const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
     const progress = Math.min((currentTime / duration) * 100, 100);
     const playerStyle = {};
@@ -666,8 +665,6 @@ class Video extends React.PureComponent {
                   <span className='video-player__time-total'>{formatTime(Math.floor(duration))}</span>
                 </span>
               )}
-
-              {link && <span className='video-player__link'>{link}</span>}
             </div>
 
             <div className='video-player__buttons right'>