mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 11:15:53 +03:00
Add facepile to Space landing page and tweak layout slightly
This commit is contained in:
parent
d10241b1c4
commit
eff0e6fcc3
3 changed files with 189 additions and 145 deletions
|
@ -153,53 +153,6 @@ $SpaceRoomViewInnerWidth: 428px;
|
|||
margin: 20px 0 !important; // override default margin from above
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_preview_info {
|
||||
color: $tertiary-fg-color;
|
||||
font-size: $font-15px;
|
||||
line-height: $font-24px;
|
||||
margin: 20px 0;
|
||||
|
||||
.mx_SpaceRoomView_preview_info_public,
|
||||
.mx_SpaceRoomView_preview_info_private {
|
||||
padding-left: 20px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
top: 0;
|
||||
left: -2px;
|
||||
mask-position: center;
|
||||
mask-repeat: no-repeat;
|
||||
background-color: $tertiary-fg-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_preview_info_public::before {
|
||||
mask-size: 12px;
|
||||
mask-image: url("$(res)/img/globe.svg");
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_preview_info_private::before {
|
||||
mask-size: 14px;
|
||||
mask-image: url("$(res)/img/element-icons/lock.svg");
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_kind_link {
|
||||
color: inherit;
|
||||
position: relative;
|
||||
padding-left: 16px;
|
||||
|
||||
&::before {
|
||||
content: "·"; // visual separator
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_preview_topic {
|
||||
font-size: $font-14px;
|
||||
line-height: $font-22px;
|
||||
|
@ -253,32 +206,80 @@ $SpaceRoomViewInnerWidth: 428px;
|
|||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_landing_memberCount {
|
||||
.mx_SpaceRoomView_landing_info {
|
||||
display: flex;
|
||||
margin-right: 60px;
|
||||
align-items: center;
|
||||
|
||||
.mx_SpaceRoomView_info {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mx_FacePile {
|
||||
display: inline-block;
|
||||
margin-left: auto;
|
||||
margin-right: 12px;
|
||||
|
||||
.mx_FacePile_faces {
|
||||
cursor: pointer;
|
||||
|
||||
> span: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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_landing_inviteButton {
|
||||
position: relative;
|
||||
margin-left: 24px;
|
||||
padding: 0 0 0 28px;
|
||||
line-height: $font-24px;
|
||||
vertical-align: text-bottom;
|
||||
padding-left: 40px;
|
||||
height: min-content;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
content: "";
|
||||
left: 8px;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background: #ffffff; // white icon fill
|
||||
mask-position: center;
|
||||
mask-size: 16px;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
background-color: $accent-color;
|
||||
mask-image: url('$(res)/img/element-icons/community-members.svg');
|
||||
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_landing_topic {
|
||||
font-size: $font-15px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_landing_adminButtons {
|
||||
|
@ -323,16 +324,6 @@ $SpaceRoomViewInnerWidth: 428px;
|
|||
background: #ffffff; // white icon fill
|
||||
}
|
||||
|
||||
&.mx_SpaceRoomView_landing_inviteButton {
|
||||
&::before {
|
||||
background-color: $accent-color;
|
||||
}
|
||||
|
||||
&::after {
|
||||
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
||||
}
|
||||
}
|
||||
|
||||
&.mx_SpaceRoomView_landing_addButton {
|
||||
&::before {
|
||||
background-color: #ac3ba8;
|
||||
|
@ -424,3 +415,50 @@ $SpaceRoomViewInnerWidth: 428px;
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_info {
|
||||
color: $tertiary-fg-color;
|
||||
font-size: $font-15px;
|
||||
line-height: $font-24px;
|
||||
margin: 20px 0;
|
||||
|
||||
.mx_SpaceRoomView_info_public,
|
||||
.mx_SpaceRoomView_info_private {
|
||||
padding-left: 20px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
top: 0;
|
||||
left: -2px;
|
||||
mask-position: center;
|
||||
mask-repeat: no-repeat;
|
||||
background-color: $tertiary-fg-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_info_public::before {
|
||||
mask-size: 12px;
|
||||
mask-image: url("$(res)/img/globe.svg");
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_info_private::before {
|
||||
mask-size: 14px;
|
||||
mask-image: url("$(res)/img/element-icons/lock.svg");
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_kind_link {
|
||||
color: inherit;
|
||||
position: relative;
|
||||
padding-left: 16px;
|
||||
|
||||
&::before {
|
||||
content: "·"; // visual separator
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,6 +92,41 @@ const useMyRoomMembership = (room: Room) => {
|
|||
return membership;
|
||||
};
|
||||
|
||||
const SpaceInfo = ({ space }) => {
|
||||
const joinRule = space.getJoinRule();
|
||||
|
||||
let visibilitySection;
|
||||
if (joinRule === "public") {
|
||||
visibilitySection = <span className="mx_SpaceRoomView_info_public">
|
||||
{ _t("Public space") }
|
||||
</span>;
|
||||
} else {
|
||||
visibilitySection = <span className="mx_SpaceRoomView_info_private">
|
||||
{ _t("Private space") }
|
||||
</span>;
|
||||
}
|
||||
|
||||
return <div className="mx_SpaceRoomView_info">
|
||||
{ visibilitySection }
|
||||
{ joinRule === "public" && <RoomMemberCount room={space}>
|
||||
{(count) => count > 0 ? (
|
||||
<AccessibleButton
|
||||
kind="link"
|
||||
onClick={() => {
|
||||
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||
action: Action.SetRightPanelPhase,
|
||||
phase: RightPanelPhases.RoomMemberList,
|
||||
refireParams: { space },
|
||||
});
|
||||
}}
|
||||
>
|
||||
{ _t("%(count)s members", { count }) }
|
||||
</AccessibleButton>
|
||||
) : null}
|
||||
</RoomMemberCount> }
|
||||
</div>
|
||||
};
|
||||
|
||||
const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const myMembership = useMyRoomMembership(space);
|
||||
|
@ -158,45 +193,13 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
|||
joinButtons = <InlineSpinner />;
|
||||
}
|
||||
|
||||
const joinRule = space.getJoinRule();
|
||||
|
||||
let visibilitySection;
|
||||
if (joinRule === "public") {
|
||||
visibilitySection = <span className="mx_SpaceRoomView_preview_info_public">
|
||||
{ _t("Public space") }
|
||||
</span>;
|
||||
} else {
|
||||
visibilitySection = <span className="mx_SpaceRoomView_preview_info_private">
|
||||
{ _t("Private space") }
|
||||
</span>;
|
||||
}
|
||||
|
||||
return <div className="mx_SpaceRoomView_preview">
|
||||
{ inviterSection }
|
||||
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
||||
<h1 className="mx_SpaceRoomView_preview_name">
|
||||
<RoomName room={space} />
|
||||
</h1>
|
||||
<div className="mx_SpaceRoomView_preview_info">
|
||||
{ visibilitySection }
|
||||
{ joinRule === "public" && <RoomMemberCount room={space}>
|
||||
{(count) => count > 0 ? (
|
||||
<AccessibleButton
|
||||
className="mx_SpaceRoomView_preview_memberCount"
|
||||
kind="link"
|
||||
onClick={() => {
|
||||
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||
action: Action.SetRightPanelPhase,
|
||||
phase: RightPanelPhases.RoomMemberList,
|
||||
refireParams: { space },
|
||||
});
|
||||
}}
|
||||
>
|
||||
{ _t("%(count)s members", { count }) }
|
||||
</AccessibleButton>
|
||||
) : null}
|
||||
</RoomMemberCount> }
|
||||
</div>
|
||||
<SpaceInfo space={space} />
|
||||
<RoomTopic room={space}>
|
||||
{(topic, ref) =>
|
||||
<div className="mx_SpaceRoomView_preview_topic" ref={ref}>
|
||||
|
@ -204,7 +207,7 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
|||
</div>
|
||||
}
|
||||
</RoomTopic>
|
||||
{ joinRule === "public" && <FacePile room={space} /> }
|
||||
{ space.getJoinRule() === "public" && <FacePile room={space} /> }
|
||||
<div className="mx_SpaceRoomView_preview_joinButtons">
|
||||
{ joinButtons }
|
||||
</div>
|
||||
|
@ -219,10 +222,14 @@ const SpaceLanding = ({ space }) => {
|
|||
let inviteButton;
|
||||
if (myMembership === "join" && space.canInvite(userId)) {
|
||||
inviteButton = (
|
||||
<AccessibleButton className="mx_SpaceRoomView_landing_inviteButton" onClick={() => {
|
||||
showRoomInviteDialog(space.roomId);
|
||||
}}>
|
||||
{ _t("Invite people") }
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
className="mx_SpaceRoomView_landing_inviteButton"
|
||||
onClick={() => {
|
||||
showRoomInviteDialog(space.roomId);
|
||||
}}
|
||||
>
|
||||
{ _t("Invite") }
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
@ -259,6 +266,14 @@ const SpaceLanding = ({ space }) => {
|
|||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
const onMembersClick = () => {
|
||||
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||
action: Action.SetRightPanelPhase,
|
||||
phase: RightPanelPhases.RoomMemberList,
|
||||
refireParams: { space },
|
||||
});
|
||||
};
|
||||
|
||||
return <div className="mx_SpaceRoomView_landing">
|
||||
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
||||
<div className="mx_SpaceRoomView_landing_name">
|
||||
|
@ -266,40 +281,20 @@ const SpaceLanding = ({ space }) => {
|
|||
{(name) => {
|
||||
const tags = { name: () => <div className="mx_SpaceRoomView_landing_nameRow">
|
||||
<h1>{ name }</h1>
|
||||
<RoomMemberCount room={space}>
|
||||
{(count) => count > 0 ? (
|
||||
<AccessibleButton
|
||||
className="mx_SpaceRoomView_landing_memberCount"
|
||||
kind="link"
|
||||
onClick={() => {
|
||||
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||
action: Action.SetRightPanelPhase,
|
||||
phase: RightPanelPhases.RoomMemberList,
|
||||
refireParams: { space },
|
||||
});
|
||||
}}
|
||||
>
|
||||
{ _t("%(count)s members", { count }) }
|
||||
</AccessibleButton>
|
||||
) : null}
|
||||
</RoomMemberCount>
|
||||
</div> };
|
||||
if (shouldShowSpaceSettings(cli, space)) {
|
||||
if (space.getJoinRule() === "public") {
|
||||
return _t("Your public space <name/>", {}, tags) as JSX.Element;
|
||||
} else {
|
||||
return _t("Your private space <name/>", {}, tags) as JSX.Element;
|
||||
}
|
||||
}
|
||||
return _t("Welcome to <name/>", {}, tags) as JSX.Element;
|
||||
}}
|
||||
</RoomName>
|
||||
</div>
|
||||
<div className="mx_SpaceRoomView_landing_info">
|
||||
<SpaceInfo space={space} />
|
||||
<FacePile room={space} onlyKnownUsers={false} numShown={7} onClick={onMembersClick} />
|
||||
{ inviteButton }
|
||||
</div>
|
||||
<div className="mx_SpaceRoomView_landing_topic">
|
||||
<RoomTopic room={space} />
|
||||
</div>
|
||||
<div className="mx_SpaceRoomView_landing_adminButtons">
|
||||
{ inviteButton }
|
||||
{ addRoomButtons }
|
||||
{ settingsButton }
|
||||
</div>
|
||||
|
|
|
@ -14,31 +14,42 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { HTMLAttributes } 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";
|
||||
|
||||
import MemberAvatar from "../avatars/MemberAvatar";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import TextWithTooltip from "../elements/TextWithTooltip";
|
||||
import { useRoomMembers } from "../../../hooks/useRoomMembers";
|
||||
|
||||
const DEFAULT_NUM_FACES = 5;
|
||||
|
||||
interface IProps {
|
||||
interface IProps extends HTMLAttributes<HTMLSpanElement> {
|
||||
room: Room;
|
||||
onlyKnownUsers?: boolean;
|
||||
numShown?: number;
|
||||
}
|
||||
|
||||
const FacePile = ({ room, numShown = DEFAULT_NUM_FACES }: IProps) => {
|
||||
const knownMembers = sortBy(room.getJoinedMembers().filter(member => {
|
||||
return !!DMRoomMap.shared().getDMRoomsForUserId(member.userId)?.length;
|
||||
}), member => member.getMxcAvatarUrl() ? 0 : 1); // sort users with an explicit avatar first
|
||||
const isKnownMember = (member: RoomMember) => !!DMRoomMap.shared().getDMRoomsForUserId(member.userId)?.length;
|
||||
|
||||
if (knownMembers.length < 1) return null;
|
||||
const shownMembers = knownMembers.slice(0, numShown);
|
||||
const FacePile = ({ room, onlyKnownUsers = true, numShown = DEFAULT_NUM_FACES, ...props }: IProps) => {
|
||||
let members = useRoomMembers(room);
|
||||
|
||||
return <div className="mx_FacePile">
|
||||
// sort users with an explicit avatar first
|
||||
const iteratees = [member => !!member.getMxcAvatarUrl()];
|
||||
if (onlyKnownUsers) {
|
||||
members = members.filter(isKnownMember);
|
||||
} else {
|
||||
// sort known users first
|
||||
iteratees.unshift(member => isKnownMember(member));
|
||||
}
|
||||
if (members.length < 1) return null;
|
||||
|
||||
const shownMembers = sortBy(members, iteratees).slice(0, numShown);
|
||||
return <div {...props} className="mx_FacePile">
|
||||
<div className="mx_FacePile_faces">
|
||||
{ shownMembers.map(member => {
|
||||
return <TextWithTooltip key={member.userId} tooltip={member.name}>
|
||||
|
@ -46,9 +57,9 @@ const FacePile = ({ room, numShown = DEFAULT_NUM_FACES }: IProps) => {
|
|||
</TextWithTooltip>;
|
||||
}) }
|
||||
</div>
|
||||
<span>
|
||||
{ _t("%(count)s people you know have already joined", { count: knownMembers.length }) }
|
||||
</span>
|
||||
{ onlyKnownUsers && <span>
|
||||
{ _t("%(count)s people you know have already joined", { count: members.length }) }
|
||||
</span> }
|
||||
</div>
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue