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

Commit 3a2ceef

Browse files
committed
Invite state was not reactive.
Changing power level did not update the ui. Signed-off-by: Timo K <[email protected]>
1 parent 0e92b34 commit 3a2ceef

File tree

6 files changed

+88
-49
lines changed

6 files changed

+88
-49
lines changed

src/components/views/context_menus/RoomContextMenu.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ limitations under the License.
1515
*/
1616

1717
import React, { useContext } from "react";
18-
import { Room } from "matrix-js-sdk/src/matrix";
18+
import { Room, RoomMemberEvent } from "matrix-js-sdk/src/matrix";
1919
import { KnownMembership } from "matrix-js-sdk/src/types";
2020

2121
import { IProps as IContextMenuProps } from "../../structures/ContextMenu";
@@ -117,9 +117,9 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
117117
const elementCallVideoRoomsEnabled = useFeatureEnabled("feature_element_call_video_rooms");
118118
const isVideoRoom =
119119
videoRoomsEnabled && (room.isElementVideoRoom() || (elementCallVideoRoomsEnabled && room.isCallRoom()));
120-
120+
const canInvite = useEventEmitterState(cli, RoomMemberEvent.PowerLevel, () => room.canInvite(cli.getUserId()!));
121121
let inviteOption: JSX.Element | undefined;
122-
if (room.canInvite(cli.getUserId()!) && !isDm && shouldShowComponent(UIComponent.InviteUsers)) {
122+
if (canInvite && !isDm && shouldShowComponent(UIComponent.InviteUsers)) {
123123
const onInviteClick = (ev: ButtonEvent): void => {
124124
ev.preventDefault();
125125
ev.stopPropagation();

src/components/views/right_panel/RoomSummaryCard.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { Icon as LockIcon } from "@vector-im/compound-design-tokens/icons/lock-s
3232
import { Icon as LockOffIcon } from "@vector-im/compound-design-tokens/icons/lock-off.svg";
3333
import { Icon as PublicIcon } from "@vector-im/compound-design-tokens/icons/public.svg";
3434
import { Icon as ErrorIcon } from "@vector-im/compound-design-tokens/icons/error.svg";
35-
import { EventType, JoinRule, Room } from "matrix-js-sdk/src/matrix";
35+
import { EventType, JoinRule, Room, RoomMemberEvent, RoomStateEvent } from "matrix-js-sdk/src/matrix";
3636

3737
import MatrixClientContext from "../../../contexts/MatrixClientContext";
3838
import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
@@ -393,6 +393,7 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, on
393393
const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () =>
394394
RoomListStore.instance.getTagsForRoom(room),
395395
);
396+
const canInviteToState = useEventEmitterState(room, RoomStateEvent.Update, () => canInviteTo(room));
396397
const isFavorite = roomTags.includes(DefaultTagID.Favourite);
397398

398399
return (
@@ -439,7 +440,7 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, on
439440
<MenuItem
440441
Icon={UserAddIcon}
441442
label={_t("action|invite")}
442-
disabled={!canInviteTo(room)}
443+
disabled={!canInviteToState}
443444
onSelect={() => inviteToRoom(room)}
444445
/>
445446
<MenuItem Icon={LinkIcon} label={_t("action|copy_link")} onSelect={onShareRoomClick} />

src/components/views/rooms/RoomHeader/CallGuestLinkButton.tsx

+9-30
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,16 @@ import { Icon as ExternalLinkIcon } from "@vector-im/compound-design-tokens/icon
1717
import { Button, IconButton, Tooltip } from "@vector-im/compound-web";
1818
import React, { useCallback, useMemo } from "react";
1919
import { logger } from "matrix-js-sdk/src/logger";
20-
import {
21-
EventTimeline,
22-
EventType,
23-
IJoinRuleEventContent,
24-
JoinRule,
25-
Room,
26-
RoomStateEvent,
27-
} from "matrix-js-sdk/src/matrix";
20+
import { EventType, IJoinRuleEventContent, JoinRule, Room } from "matrix-js-sdk/src/matrix";
2821

2922
import Modal from "../../../../Modal";
3023
import ShareDialog from "../../dialogs/ShareDialog";
3124
import { _t } from "../../../../languageHandler";
3225
import SettingsStore from "../../../../settings/SettingsStore";
3326
import SdkConfig from "../../../../SdkConfig";
3427
import { calculateRoomVia } from "../../../../utils/permalinks/Permalinks";
35-
import { useEventEmitterState } from "../../../../hooks/useEventEmitter";
3628
import BaseDialog from "../../dialogs/BaseDialog";
29+
import { useGuestAccessInformation } from "../../../../hooks/room/useGuestAccessInformation";
3730

3831
/**
3932
* Display a button to open a dialog to share a link to the call using a element call guest spa url (`element_call:guest_spa_url` in the EW config).
@@ -45,23 +38,7 @@ export const CallGuestLinkButton: React.FC<{ room: Room }> = ({ room }) => {
4538
return SdkConfig.get("element_call").guest_spa_url;
4639
}, []);
4740

48-
// We use the direct function only in functions triggered by user interaction to avoid computation on every render.
49-
const isRoomJoinable = useCallback(
50-
() => room.getJoinRule() === JoinRule.Public || room.getJoinRule() === JoinRule.Knock,
51-
[room],
52-
);
53-
54-
const isRoomJoinableState = useEventEmitterState(room, RoomStateEvent.Events, isRoomJoinable);
55-
56-
const canChangeJoinRule = useEventEmitterState(
57-
room,
58-
RoomStateEvent.Events,
59-
() =>
60-
room
61-
.getLiveTimeline()
62-
?.getState(EventTimeline.FORWARDS)
63-
?.maySendStateEvent(EventType.RoomJoinRules, room.myUserId) ?? false,
64-
);
41+
const { canChangeJoinRule, roomIsJoinableState, isRoomJoinable, canInvite } = useGuestAccessInformation(room);
6542

6643
const generateCallLink = useCallback(() => {
6744
if (!isRoomJoinable()) throw new Error("Cannot create link for room that users can not join without invite.");
@@ -104,16 +81,17 @@ export const CallGuestLinkButton: React.FC<{ room: Room }> = ({ room }) => {
10481
// the room needs to be set to public or knock to generate a link
10582
Modal.createDialog(JoinRuleDialog, {
10683
room,
84+
canInvite,
10785
}).finished.then(() => {
10886
// we need to use the function here because the callback got called before the state was updated.
10987
if (isRoomJoinable()) showLinkModal();
11088
});
11189
}
112-
}, [showLinkModal, room, isRoomJoinable]);
90+
}, [isRoomJoinable, showLinkModal, room, canInvite]);
11391

11492
return (
11593
<>
116-
{(canChangeJoinRule || isRoomJoinableState) && guestSpaUrl && (
94+
{(canChangeJoinRule || roomIsJoinableState) && guestSpaUrl && (
11795
<Tooltip label={_t("voip|get_call_link")}>
11896
<IconButton onClick={shareClick} aria-label={_t("voip|get_call_link")}>
11997
<ExternalLinkIcon />
@@ -132,7 +110,8 @@ export const CallGuestLinkButton: React.FC<{ room: Room }> = ({ room }) => {
132110
export const JoinRuleDialog: React.FC<{
133111
onFinished(): void;
134112
room: Room;
135-
}> = ({ room, onFinished }) => {
113+
canInvite: boolean;
114+
}> = ({ onFinished, room, canInvite }) => {
136115
const askToJoinEnabled = SettingsStore.getValue("feature_ask_to_join");
137116
const [isUpdating, setIsUpdating] = React.useState<undefined | JoinRule>(undefined);
138117
const changeJoinRule = useCallback(
@@ -156,7 +135,7 @@ export const JoinRuleDialog: React.FC<{
156135
<BaseDialog title={_t("update_room_access_modal|title")} onFinished={onFinished} className="mx_JoinRuleDialog">
157136
<p>{_t("update_room_access_modal|description")}</p>
158137
<div className="mx_JoinRuleDialogButtons">
159-
{askToJoinEnabled && (
138+
{askToJoinEnabled && canInvite && (
160139
<Button
161140
kind="secondary"
162141
className="mx_Dialog_nonDialogButton"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
Copyright 2024 The Matrix.org Foundation C.I.C.
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 { useCallback } from "react";
18+
import { EventTimeline, EventType, JoinRule, Room, RoomMemberEvent, RoomStateEvent } from "matrix-js-sdk/src/matrix";
19+
20+
import { useEventEmitterState } from "../useEventEmitter";
21+
22+
interface GuestAccessInformation {
23+
canChangeJoinRule: boolean;
24+
roomIsJoinableState: boolean;
25+
isRoomJoinable: () => boolean;
26+
canInvite: boolean;
27+
}
28+
29+
/**
30+
* Helper to retrieve the guest access related information for a room.
31+
* @param room
32+
* @returns The GuestAccessInformation which helps decide what options the user should be given.
33+
*/
34+
export const useGuestAccessInformation = (room: Room): GuestAccessInformation => {
35+
// We use the direct function only in functions triggered by user interaction to avoid computation on every render.
36+
const isRoomJoinable = useCallback(
37+
() =>
38+
room.getJoinRule() === JoinRule.Public ||
39+
(room.getJoinRule() === JoinRule.Knock && room.canInvite(room.myUserId)),
40+
[room],
41+
);
42+
43+
const roomIsJoinableState = useEventEmitterState(room, RoomStateEvent.Update, isRoomJoinable);
44+
const canInvite = useEventEmitterState(room, RoomStateEvent.Update, () => room.canInvite(room.myUserId));
45+
46+
const canChangeJoinRule = useEventEmitterState(
47+
room.client,
48+
RoomMemberEvent.PowerLevel,
49+
() =>
50+
room
51+
.getLiveTimeline()
52+
?.getState(EventTimeline.FORWARDS)
53+
?.maySendStateEvent(EventType.RoomJoinRules, room.myUserId) ?? false,
54+
);
55+
return { canChangeJoinRule, roomIsJoinableState, isRoomJoinable, canInvite };
56+
};

src/hooks/room/useRoomCall.ts

+11-12
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { JoinRule, Room } from "matrix-js-sdk/src/matrix";
17+
import { Room } from "matrix-js-sdk/src/matrix";
1818
import React, { useCallback, useEffect, useMemo, useState } from "react";
1919
import { CallType } from "matrix-js-sdk/src/webrtc/call";
2020

@@ -26,7 +26,7 @@ import { useWidgets } from "../../components/views/right_panel/RoomSummaryCard";
2626
import { WidgetType } from "../../widgets/WidgetType";
2727
import { useCall, useConnectionState, useParticipantCount } from "../useCall";
2828
import { useRoomMemberCount } from "../useRoomMembers";
29-
import { Call, ConnectionState, ElementCall } from "../../models/Call";
29+
import { ConnectionState, ElementCall } from "../../models/Call";
3030
import { placeCall } from "../../utils/room/placeCall";
3131
import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
3232
import { useRoomState } from "../useRoomState";
@@ -40,6 +40,7 @@ import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
4040
import { Action } from "../../dispatcher/actions";
4141
import { CallStore, CallStoreEvent } from "../../stores/CallStore";
4242
import { isVideoRoom } from "../../utils/video-rooms";
43+
import { useGuestAccessInformation } from "./useGuestAccessInformation";
4344

4445
export enum PlatformCallType {
4546
ElementCall,
@@ -170,17 +171,17 @@ export const useRoomCall = (
170171
useEffect(() => {
171172
updateWidgetState();
172173
}, [room, jitsiWidget, groupCall, updateWidgetState]);
173-
const [activeCalls, setActiveCalls] = useState<Call[]>(Array.from(CallStore.instance.activeCalls));
174-
useEventEmitter(CallStore.instance, CallStoreEvent.ActiveCalls, () => {
175-
setActiveCalls(Array.from(CallStore.instance.activeCalls));
176-
});
177174
const [canPinWidget, setCanPinWidget] = useState(false);
178175
const [widgetPinned, setWidgetPinned] = useState(false);
179176
// We only want to prompt to pin the widget if it's not element call based.
180177
const isECWidget = WidgetType.CALL.matches(widget?.type ?? "");
181178
const promptPinWidget = !isECWidget && canPinWidget && !widgetPinned;
182-
const userId = room.client.getUserId();
183-
const canInviteToRoom = userId ? room.canInvite(userId) : false;
179+
const activeCalls = useEventEmitterState(CallStore.instance, CallStoreEvent.ActiveCalls, () =>
180+
Array.from(CallStore.instance.activeCalls),
181+
);
182+
const { canChangeJoinRule, roomIsJoinableState } = useGuestAccessInformation(room);
183+
const canCallAlone = canChangeJoinRule || roomIsJoinableState;
184+
184185
const state = useMemo((): State => {
185186
if (activeCalls.find((call) => call.roomId != room.roomId)) {
186187
return State.Ongoing;
@@ -191,8 +192,6 @@ export const useRoomCall = (
191192
if (hasLegacyCall) {
192193
return State.Ongoing;
193194
}
194-
const canCallAlone =
195-
canInviteToRoom && (room.getJoinRule() === "public" || room.getJoinRule() === JoinRule.Knock);
196195
if (!(memberCount > 1 || canCallAlone)) {
197196
return State.NoOneHere;
198197
}
@@ -203,7 +202,7 @@ export const useRoomCall = (
203202
return State.NoCall;
204203
}, [
205204
activeCalls,
206-
canInviteToRoom,
205+
canCallAlone,
207206
hasGroupCall,
208207
hasJitsiWidget,
209208
hasLegacyCall,
@@ -212,7 +211,7 @@ export const useRoomCall = (
212211
mayEditWidgets,
213212
memberCount,
214213
promptPinWidget,
215-
room,
214+
room.roomId,
216215
]);
217216

218217
const voiceCallClick = useCallback(

test/components/views/rooms/RoomHeader/CallGuestLinkButton-test.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,8 @@ describe("<CallGuestLinkButton />", () => {
220220
describe("<JoinRuleDialog />", () => {
221221
const onFinished = jest.fn();
222222

223-
const getComponent = (room: Room) =>
224-
render(<JoinRuleDialog room={room} onFinished={onFinished} />, {
223+
const getComponent = (room: Room, canInvite: boolean = true) =>
224+
render(<JoinRuleDialog room={room} canInvite={canInvite} onFinished={onFinished} />, {
225225
wrapper: ({ children }) => (
226226
<SDKContext.Provider value={sdkContext}>
227227
<TooltipProvider>{children}</TooltipProvider>
@@ -238,6 +238,10 @@ describe("<CallGuestLinkButton />", () => {
238238
const { container } = getComponent(room);
239239
expect(getByText(container, "Ask to join")).toBeInTheDocument();
240240
});
241+
it("font show ask to join if feature is enabled but cannot invite", () => {
242+
getComponent(room, false);
243+
expect(screen.queryByText("Ask to join")).not.toBeInTheDocument();
244+
});
241245
it("doesn't show ask to join if feature is disabled", () => {
242246
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
243247
getComponent(room);

0 commit comments

Comments
 (0)