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

Commit

Permalink
Invite state was not reactive.
Browse files Browse the repository at this point in the history
Changing power level did not update the ui.

Signed-off-by: Timo K <toger5@hotmail.de>
  • Loading branch information
toger5 committed Apr 7, 2024
1 parent 22be08c commit a4c9cac
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 49 deletions.
6 changes: 3 additions & 3 deletions src/components/views/context_menus/RoomContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ limitations under the License.
*/

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

import { IProps as IContextMenuProps } from "../../structures/ContextMenu";
Expand Down Expand Up @@ -117,9 +117,9 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
const elementCallVideoRoomsEnabled = useFeatureEnabled("feature_element_call_video_rooms");
const isVideoRoom =
videoRoomsEnabled && (room.isElementVideoRoom() || (elementCallVideoRoomsEnabled && room.isCallRoom()));

const canInvite = useEventEmitterState(cli, RoomMemberEvent.PowerLevel, () => room.canInvite(cli.getUserId()!));
let inviteOption: JSX.Element | undefined;
if (room.canInvite(cli.getUserId()!) && !isDm && shouldShowComponent(UIComponent.InviteUsers)) {
if (canInvite && !isDm && shouldShowComponent(UIComponent.InviteUsers)) {
const onInviteClick = (ev: ButtonEvent): void => {
ev.preventDefault();
ev.stopPropagation();
Expand Down
5 changes: 3 additions & 2 deletions src/components/views/right_panel/RoomSummaryCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { Icon as LockIcon } from "@vector-im/compound-design-tokens/icons/lock-s
import { Icon as LockOffIcon } from "@vector-im/compound-design-tokens/icons/lock-off.svg";
import { Icon as PublicIcon } from "@vector-im/compound-design-tokens/icons/public.svg";
import { Icon as ErrorIcon } from "@vector-im/compound-design-tokens/icons/error.svg";
import { EventType, JoinRule, Room } from "matrix-js-sdk/src/matrix";
import { EventType, JoinRule, Room, RoomMemberEvent, RoomStateEvent } from "matrix-js-sdk/src/matrix";

Check failure on line 35 in src/components/views/right_panel/RoomSummaryCard.tsx

View workflow job for this annotation

GitHub Actions / Typescript Syntax Check

'RoomMemberEvent' is declared but its value is never read.

Check failure on line 35 in src/components/views/right_panel/RoomSummaryCard.tsx

View workflow job for this annotation

GitHub Actions / ESLint

'RoomMemberEvent' is defined but never used

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

return (
Expand Down Expand Up @@ -439,7 +440,7 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, on
<MenuItem
Icon={UserAddIcon}
label={_t("action|invite")}
disabled={!canInviteTo(room)}
disabled={!canInviteToState}
onSelect={() => inviteToRoom(room)}
/>
<MenuItem Icon={LinkIcon} label={_t("action|copy_link")} onSelect={onShareRoomClick} />
Expand Down
39 changes: 9 additions & 30 deletions src/components/views/rooms/RoomHeader/CallGuestLinkButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,16 @@ import { Icon as ExternalLinkIcon } from "@vector-im/compound-design-tokens/icon
import { Button, IconButton, Tooltip } from "@vector-im/compound-web";
import React, { useCallback, useMemo } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import {
EventTimeline,
EventType,
IJoinRuleEventContent,
JoinRule,
Room,
RoomStateEvent,
} from "matrix-js-sdk/src/matrix";
import { EventType, IJoinRuleEventContent, JoinRule, Room } from "matrix-js-sdk/src/matrix";

import Modal from "../../../../Modal";
import ShareDialog from "../../dialogs/ShareDialog";
import { _t } from "../../../../languageHandler";
import SettingsStore from "../../../../settings/SettingsStore";
import SdkConfig from "../../../../SdkConfig";
import { calculateRoomVia } from "../../../../utils/permalinks/Permalinks";
import { useEventEmitterState } from "../../../../hooks/useEventEmitter";
import BaseDialog from "../../dialogs/BaseDialog";
import { useGuestAccessInformation } from "../../../../hooks/room/useGuestAccessInformation";

/**
* 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).
Expand All @@ -45,23 +38,7 @@ export const CallGuestLinkButton: React.FC<{ room: Room }> = ({ room }) => {
return SdkConfig.get("element_call").guest_spa_url;
}, []);

// We use the direct function only in functions triggered by user interaction to avoid computation on every render.
const isRoomJoinable = useCallback(
() => room.getJoinRule() === JoinRule.Public || room.getJoinRule() === JoinRule.Knock,
[room],
);

const isRoomJoinableState = useEventEmitterState(room, RoomStateEvent.Events, isRoomJoinable);

const canChangeJoinRule = useEventEmitterState(
room,
RoomStateEvent.Events,
() =>
room
.getLiveTimeline()
?.getState(EventTimeline.FORWARDS)
?.maySendStateEvent(EventType.RoomJoinRules, room.myUserId) ?? false,
);
const { canChangeJoinRule, roomIsJoinableState, isRoomJoinable, canInvite } = useGuestAccessInformation(room);

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

return (
<>
{(canChangeJoinRule || isRoomJoinableState) && guestSpaUrl && (
{(canChangeJoinRule || roomIsJoinableState) && guestSpaUrl && (
<Tooltip label={_t("voip|get_call_link")}>
<IconButton onClick={shareClick} aria-label={_t("voip|get_call_link")}>
<ExternalLinkIcon />
Expand All @@ -132,7 +110,8 @@ export const CallGuestLinkButton: React.FC<{ room: Room }> = ({ room }) => {
export const JoinRuleDialog: React.FC<{
onFinished(): void;
room: Room;
}> = ({ room, onFinished }) => {
canInvite: boolean;
}> = ({ onFinished, room, canInvite }) => {
const askToJoinEnabled = SettingsStore.getValue("feature_ask_to_join");
const [isUpdating, setIsUpdating] = React.useState<undefined | JoinRule>(undefined);
const changeJoinRule = useCallback(
Expand All @@ -156,7 +135,7 @@ export const JoinRuleDialog: React.FC<{
<BaseDialog title={_t("update_room_access_modal|title")} onFinished={onFinished} className="mx_JoinRuleDialog">
<p>{_t("update_room_access_modal|description")}</p>
<div className="mx_JoinRuleDialogButtons">
{askToJoinEnabled && (
{askToJoinEnabled && canInvite && (
<Button
kind="secondary"
className="mx_Dialog_nonDialogButton"
Expand Down
56 changes: 56 additions & 0 deletions src/hooks/room/useGuestAccessInformation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
Copyright 2024 The Matrix.org Foundation C.I.C.
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 { useCallback } from "react";
import { EventTimeline, EventType, JoinRule, Room, RoomMemberEvent, RoomStateEvent } from "matrix-js-sdk/src/matrix";

import { useEventEmitterState } from "../useEventEmitter";

interface GuestAccessInformation {
canChangeJoinRule: boolean;
roomIsJoinableState: boolean;
isRoomJoinable: () => boolean;
canInvite: boolean;
}

/**
* Helper to retrieve the guest access related information for a room.
* @param room
* @returns The GuestAccessInformation which helps decide what options the user should be given.
*/
export const useGuestAccessInformation = (room: Room): GuestAccessInformation => {
// We use the direct function only in functions triggered by user interaction to avoid computation on every render.
const isRoomJoinable = useCallback(
() =>
room.getJoinRule() === JoinRule.Public ||
(room.getJoinRule() === JoinRule.Knock && room.canInvite(room.myUserId)),
[room],
);

const roomIsJoinableState = useEventEmitterState(room, RoomStateEvent.Update, isRoomJoinable);
const canInvite = useEventEmitterState(room, RoomStateEvent.Update, () => room.canInvite(room.myUserId));

const canChangeJoinRule = useEventEmitterState(
room.client,
RoomMemberEvent.PowerLevel,
() =>
room
.getLiveTimeline()
?.getState(EventTimeline.FORWARDS)
?.maySendStateEvent(EventType.RoomJoinRules, room.myUserId) ?? false,
);
return { canChangeJoinRule, roomIsJoinableState, isRoomJoinable, canInvite };
};
23 changes: 11 additions & 12 deletions src/hooks/room/useRoomCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

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

Expand All @@ -26,7 +26,7 @@ import { useWidgets } from "../../components/views/right_panel/RoomSummaryCard";
import { WidgetType } from "../../widgets/WidgetType";
import { useCall, useConnectionState, useParticipantCount } from "../useCall";
import { useRoomMemberCount } from "../useRoomMembers";
import { Call, ConnectionState, ElementCall } from "../../models/Call";
import { ConnectionState, ElementCall } from "../../models/Call";
import { placeCall } from "../../utils/room/placeCall";
import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
import { useRoomState } from "../useRoomState";
Expand All @@ -40,6 +40,7 @@ import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
import { Action } from "../../dispatcher/actions";
import { CallStore, CallStoreEvent } from "../../stores/CallStore";
import { isVideoRoom } from "../../utils/video-rooms";
import { useGuestAccessInformation } from "./useGuestAccessInformation";

export enum PlatformCallType {
ElementCall,
Expand Down Expand Up @@ -170,17 +171,17 @@ export const useRoomCall = (
useEffect(() => {
updateWidgetState();
}, [room, jitsiWidget, groupCall, updateWidgetState]);
const [activeCalls, setActiveCalls] = useState<Call[]>(Array.from(CallStore.instance.activeCalls));
useEventEmitter(CallStore.instance, CallStoreEvent.ActiveCalls, () => {
setActiveCalls(Array.from(CallStore.instance.activeCalls));
});
const [canPinWidget, setCanPinWidget] = useState(false);
const [widgetPinned, setWidgetPinned] = useState(false);
// We only want to prompt to pin the widget if it's not element call based.
const isECWidget = WidgetType.CALL.matches(widget?.type ?? "");
const promptPinWidget = !isECWidget && canPinWidget && !widgetPinned;
const userId = room.client.getUserId();
const canInviteToRoom = userId ? room.canInvite(userId) : false;
const activeCalls = useEventEmitterState(CallStore.instance, CallStoreEvent.ActiveCalls, () =>
Array.from(CallStore.instance.activeCalls),
);
const { canChangeJoinRule, roomIsJoinableState } = useGuestAccessInformation(room);
const canCallAlone = canChangeJoinRule || roomIsJoinableState;

const state = useMemo((): State => {
if (activeCalls.find((call) => call.roomId != room.roomId)) {
return State.Ongoing;
Expand All @@ -191,8 +192,6 @@ export const useRoomCall = (
if (hasLegacyCall) {
return State.Ongoing;
}
const canCallAlone =
canInviteToRoom && (room.getJoinRule() === "public" || room.getJoinRule() === JoinRule.Knock);
if (!(memberCount > 1 || canCallAlone)) {
return State.NoOneHere;
}
Expand All @@ -203,7 +202,7 @@ export const useRoomCall = (
return State.NoCall;
}, [
activeCalls,
canInviteToRoom,
canCallAlone,
hasGroupCall,
hasJitsiWidget,
hasLegacyCall,
Expand All @@ -212,7 +211,7 @@ export const useRoomCall = (
mayEditWidgets,
memberCount,
promptPinWidget,
room,
room.roomId,
]);

const voiceCallClick = useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,8 @@ describe("<CallGuestLinkButton />", () => {
describe("<JoinRuleDialog />", () => {
const onFinished = jest.fn();

const getComponent = (room: Room) =>
render(<JoinRuleDialog room={room} onFinished={onFinished} />, {
const getComponent = (room: Room, canInvite: boolean = true) =>
render(<JoinRuleDialog room={room} canInvite={canInvite} onFinished={onFinished} />, {
wrapper: ({ children }) => (
<SDKContext.Provider value={sdkContext}>
<TooltipProvider>{children}</TooltipProvider>
Expand All @@ -238,6 +238,10 @@ describe("<CallGuestLinkButton />", () => {
const { container } = getComponent(room);
expect(getByText(container, "Ask to join")).toBeInTheDocument();
});
it("font show ask to join if feature is enabled but cannot invite", () => {
getComponent(room, false);
expect(screen.queryByText("Ask to join")).not.toBeInTheDocument();
});
it("doesn't show ask to join if feature is disabled", () => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
getComponent(room);
Expand Down

0 comments on commit a4c9cac

Please sign in to comment.