about summary refs log tree commit diff
path: root/app/javascript/mastodon/components/media_gallery.js
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2018-02-22 00:35:46 +0100
committerGitHub <noreply@github.com>2018-02-22 00:35:46 +0100
commit90f12f2e5a41115a9a756f9dd38054736080d4f9 (patch)
tree72af04d32879a08a620e306c3c15e1e2f7985f37 /app/javascript/mastodon/components/media_gallery.js
parentd3a62d263703142250d7d59335394a8e2a599ed4 (diff)
Focal points (#6520)
* Add focus param to media API, center thumbnails on focus point

* Add UI for setting a focal point

* Improve focal point icon on upload item

* Use focal point in upload preview

* Add focalPoint property to ActivityPub

* Don't show focal point button for non-image attachments
Diffstat (limited to 'app/javascript/mastodon/components/media_gallery.js')
-rw-r--r--app/javascript/mastodon/components/media_gallery.js71
1 files changed, 62 insertions, 9 deletions
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index a3ffc45ea..9e1bb77c2 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -12,6 +12,26 @@ const messages = defineMessages({
   toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
 });
 
+const shiftToPoint = (containerToImageRatio, containerSize, imageSize, focusSize, toMinus) => {
+  const containerCenter = Math.floor(containerSize / 2);
+  const focusFactor     = (focusSize + 1) / 2;
+  const scaledImage     = Math.floor(imageSize / containerToImageRatio);
+
+  let focus = Math.floor(focusFactor * scaledImage);
+
+  if (toMinus) focus = scaledImage - focus;
+
+  let focusOffset = focus - containerCenter;
+
+  const remainder = scaledImage - focus;
+  const containerRemainder = containerSize - containerCenter;
+
+  if (remainder < containerRemainder) focusOffset -= containerRemainder - remainder;
+  if (focusOffset < 0) focusOffset = 0;
+
+  return (focusOffset * -100 / containerSize) + '%';
+};
+
 class Item extends React.PureComponent {
 
   static contextTypes = {
@@ -24,6 +44,8 @@ class Item extends React.PureComponent {
     index: PropTypes.number.isRequired,
     size: PropTypes.number.isRequired,
     onClick: PropTypes.func.isRequired,
+    containerWidth: PropTypes.number,
+    containerHeight: PropTypes.number,
   };
 
   static defaultProps = {
@@ -62,7 +84,7 @@ class Item extends React.PureComponent {
   }
 
   render () {
-    const { attachment, index, size, standalone } = this.props;
+    const { attachment, index, size, standalone, containerWidth, containerHeight } = this.props;
 
     let width  = 50;
     let height = 100;
@@ -116,16 +138,40 @@ class Item extends React.PureComponent {
     let thumbnail = '';
 
     if (attachment.get('type') === 'image') {
-      const previewUrl = attachment.get('preview_url');
+      const previewUrl   = attachment.get('preview_url');
       const previewWidth = attachment.getIn(['meta', 'small', 'width']);
 
-      const originalUrl = attachment.get('url');
-      const originalWidth = attachment.getIn(['meta', 'original', 'width']);
+      const originalUrl    = attachment.get('url');
+      const originalWidth  = attachment.getIn(['meta', 'original', 'width']);
+      const originalHeight = attachment.getIn(['meta', 'original', 'height']);
 
       const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';
 
       const srcSet = hasSize ? `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w` : null;
-      const sizes = hasSize ? `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw` : null;
+      const sizes  = hasSize ? `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw` : null;
+
+      const focusX     = attachment.getIn(['meta', 'focus', 'x']);
+      const focusY     = attachment.getIn(['meta', 'focus', 'y']);
+      const imageStyle = {};
+
+      if (originalWidth && originalHeight && containerWidth && containerHeight && focusX && focusY) {
+        const widthRatio  = originalWidth / (containerWidth * (width / 100));
+        const heightRatio = originalHeight / (containerHeight * (height / 100));
+
+        let hShift = 0;
+        let vShift = 0;
+
+        if (widthRatio > heightRatio) {
+          hShift = shiftToPoint(heightRatio, (containerWidth * (width / 100)), originalWidth, focusX);
+        } else if(widthRatio < heightRatio) {
+          vShift = shiftToPoint(widthRatio, (containerHeight * (height / 100)), originalHeight, focusY, true);
+        }
+
+        imageStyle.top  = vShift;
+        imageStyle.left = hShift;
+      } else {
+        imageStyle.height = '100%';
+      }
 
       thumbnail = (
         <a
@@ -134,7 +180,14 @@ class Item extends React.PureComponent {
           onClick={this.handleClick}
           target='_blank'
         >
-          <img src={previewUrl} srcSet={srcSet} sizes={sizes} alt={attachment.get('description')} title={attachment.get('description')} />
+          <img
+            src={previewUrl}
+            srcSet={srcSet}
+            sizes={sizes}
+            alt={attachment.get('description')}
+            title={attachment.get('description')}
+            style={imageStyle}
+          />
         </a>
       );
     } else if (attachment.get('type') === 'gifv') {
@@ -205,7 +258,7 @@ export default class MediaGallery extends React.PureComponent {
   }
 
   handleRef = (node) => {
-    if (node && this.isStandaloneEligible()) {
+    if (node /*&& this.isStandaloneEligible()*/) {
       // offsetWidth triggers a layout, so only calculate when we need to
       this.setState({
         width: node.offsetWidth,
@@ -256,12 +309,12 @@ export default class MediaGallery extends React.PureComponent {
       if (this.isStandaloneEligible()) {
         children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} />;
       } else {
-        children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} />);
+        children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} containerWidth={width} containerHeight={height} />);
       }
     }
 
     return (
-      <div className='media-gallery' style={style}>
+      <div className='media-gallery' style={style} ref={this.handleRef}>
         <div className={classNames('spoiler-button', { 'spoiler-button--visible': visible })}>
           <IconButton title={intl.formatMessage(messages.toggle_visible)} icon={visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />
         </div>