From 40e1aca891dba8205de2e9e4c999ee9fd53799be Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Wed, 14 Oct 2020 14:36:37 -0500
Subject: [PATCH] Making front end work w/ pictrs v2. Fixes #57

---
 src/assets/css/main.css                      |  4 ++
 src/shared/components/banner-icon-header.tsx | 12 ++--
 src/shared/components/community-link.tsx     |  9 +--
 src/shared/components/navbar.tsx             | 16 +----
 src/shared/components/pictrs-image.tsx       | 66 ++++++++++++++++++++
 src/shared/components/post-listing.tsx       | 23 ++++---
 src/shared/components/user-listing.tsx       | 14 +----
 src/shared/utils.ts                          | 43 -------------
 8 files changed, 95 insertions(+), 92 deletions(-)
 create mode 100644 src/shared/components/pictrs-image.tsx

diff --git a/src/assets/css/main.css b/src/assets/css/main.css
index e289266..c86e8ec 100644
--- a/src/assets/css/main.css
+++ b/src/assets/css/main.css
@@ -297,6 +297,10 @@ br.big {
   margin-top: -60px;
 }
 
+.img-icon {
+  width: 2rem; height: 2rem;
+}
+
 .tribute-container ul {
   margin: 0;
   margin-top: 2px;
diff --git a/src/shared/components/banner-icon-header.tsx b/src/shared/components/banner-icon-header.tsx
index 8c0eedb..cb84eb2 100644
--- a/src/shared/components/banner-icon-header.tsx
+++ b/src/shared/components/banner-icon-header.tsx
@@ -1,4 +1,5 @@
 import { Component } from 'inferno';
+import { PictrsImage } from './pictrs-image';
 
 interface BannerIconHeaderProps {
   banner?: string;
@@ -13,15 +14,12 @@ export class BannerIconHeader extends Component<BannerIconHeaderProps, any> {
   render() {
     return (
       <div class="position-relative mb-2">
-        {this.props.banner && (
-          <img src={this.props.banner} class="banner img-fluid" />
-        )}
+        {this.props.banner && <PictrsImage src={this.props.banner} />}
         {this.props.icon && (
-          <img
+          <PictrsImage
             src={this.props.icon}
-            className={`ml-2 mb-0 ${
-              this.props.banner ? 'avatar-pushup' : ''
-            } rounded-circle avatar-overlay`}
+            iconOverlay
+            pushup={!!this.props.banner}
           />
         )}
       </div>
diff --git a/src/shared/components/community-link.tsx b/src/shared/components/community-link.tsx
index 003f61e..db69f44 100644
--- a/src/shared/components/community-link.tsx
+++ b/src/shared/components/community-link.tsx
@@ -1,7 +1,8 @@
 import { Component } from 'inferno';
 import { Link } from 'inferno-router';
 import { Community } from 'lemmy-js-client';
-import { hostname, pictrsAvatarThumbnail, showAvatars } from '../utils';
+import { hostname, showAvatars } from '../utils';
+import { PictrsImage } from './pictrs-image';
 
 interface CommunityOther {
   name: string;
@@ -47,11 +48,7 @@ export class CommunityLink extends Component<CommunityLinkProps, any> {
         to={link}
       >
         {!this.props.hideAvatar && community.icon && showAvatars() && (
-          <img
-            style="width: 2rem; height: 2rem;"
-            src={pictrsAvatarThumbnail(community.icon)}
-            class="rounded-circle mr-2"
-          />
+          <PictrsImage src={community.icon} icon />
         )}
         <span>{displayName}</span>
       </Link>
diff --git a/src/shared/components/navbar.tsx b/src/shared/components/navbar.tsx
index e3c27bd..04dae46 100644
--- a/src/shared/components/navbar.tsx
+++ b/src/shared/components/navbar.tsx
@@ -20,7 +20,6 @@ import {
 } from 'lemmy-js-client';
 import {
   wsJsonToRes,
-  pictrsAvatarThumbnail,
   showAvatars,
   fetchLimit,
   toast,
@@ -32,6 +31,7 @@ import {
   wsSubscribe,
 } from '../utils';
 import { i18n } from '../i18next';
+import { PictrsImage } from './pictrs-image';
 
 interface NavbarProps {
   site: GetSiteResponse;
@@ -190,12 +190,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
               to="/"
             >
               {this.props.site.site.icon && showAvatars() && (
-                <img
-                  src={pictrsAvatarThumbnail(this.props.site.site.icon)}
-                  height="32"
-                  width="32"
-                  class="rounded-circle mr-2"
-                />
+                <PictrsImage src={this.props.site.site.icon} icon />
               )}
               {this.props.site.site.name}
             </Link>
@@ -345,12 +340,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
                     >
                       <span>
                         {user.avatar && showAvatars() && (
-                          <img
-                            src={pictrsAvatarThumbnail(user.avatar)}
-                            height="32"
-                            width="32"
-                            class="rounded-circle mr-2"
-                          />
+                          <PictrsImage src={user.avatar} icon />
                         )}
                         {user.preferred_username
                           ? user.preferred_username
diff --git a/src/shared/components/pictrs-image.tsx b/src/shared/components/pictrs-image.tsx
new file mode 100644
index 0000000..471a0b7
--- /dev/null
+++ b/src/shared/components/pictrs-image.tsx
@@ -0,0 +1,66 @@
+import { Component } from 'inferno';
+
+const iconThumbnailSize = 96;
+const thumbnailSize = 256;
+
+interface PictrsImageProps {
+  src: string;
+  icon?: boolean;
+  thumbnail?: boolean;
+  nsfw?: boolean;
+  iconOverlay?: boolean;
+  pushup?: boolean;
+}
+
+export class PictrsImage extends Component<PictrsImageProps, any> {
+  constructor(props: any, context: any) {
+    super(props, context);
+  }
+
+  render() {
+    return (
+      <picture>
+        <source srcSet={this.src('webp')} type="image/webp" />
+        <source srcSet={this.src('jpg')} type="image/jpeg" />
+        <img
+          src={this.src('jpg')}
+          className={`img-fluid 
+        ${this.props.thumbnail ? 'thumbnail rounded ' : 'img-expanded '} 
+        ${this.props.thumbnail && this.props.nsfw && 'img-blur '}
+        ${this.props.icon && 'rounded-circle img-icon mr-2 '}
+        ${this.props.iconOverlay && 'ml-2 mb-0 rounded-circle avatar-overlay '}
+        ${this.props.pushup && 'avatar-pushup '}
+        `}
+        />
+      </picture>
+    );
+  }
+
+  src(format: string): string {
+    // sample url:
+    // http://localhost:8535/pictrs/image/file.png?thumbnail=256&format=jpg
+
+    let split = this.props.src.split('/pictrs/image/');
+
+    // If theres not multiple, then its not a pictrs image
+    if (split.length == 1) {
+      return this.props.src;
+    }
+
+    let host = split[0];
+    let path = split[1];
+
+    let params = { format };
+
+    if (this.props.thumbnail) {
+      params['thumbnail'] = thumbnailSize;
+    } else if (this.props.icon) {
+      params['thumbnail'] = iconThumbnailSize;
+    }
+
+    let paramsStr = `?${new URLSearchParams(params).toString()}`;
+    let out = `${host}/pictrs/image/${path}${paramsStr}`;
+
+    return out;
+  }
+}
diff --git a/src/shared/components/post-listing.tsx b/src/shared/components/post-listing.tsx
index 52c40d4..6545b2a 100644
--- a/src/shared/components/post-listing.tsx
+++ b/src/shared/components/post-listing.tsx
@@ -24,6 +24,7 @@ import { PostForm } from './post-form';
 import { IFramelyCard } from './iframely-card';
 import { UserListing } from './user-listing';
 import { CommunityLink } from './community-link';
+import { PictrsImage } from './pictrs-image';
 import {
   md,
   mdToHtml,
@@ -32,7 +33,6 @@ import {
   isImage,
   isVideo,
   getUnixTime,
-  pictrsImage,
   setupTippy,
   hostname,
   previewLines,
@@ -164,27 +164,26 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
   imgThumb(src: string) {
     let post = this.props.post;
     return (
-      <img
-        className={`img-fluid thumbnail rounded ${
-          post.nsfw || post.community_nsfw ? 'img-blur' : ''
-        }`}
+      <PictrsImage
         src={src}
+        thumbnail
+        nsfw={post.nsfw || post.community_nsfw}
       />
     );
   }
 
-  getImage(thumbnail: boolean = false) {
+  getImageSrc(): string {
     let post = this.props.post;
     if (isImage(post.url)) {
       if (post.url.includes('pictrs')) {
-        return pictrsImage(post.url, thumbnail);
+        return post.url;
       } else if (post.thumbnail_url) {
-        return pictrsImage(post.thumbnail_url, thumbnail);
+        return post.thumbnail_url;
       } else {
         return post.url;
       }
     } else if (post.thumbnail_url) {
-      return pictrsImage(post.thumbnail_url, thumbnail);
+      return post.thumbnail_url;
     } else {
       return null;
     }
@@ -200,7 +199,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
           data-tippy-content={i18n.t('expand_here')}
           onClick={linkEvent(this, this.handleImageExpandClick)}
         >
-          {this.imgThumb(this.getImage(true))}
+          {this.imgThumb(this.getImageSrc())}
           <svg class="icon mini-overlay">
             <use xlinkHref="#icon-image"></use>
           </svg>
@@ -215,7 +214,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
           rel="noopener"
           title={post.url}
         >
-          {this.imgThumb(this.getImage(true))}
+          {this.imgThumb(this.getImageSrc())}
           <svg class="icon mini-overlay">
             <use xlinkHref="#icon-external-link"></use>
           </svg>
@@ -442,7 +441,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                     class="pointer"
                     onClick={linkEvent(this, this.handleImageExpandClick)}
                   >
-                    <img class="img-fluid img-expanded" src={this.getImage()} />
+                    <PictrsImage src={this.getImageSrc()} />
                   </span>
                 </div>
               </span>
diff --git a/src/shared/components/user-listing.tsx b/src/shared/components/user-listing.tsx
index fd296fa..1b97dbb 100644
--- a/src/shared/components/user-listing.tsx
+++ b/src/shared/components/user-listing.tsx
@@ -1,13 +1,9 @@
 import { Component } from 'inferno';
 import { Link } from 'inferno-router';
 import { UserView } from 'lemmy-js-client';
-import {
-  pictrsAvatarThumbnail,
-  showAvatars,
-  hostname,
-  isCakeDay,
-} from '../utils';
+import { showAvatars, hostname, isCakeDay } from '../utils';
 import { CakeDay } from './cake-day';
+import { PictrsImage } from './pictrs-image';
 
 export interface UserOther {
   name: string;
@@ -59,11 +55,7 @@ export class UserListing extends Component<UserListingProps, any> {
           to={link}
         >
           {!this.props.hideAvatar && user.avatar && showAvatars() && (
-            <img
-              style="width: 2rem; height: 2rem;"
-              src={pictrsAvatarThumbnail(user.avatar)}
-              class="rounded-circle mr-2"
-            />
+            <PictrsImage src={user.avatar} icon />
           )}
           <span>{displayName}</span>
         </Link>
diff --git a/src/shared/utils.ts b/src/shared/utils.ts
index 5b14f0d..9bc9b23 100644
--- a/src/shared/utils.ts
+++ b/src/shared/utils.ts
@@ -491,15 +491,6 @@ export function objectFlip(obj: any) {
   return ret;
 }
 
-export function pictrsAvatarThumbnail(src: string): string {
-  // sample url: http://localhost:8535/pictrs/image/thumbnail256/gs7xuu.jpg
-  let split = src.split('/pictrs/image');
-  let out = `${split[0]}/pictrs/image/${
-    canUseWebP() ? 'webp/' : ''
-  }thumbnail96${split[1]}`;
-  return out;
-}
-
 export function showAvatars(): boolean {
   return (
     (UserService.Instance.user && UserService.Instance.user.show_avatars) ||
@@ -520,23 +511,6 @@ export function isCakeDay(published: string): boolean {
   );
 }
 
-// Converts to image thumbnail
-export function pictrsImage(hash: string, thumbnail: boolean = false): string {
-  let root = `/pictrs/image`;
-
-  // Necessary for other servers / domains
-  if (hash.includes('pictrs')) {
-    let split = hash.split('/pictrs/image/');
-    root = `${split[0]}/pictrs/image`;
-    hash = split[1];
-  }
-
-  let out = `${root}/${canUseWebP() ? 'webp/' : ''}${
-    thumbnail ? 'thumbnail256/' : ''
-  }${hash}`;
-  return out;
-}
-
 export function isCommentType(
   item: Comment | PrivateMessage | Post
 ): item is Comment {
@@ -1090,23 +1064,6 @@ export function hostname(url: string): string {
   return cUrl.port ? `${cUrl.hostname}:${cUrl.port}` : `${cUrl.hostname}`;
 }
 
-function canUseWebP() {
-  // TODO pictshare might have a webp conversion bug, try disabling this
-  return false;
-
-  // var elem = document.createElement('canvas');
-  // if (!!(elem.getContext && elem.getContext('2d'))) {
-  //   var testString = !(window.mozInnerScreenX == null) ? 'png' : 'webp';
-  //   // was able or not to get WebP representation
-  //   return (
-  //     elem.toDataURL('image/webp').startsWith('data:image/' + testString)
-  //   );
-  // }
-
-  // // very old browser like IE 8, canvas not supported
-  // return false;
-}
-
 export function validTitle(title?: string): boolean {
   // Initial title is null, minimum length is taken care of by textarea's minLength={3}
   if (title === null || title.length < 3) return true;
-- 
2.44.1