Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Introduce room knocks bar
Browse files Browse the repository at this point in the history
Signed-off-by: Charly Nguyen <[email protected]>
  • Loading branch information
Charly Nguyen committed Aug 30, 2023
1 parent dc70ea5 commit 794a35d
Show file tree
Hide file tree
Showing 6 changed files with 478 additions and 1 deletion.
1 change: 1 addition & 0 deletions res/css/_components.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@
@import "./views/rooms/_RoomCallBanner.pcss";
@import "./views/rooms/_RoomHeader.pcss";
@import "./views/rooms/_RoomInfoLine.pcss";
@import "./views/rooms/_RoomKnocksBar.pcss";
@import "./views/rooms/_RoomList.pcss";
@import "./views/rooms/_RoomListHeader.pcss";
@import "./views/rooms/_RoomPreviewBar.pcss";
Expand Down
50 changes: 50 additions & 0 deletions res/css/views/rooms/_RoomKnocksBar.pcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
Copyright 2023 Nordeck IT + Consulting GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

.mx_RoomKnocksBar {
background-color: var(--cpd-color-bg-subtle-secondary);
display: flex;
padding: var(--cpd-space-2x) var(--cpd-space-4x);
}

.mx_RoomKnocksBar_content {
flex-grow: 1;
margin: 0 var(--cpd-space-3x);
}

.mx_RoomKnocksBar_paragraph {
color: $secondary-content;
font-size: var(--cpd-font-size-body-sm);
margin: 0;
}

.mx_RoomKnocksBar_link {
margin-left: var(--cpd-space-3x);
}

.mx_RoomKnocksBar_action,
.mx_RoomKnocksBar_avatar {
align-self: center;
flex-shrink: 0;
}

.mx_RoomKnocksBar_action + .mx_RoomKnocksBar_action {
margin-left: var(--cpd-space-3x);
}

.mx_RoomKnocksBar_avatar + .mx_RoomKnocksBar_avatar {
margin-left: calc(var(--cpd-space-4x) * -1);
}
2 changes: 2 additions & 0 deletions src/components/views/rooms/LegacyRoomHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import RoomTopic from "../elements/RoomTopic";
import RoomName from "../elements/RoomName";
import { E2EStatus } from "../../../utils/ShieldUtils";
import { IOOBData } from "../../../stores/ThreepidInviteStore";
import { RoomKnocksBar } from "./RoomKnocksBar";
import { SearchScope } from "./SearchBar";
import { aboveLeftOf, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
import RoomContextMenu from "../context_menus/RoomContextMenu";
Expand Down Expand Up @@ -820,6 +821,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
</div>
{!isVideoRoom && <RoomCallBanner roomId={this.props.room.roomId} />}
<RoomLiveShareWarning roomId={this.props.room.roomId} />
<RoomKnocksBar room={this.props.room} />
</header>
);
}
Expand Down
152 changes: 152 additions & 0 deletions src/components/views/rooms/RoomKnocksBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
Copyright 2023 Nordeck IT + Consulting GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { EventTimeline, JoinRule, MatrixError, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
import React, { ReactElement, ReactNode, useCallback, useState, VFC } from "react";

import { Icon as CheckIcon } from "../../../../res/img/feather-customised/check.svg";
import { Icon as XIcon } from "../../../../res/img/feather-customised/x.svg";
import dis from "../../../dispatcher/dispatcher";
import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
import { _t } from "../../../languageHandler";
import Modal from "../../../Modal";
import MemberAvatar from "../avatars/MemberAvatar";
import ErrorDialog from "../dialogs/ErrorDialog";
import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog";
import AccessibleButton from "../elements/AccessibleButton";
import Heading from "../typography/Heading";

export const RoomKnocksBar: VFC<{ room: Room }> = ({ room }) => {
const client = room.client;
const userId = client.getUserId() || "";
const canInvite = room.canInvite(userId);
const member = room.getMember(userId);
const state = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
const canKick = member && state ? state.hasSufficientPowerLevelFor("kick", member.powerLevel) : false;

const handleApprove = (userId: string): void => {
setDisabled(true);
client.invite(room.roomId, userId).catch(onError);
};

const handleDeny = (userId: string): void => {
setDisabled(true);
client.kick(room.roomId, userId).catch(onError);
};

const handleOpenRoomSettings = (): void =>
dis.dispatch({ action: "open_room_settings", room_id: room.roomId, initial_tab_id: RoomSettingsTab.People });

const onError = (error: MatrixError): void => {
setDisabled(false);
Modal.createDialog(ErrorDialog, { title: error.name, description: error.message });
};

const [disabled, setDisabled] = useState(false);
const knockMembers = useTypedEventEmitterState(
room,
RoomStateEvent.Members,
useCallback(() => room.getMembersWithMembership("knock"), [room]),
);
const knockMembersCount = knockMembers.length;

if (room.getJoinRule() !== JoinRule.Knock || knockMembersCount === 0 || (!canInvite && !canKick)) return null;

let buttons: ReactElement = (
<AccessibleButton
className="mx_RoomKnocksBar_action"
kind="primary"
onClick={handleOpenRoomSettings}
title={_t("action|view")}
>
{_t("action|view")}
</AccessibleButton>
);
let names: string = knockMembers
.slice(0, 2)
.map((knockMember) => knockMember.name)
.join(", ");
let link: ReactNode = null;
switch (knockMembersCount) {
case 1: {
buttons = (
<>
<AccessibleButton
className="mx_RoomKnocksBar_action"
disabled={!canKick || disabled}
kind="icon_primary_outline"
onClick={() => handleDeny(knockMembers[0].userId)}
title={_t("Deny")}
>
<XIcon width={18} height={18} />
</AccessibleButton>
<AccessibleButton
className="mx_RoomKnocksBar_action"
disabled={!canInvite || disabled}
kind="icon_primary"
onClick={() => handleApprove(knockMembers[0].userId)}
title={_t("Approve")}
>
<CheckIcon width={18} height={18} />
</AccessibleButton>
</>
);
names = `${knockMembers[0].name} (${knockMembers[0].userId})`;
link = (
<AccessibleButton
className="mx_RoomKnocksBar_link"
element="a"
kind="link_inline"
onClick={handleOpenRoomSettings}
>
{_t("action|view_message")}
</AccessibleButton>
);
break;
}
case 2: {
names = _t("%(names)s and %(name)s", { names: knockMembers[0].name, name: knockMembers[1].name });
break;
}
case 3: {
names = _t("%(names)s and %(name)s", { names, name: knockMembers[2].name });
break;
}
default:
names = _t("%(names)s and %(count)s others", { names, count: knockMembersCount - 2 });
}

return (
<div className="mx_RoomKnocksBar">
{knockMembers.slice(0, 2).map((knockMember) => (
<MemberAvatar
className="mx_RoomKnocksBar_avatar"
key={knockMember.userId}
member={knockMember}
size="32px"
/>
))}
<div className="mx_RoomKnocksBar_content">
<Heading size="4">{_t("%(count)s people asking to join", { count: knockMembersCount })}</Heading>
<p className="mx_RoomKnocksBar_paragraph">
{names}
{link}
</p>
</div>
{buttons}
</div>
);
};
12 changes: 11 additions & 1 deletion src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
"search": "Search",
"quote": "Quote",
"unpin": "Unpin",
"view": "View",
"view_message": "View message",
"start_chat": "Start chat",
"invites_list": "Invites",
"reject": "Reject",
Expand All @@ -73,7 +75,6 @@
"report_content": "Report Content",
"resend": "Resend",
"next": "Next",
"view": "View",
"ask_to_join": "Ask to join",
"forward": "Forward",
"copy_link": "Copy link",
Expand Down Expand Up @@ -1830,6 +1831,15 @@
"Public room": "Public room",
"Private space": "Private space",
"Private room": "Private room",
"%(names)s and %(name)s": "%(names)s and %(name)s",
"%(names)s and %(count)s others": {
"other": "%(names)s and %(count)s others",
"one": "%(names)s and %(count)s other"
},
"%(count)s people asking to join": {
"other": "%(count)s people asking to join",
"one": "Asking to join"
},
"Start new chat": "Start new chat",
"Invite to space": "Invite to space",
"You do not have permissions to invite people to this space": "You do not have permissions to invite people to this space",
Expand Down
Loading

0 comments on commit 794a35d

Please sign in to comment.