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

Commit 4bc1c8c

Browse files
author
Charly Nguyen
committed
Introduce room knocks bar
Signed-off-by: Charly Nguyen <charly.nguyen@nordeck.net>
1 parent 46037d2 commit 4bc1c8c

File tree

6 files changed

+478
-1
lines changed

6 files changed

+478
-1
lines changed

res/css/_components.pcss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@
298298
@import "./views/rooms/_RoomCallBanner.pcss";
299299
@import "./views/rooms/_RoomHeader.pcss";
300300
@import "./views/rooms/_RoomInfoLine.pcss";
301+
@import "./views/rooms/_RoomKnocksBar.pcss";
301302
@import "./views/rooms/_RoomList.pcss";
302303
@import "./views/rooms/_RoomListHeader.pcss";
303304
@import "./views/rooms/_RoomPreviewBar.pcss";
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
Copyright 2023 Nordeck IT + Consulting GmbH
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
.mx_RoomKnocksBar {
18+
background-color: var(--cpd-color-bg-subtle-secondary);
19+
display: flex;
20+
padding: var(--cpd-space-2x) var(--cpd-space-4x);
21+
}
22+
23+
.mx_RoomKnocksBar_content {
24+
flex-grow: 1;
25+
margin: 0 var(--cpd-space-3x);
26+
}
27+
28+
.mx_RoomKnocksBar_paragraph {
29+
color: $secondary-content;
30+
font-size: var(--cpd-font-size-body-sm);
31+
margin: 0;
32+
}
33+
34+
.mx_RoomKnocksBar_link {
35+
margin-left: var(--cpd-space-3x);
36+
}
37+
38+
.mx_RoomKnocksBar_action,
39+
.mx_RoomKnocksBar_avatar {
40+
align-self: center;
41+
flex-shrink: 0;
42+
}
43+
44+
.mx_RoomKnocksBar_action + .mx_RoomKnocksBar_action {
45+
margin-left: var(--cpd-space-3x);
46+
}
47+
48+
.mx_RoomKnocksBar_avatar + .mx_RoomKnocksBar_avatar {
49+
margin-left: calc(var(--cpd-space-4x) * -1);
50+
}

src/components/views/rooms/LegacyRoomHeader.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import RoomTopic from "../elements/RoomTopic";
3636
import RoomName from "../elements/RoomName";
3737
import { E2EStatus } from "../../../utils/ShieldUtils";
3838
import { IOOBData } from "../../../stores/ThreepidInviteStore";
39+
import { RoomKnocksBar } from "./RoomKnocksBar";
3940
import { SearchScope } from "./SearchBar";
4041
import { aboveLeftOf, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
4142
import RoomContextMenu from "../context_menus/RoomContextMenu";
@@ -820,6 +821,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
820821
</div>
821822
{!isVideoRoom && <RoomCallBanner roomId={this.props.room.roomId} />}
822823
<RoomLiveShareWarning roomId={this.props.room.roomId} />
824+
<RoomKnocksBar room={this.props.room} />
823825
</header>
824826
);
825827
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
Copyright 2023 Nordeck IT + Consulting GmbH
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { EventTimeline, JoinRule, MatrixError, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
18+
import React, { ReactElement, ReactNode, useCallback, useState, VFC } from "react";
19+
20+
import { Icon as CheckIcon } from "../../../../res/img/feather-customised/check.svg";
21+
import { Icon as XIcon } from "../../../../res/img/feather-customised/x.svg";
22+
import dis from "../../../dispatcher/dispatcher";
23+
import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
24+
import { _t } from "../../../languageHandler";
25+
import Modal from "../../../Modal";
26+
import MemberAvatar from "../avatars/MemberAvatar";
27+
import ErrorDialog from "../dialogs/ErrorDialog";
28+
import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog";
29+
import AccessibleButton from "../elements/AccessibleButton";
30+
import Heading from "../typography/Heading";
31+
32+
export const RoomKnocksBar: VFC<{ room: Room }> = ({ room }) => {
33+
const client = room.client;
34+
const userId = client.getUserId() || "";
35+
const canInvite = room.canInvite(userId);
36+
const member = room.getMember(userId);
37+
const state = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
38+
const canKick = member && state ? state.hasSufficientPowerLevelFor("kick", member.powerLevel) : false;
39+
40+
const handleApprove = (userId: string): void => {
41+
setDisabled(true);
42+
client.invite(room.roomId, userId).catch(onError);
43+
};
44+
45+
const handleDeny = (userId: string): void => {
46+
setDisabled(true);
47+
client.kick(room.roomId, userId).catch(onError);
48+
};
49+
50+
const handleOpenRoomSettings = (): void =>
51+
dis.dispatch({ action: "open_room_settings", room_id: room.roomId, initial_tab_id: RoomSettingsTab.People });
52+
53+
const onError = (error: MatrixError): void => {
54+
setDisabled(false);
55+
Modal.createDialog(ErrorDialog, { title: error.name, description: error.message });
56+
};
57+
58+
const [disabled, setDisabled] = useState(false);
59+
const knockMembers = useTypedEventEmitterState(
60+
room,
61+
RoomStateEvent.Members,
62+
useCallback(() => room.getMembersWithMembership("knock"), [room]),
63+
);
64+
const knockMembersCount = knockMembers.length;
65+
66+
if (room.getJoinRule() !== JoinRule.Knock || knockMembersCount === 0 || (!canInvite && !canKick)) return null;
67+
68+
let buttons: ReactElement = (
69+
<AccessibleButton
70+
className="mx_RoomKnocksBar_action"
71+
kind="primary"
72+
onClick={handleOpenRoomSettings}
73+
title={_t("action|view")}
74+
>
75+
{_t("action|view")}
76+
</AccessibleButton>
77+
);
78+
let names: string = knockMembers
79+
.slice(0, 2)
80+
.map((knockMember) => knockMember.name)
81+
.join(", ");
82+
let link: ReactNode = null;
83+
switch (knockMembersCount) {
84+
case 1: {
85+
buttons = (
86+
<>
87+
<AccessibleButton
88+
className="mx_RoomKnocksBar_action"
89+
disabled={!canKick || disabled}
90+
kind="icon_primary_outline"
91+
onClick={() => handleDeny(knockMembers[0].userId)}
92+
title={_t("Deny")}
93+
>
94+
<XIcon width={18} height={18} />
95+
</AccessibleButton>
96+
<AccessibleButton
97+
className="mx_RoomKnocksBar_action"
98+
disabled={!canInvite || disabled}
99+
kind="icon_primary"
100+
onClick={() => handleApprove(knockMembers[0].userId)}
101+
title={_t("Approve")}
102+
>
103+
<CheckIcon width={18} height={18} />
104+
</AccessibleButton>
105+
</>
106+
);
107+
names = `${knockMembers[0].name} (${knockMembers[0].userId})`;
108+
link = (
109+
<AccessibleButton
110+
className="mx_RoomKnocksBar_link"
111+
element="a"
112+
kind="link_inline"
113+
onClick={handleOpenRoomSettings}
114+
>
115+
{_t("action|view_message")}
116+
</AccessibleButton>
117+
);
118+
break;
119+
}
120+
case 2: {
121+
names = _t("%(names)s and %(name)s", { names: knockMembers[0].name, name: knockMembers[1].name });
122+
break;
123+
}
124+
case 3: {
125+
names = _t("%(names)s and %(name)s", { names, name: knockMembers[2].name });
126+
break;
127+
}
128+
default:
129+
names = _t("%(names)s and %(count)s others", { names, count: knockMembersCount - 2 });
130+
}
131+
132+
return (
133+
<div className="mx_RoomKnocksBar">
134+
{knockMembers.slice(0, 2).map((knockMember) => (
135+
<MemberAvatar
136+
className="mx_RoomKnocksBar_avatar"
137+
key={knockMember.userId}
138+
member={knockMember}
139+
size="32px"
140+
/>
141+
))}
142+
<div className="mx_RoomKnocksBar_content">
143+
<Heading size="4">{_t("%(count)s people asking to join", { count: knockMembersCount })}</Heading>
144+
<p className="mx_RoomKnocksBar_paragraph">
145+
{names}
146+
{link}
147+
</p>
148+
</div>
149+
{buttons}
150+
</div>
151+
);
152+
};

src/i18n/strings/en_EN.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
"search": "Search",
6868
"quote": "Quote",
6969
"unpin": "Unpin",
70+
"view": "View",
71+
"view_message": "View message",
7072
"start_chat": "Start chat",
7173
"invites_list": "Invites",
7274
"reject": "Reject",
@@ -87,7 +89,6 @@
8789
"report_content": "Report Content",
8890
"resend": "Resend",
8991
"next": "Next",
90-
"view": "View",
9192
"ask_to_join": "Ask to join",
9293
"forward": "Forward",
9394
"copy_link": "Copy link",
@@ -1861,6 +1862,15 @@
18611862
"Public room": "Public room",
18621863
"Private space": "Private space",
18631864
"Private room": "Private room",
1865+
"%(names)s and %(name)s": "%(names)s and %(name)s",
1866+
"%(names)s and %(count)s others": {
1867+
"other": "%(names)s and %(count)s others",
1868+
"one": "%(names)s and %(count)s other"
1869+
},
1870+
"%(count)s people asking to join": {
1871+
"other": "%(count)s people asking to join",
1872+
"one": "Asking to join"
1873+
},
18641874
"Start new chat": "Start new chat",
18651875
"Invite to space": "Invite to space",
18661876
"You do not have permissions to invite people to this space": "You do not have permissions to invite people to this space",

0 commit comments

Comments
 (0)