From 60828913d22541b8295ea8cce2874a210be23887 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 22 Apr 2021 08:13:03 +0100
Subject: [PATCH 1/4] Iterate the spaces face pile design

---
 res/css/structures/_SpaceRoomView.scss        | 25 +----------
 res/css/views/elements/_FacePile.scss         | 27 ++++++++++-
 src/components/views/elements/FacePile.tsx    | 45 ++++++++++++++-----
 .../views/elements/TextWithTooltip.js         |  7 ++-
 src/i18n/strings/en_EN.json                   |  6 +++
 5 files changed, 70 insertions(+), 40 deletions(-)

diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss
index 2e7cfb55d9..cb7006fb86 100644
--- a/res/css/structures/_SpaceRoomView.scss
+++ b/res/css/structures/_SpaceRoomView.scss
@@ -225,34 +225,11 @@ $SpaceRoomViewInnerWidth: 428px;
                 .mx_FacePile_faces {
                     cursor: pointer;
 
-                    > span:hover {
+                    &:hover {
                         .mx_BaseAvatar {
                             filter: brightness(0.8);
                         }
                     }
-
-                    > span:first-child {
-                        position: relative;
-
-                        .mx_BaseAvatar {
-                            filter: brightness(0.8);
-                        }
-
-                        &::before {
-                            content: "";
-                            z-index: 1;
-                            position: absolute;
-                            top: 0;
-                            left: 0;
-                            height: 30px;
-                            width: 30px;
-                            background: #ffffff; // white icon fill
-                            mask-position: center;
-                            mask-size: 24px;
-                            mask-repeat: no-repeat;
-                            mask-image: url('$(res)/img/element-icons/room/ellipsis.svg');
-                        }
-                    }
                 }
             }
 
diff --git a/res/css/views/elements/_FacePile.scss b/res/css/views/elements/_FacePile.scss
index 9a992f59d1..0f453eb3ff 100644
--- a/res/css/views/elements/_FacePile.scss
+++ b/res/css/views/elements/_FacePile.scss
@@ -20,7 +20,7 @@ limitations under the License.
         flex-direction: row-reverse;
         vertical-align: middle;
 
-        > span + span {
+        > .mx_FacePile_face + .mx_FacePile_face {
             margin-right: -8px;
         }
 
@@ -31,9 +31,32 @@ limitations under the License.
         .mx_BaseAvatar_initial {
             margin: 1px; // to offset the border on the image
         }
+
+        .mx_FacePile_more {
+            position: relative;
+            border-radius: 100%;
+            width: 30px;
+            height: 30px;
+            background-color: $groupFilterPanel-bg-color;
+
+            &::before {
+                content: "";
+                z-index: 1;
+                position: absolute;
+                top: 0;
+                left: 0;
+                height: 30px;
+                width: 30px;
+                background: $tertiary-fg-color;
+                mask-position: center;
+                mask-size: 20px;
+                mask-repeat: no-repeat;
+                mask-image: url('$(res)/img/element-icons/room/ellipsis.svg');
+            }
+        }
     }
 
-    > span {
+    .mx_FacePile_summary {
         margin-left: 12px;
         font-size: $font-14px;
         line-height: $font-24px;
diff --git a/src/components/views/elements/FacePile.tsx b/src/components/views/elements/FacePile.tsx
index e223744352..67b218494a 100644
--- a/src/components/views/elements/FacePile.tsx
+++ b/src/components/views/elements/FacePile.tsx
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import React, { HTMLAttributes } from "react";
+import React, { HTMLAttributes, ReactNode, useContext } from "react";
 import { Room } from "matrix-js-sdk/src/models/room";
 import { RoomMember } from "matrix-js-sdk/src/models/room-member";
 import { sortBy } from "lodash";
@@ -24,6 +24,7 @@ import { _t } from "../../../languageHandler";
 import DMRoomMap from "../../../utils/DMRoomMap";
 import TextWithTooltip from "../elements/TextWithTooltip";
 import { useRoomMembers } from "../../../hooks/useRoomMembers";
+import MatrixClientContext from "../../../contexts/MatrixClientContext";
 
 const DEFAULT_NUM_FACES = 5;
 
@@ -36,6 +37,7 @@ interface IProps extends HTMLAttributes<HTMLSpanElement> {
 const isKnownMember = (member: RoomMember) => !!DMRoomMap.shared().getDMRoomsForUserId(member.userId)?.length;
 
 const FacePile = ({ room, onlyKnownUsers = true, numShown = DEFAULT_NUM_FACES, ...props }: IProps) => {
+    const cli = useContext(MatrixClientContext);
     let members = useRoomMembers(room);
 
     // sort users with an explicit avatar first
@@ -46,21 +48,40 @@ const FacePile = ({ room, onlyKnownUsers = true, numShown = DEFAULT_NUM_FACES, .
         // sort known users first
         iteratees.unshift(member => isKnownMember(member));
     }
-    if (members.length < 1) return null;
 
-    const shownMembers = sortBy(members, iteratees).slice(0, numShown);
+    // exclude ourselves from the shown members list
+    const shownMembers = sortBy(members.filter(m => m.userId !== cli.getUserId()), iteratees).slice(0, numShown);
+    if (shownMembers.length < 1) return null;
+
+    const commaSeparatedMembers = shownMembers.map(m => m.rawDisplayName).reverse().join(", ");
+
+    let tooltip: ReactNode;
+    if (props.onClick) {
+        tooltip = <div>
+            <div className="mx_Tooltip_title">
+                { _t("View all %(count)s members", { count: members.length }) }
+            </div>
+            <div className="mx_Tooltip_sub">
+                { _t("Including %(commaSeparatedMembers)s", { commaSeparatedMembers }) }
+            </div>
+        </div>;
+    } else {
+        tooltip = _t("%(count)s members including %(commaSeparatedMembers)s", {
+            count: members.length,
+            commaSeparatedMembers,
+        });
+    }
+
     return <div {...props} className="mx_FacePile">
-        <div className="mx_FacePile_faces">
-            { shownMembers.map(member => {
-                return <TextWithTooltip key={member.userId} tooltip={member.name}>
-                    <MemberAvatar member={member} width={28} height={28} />
-                </TextWithTooltip>;
-            }) }
-        </div>
-        { onlyKnownUsers && <span>
+        <TextWithTooltip class="mx_FacePile_faces" tooltip={tooltip} tooltipProps={{ yOffset: 32 }}>
+            { members.length > numShown ? <span className="mx_FacePile_face mx_FacePile_more" /> : null }
+            { shownMembers.map(m =>
+                <MemberAvatar key={m.userId} member={m} width={28} height={28} className="mx_FacePile_face" /> )}
+        </TextWithTooltip>
+        { onlyKnownUsers && <span className="mx_FacePile_summary">
             { _t("%(count)s people you know have already joined", { count: members.length }) }
         </span> }
-    </div>
+    </div>;
 };
 
 export default FacePile;
diff --git a/src/components/views/elements/TextWithTooltip.js b/src/components/views/elements/TextWithTooltip.js
index 0bd491768c..a6fc00fc2e 100644
--- a/src/components/views/elements/TextWithTooltip.js
+++ b/src/components/views/elements/TextWithTooltip.js
@@ -25,6 +25,7 @@ export default class TextWithTooltip extends React.Component {
         class: PropTypes.string,
         tooltipClass: PropTypes.string,
         tooltip: PropTypes.node.isRequired,
+        tooltipProps: PropTypes.object,
     };
 
     constructor() {
@@ -46,15 +47,17 @@ export default class TextWithTooltip extends React.Component {
     render() {
         const Tooltip = sdk.getComponent("elements.Tooltip");
 
-        const {class: className, children, tooltip, tooltipClass, ...props} = this.props;
+        const {class: className, children, tooltip, tooltipClass, tooltipProps, ...props} = this.props;
 
         return (
             <span {...props} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave} className={className}>
                 {children}
                 {this.state.hover && <Tooltip
+                    {...tooltipProps}
                     label={tooltip}
                     tooltipClassName={tooltipClass}
-                    className={"mx_TextWithTooltip_tooltip"} /> }
+                    className={"mx_TextWithTooltip_tooltip"}
+                /> }
             </span>
         );
     }
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 133d24e3c8..f1b700540f 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1916,7 +1916,13 @@
     "Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.": "Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.",
     "collapse": "collapse",
     "expand": "expand",
+    "View all %(count)s members|other": "View all %(count)s members",
+    "View all %(count)s members|one": "View 1 member",
+    "Including %(commaSeparatedMembers)s": "Including %(commaSeparatedMembers)s",
+    "%(count)s members including %(commaSeparatedMembers)s|other": "%(count)s members including %(commaSeparatedMembers)s",
+    "%(count)s members including %(commaSeparatedMembers)s|one": "%(commaSeparatedMembers)s",
     "%(count)s people you know have already joined|other": "%(count)s people you know have already joined",
+    "%(count)s people you know have already joined|one": "%(count)s person you know has already joined",
     "Rotate Right": "Rotate Right",
     "Rotate Left": "Rotate Left",
     "Zoom out": "Zoom out",

From 90cd5d0472d4bb09640d17a2e8c7bb132cc0b17c Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 22 Apr 2021 08:18:28 +0100
Subject: [PATCH 2/4] Remove old redundant hover effect

---
 res/css/structures/_SpaceRoomView.scss | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss
index cb7006fb86..2dbf0fe0fe 100644
--- a/res/css/structures/_SpaceRoomView.scss
+++ b/res/css/structures/_SpaceRoomView.scss
@@ -224,12 +224,6 @@ $SpaceRoomViewInnerWidth: 428px;
 
                 .mx_FacePile_faces {
                     cursor: pointer;
-
-                    &:hover {
-                        .mx_BaseAvatar {
-                            filter: brightness(0.8);
-                        }
-                    }
                 }
             }
 

From ca07b1ed04fc1357296a7dfca83b0265544193bb Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 22 Apr 2021 09:06:53 +0100
Subject: [PATCH 3/4] Update res/css/views/elements/_FacePile.scss

Co-authored-by: Germain <germain@souquet.com>
---
 res/css/views/elements/_FacePile.scss | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/res/css/views/elements/_FacePile.scss b/res/css/views/elements/_FacePile.scss
index 0f453eb3ff..c691baffb5 100644
--- a/res/css/views/elements/_FacePile.scss
+++ b/res/css/views/elements/_FacePile.scss
@@ -45,8 +45,8 @@ limitations under the License.
                 position: absolute;
                 top: 0;
                 left: 0;
-                height: 30px;
-                width: 30px;
+                height: inherit;
+                width: inherit;
                 background: $tertiary-fg-color;
                 mask-position: center;
                 mask-size: 20px;

From 23c61752cdfcaebde67db358d38684225c969900 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 22 Apr 2021 09:08:25 +0100
Subject: [PATCH 4/4] Add comment

---
 src/components/views/elements/FacePile.tsx | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/components/views/elements/FacePile.tsx b/src/components/views/elements/FacePile.tsx
index 67b218494a..aeca2e844b 100644
--- a/src/components/views/elements/FacePile.tsx
+++ b/src/components/views/elements/FacePile.tsx
@@ -53,6 +53,8 @@ const FacePile = ({ room, onlyKnownUsers = true, numShown = DEFAULT_NUM_FACES, .
     const shownMembers = sortBy(members.filter(m => m.userId !== cli.getUserId()), iteratees).slice(0, numShown);
     if (shownMembers.length < 1) return null;
 
+    // We reverse the order of the shown faces in CSS to simplify their visual overlap,
+    // reverse members in tooltip order to make the order between the two match up.
     const commaSeparatedMembers = shownMembers.map(m => m.rawDisplayName).reverse().join(", ");
 
     let tooltip: ReactNode;