This repository has been archived by the owner on Sep 11, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 827
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Call Guest Access, give user the option to change the acces level so …
…they can generate a call link. (#12401) * Ask the user to change the room access settings if they click the create link button. Signed-off-by: Timo K <toger5@hotmail.de> * disable call button if appropriate. Signed-off-by: Timo K <toger5@hotmail.de> * Add tests Refactor tests to be in CallGuestLinkButton-test instead of the RoomHeader Signed-off-by: Timo K <toger5@hotmail.de> * add test for: no button if cannot change join rule and room not public nor knock Signed-off-by: Timo K <toger5@hotmail.de> * fix tests Signed-off-by: Timo K <toger5@hotmail.de> * add JoinRuleDialog tests Signed-off-by: Timo K <toger5@hotmail.de> * move spy into before each Signed-off-by: Timo K <toger5@hotmail.de> * Update src/i18n/strings/en_EN.json Co-authored-by: Robin <robin@robin.town> * remove inline css and update modal style Signed-off-by: Timo K <toger5@hotmail.de> * Update src/i18n/strings/en_EN.json Co-authored-by: Robin <robin@robin.town> * Update src/i18n/strings/en_EN.json Co-authored-by: Robin <robin@robin.town> * Invite state was not reactive. Changing power level did not update the ui. Signed-off-by: Timo K <toger5@hotmail.de> * linter Signed-off-by: Timo K <toger5@hotmail.de> * make useGuestAccessInformation use useRoomState Signed-off-by: Timo K <toger5@hotmail.de> * fix tests and simplify logic * fix tests * review Signed-off-by: Timo K <toger5@hotmail.de> --------- Signed-off-by: Timo K <toger5@hotmail.de> Co-authored-by: Robin <robin@robin.town>
- Loading branch information
Showing
11 changed files
with
588 additions
and
175 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.mx_JoinRuleDialog { | ||
.mx_JoinRuleDialogButtons { | ||
display: flex; | ||
column-gap: 5px; | ||
justify-content: center; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
167 changes: 167 additions & 0 deletions
167
src/components/views/rooms/RoomHeader/CallGuestLinkButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
/* | ||
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 { Icon as ExternalLinkIcon } from "@vector-im/compound-design-tokens/icons/link.svg"; | ||
import { Button, IconButton, Tooltip } from "@vector-im/compound-web"; | ||
import React, { useCallback } from "react"; | ||
import { logger } from "matrix-js-sdk/src/logger"; | ||
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 { calculateRoomVia } from "../../../../utils/permalinks/Permalinks"; | ||
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). | ||
* @param room | ||
* @returns Nothing if there is not the option to share a link (No guest_spa_url is set) or a button to open a dialog to share the link. | ||
*/ | ||
export const CallGuestLinkButton: React.FC<{ room: Room }> = ({ room }) => { | ||
const { canInviteGuests, guestSpaUrl, isRoomJoinable, canInvite } = useGuestAccessInformation(room); | ||
|
||
const generateCallLink = useCallback(() => { | ||
if (!isRoomJoinable()) throw new Error("Cannot create link for room that users can not join without invite."); | ||
if (!guestSpaUrl) throw new Error("No guest SPA url for external links provided."); | ||
const url = new URL(guestSpaUrl); | ||
url.pathname = "/room/"; | ||
// Set params for the sharable url | ||
url.searchParams.set("roomId", room.roomId); | ||
if (room.hasEncryptionStateEvent()) url.searchParams.set("perParticipantE2EE", "true"); | ||
for (const server of calculateRoomVia(room)) { | ||
url.searchParams.set("viaServers", server); | ||
} | ||
|
||
// Move params into hash | ||
url.hash = "/" + room.name + url.search; | ||
url.search = ""; | ||
|
||
logger.info("Generated element call external url:", url); | ||
return url; | ||
}, [guestSpaUrl, isRoomJoinable, room]); | ||
|
||
const showLinkModal = useCallback(() => { | ||
try { | ||
// generateCallLink throws if the invite rules are not met | ||
const target = generateCallLink(); | ||
Modal.createDialog(ShareDialog, { | ||
target, | ||
customTitle: _t("share|share_call"), | ||
subtitle: _t("share|share_call_subtitle"), | ||
}); | ||
} catch (e) { | ||
logger.error("Could not generate call link.", e); | ||
} | ||
}, [generateCallLink]); | ||
|
||
const shareClick = useCallback(() => { | ||
if (isRoomJoinable()) { | ||
showLinkModal(); | ||
} else { | ||
// the room needs to be set to public or knock to generate a link | ||
Modal.createDialog(JoinRuleDialog, { | ||
room, | ||
// If the user cannot invite the Knocking is not given as an option. | ||
canInvite, | ||
}).finished.then(() => { | ||
// we need to use the function here because the callback got called before the state was updated. | ||
if (isRoomJoinable()) showLinkModal(); | ||
}); | ||
} | ||
}, [isRoomJoinable, showLinkModal, room, canInvite]); | ||
|
||
return ( | ||
<> | ||
{canInviteGuests && ( | ||
<Tooltip label={_t("voip|get_call_link")}> | ||
<IconButton onClick={shareClick} aria-label={_t("voip|get_call_link")}> | ||
<ExternalLinkIcon /> | ||
</IconButton> | ||
</Tooltip> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
/** | ||
* A dialog to change the join rule of a room to public or knock. | ||
* @param room The room to change the join rule of. | ||
* @param onFinished Callback that is getting called if the dialog wants to close. | ||
*/ | ||
export const JoinRuleDialog: React.FC<{ | ||
onFinished(): void; | ||
room: Room; | ||
canInvite: boolean; | ||
}> = ({ onFinished, room, canInvite }) => { | ||
const askToJoinEnabled = SettingsStore.getValue("feature_ask_to_join"); | ||
const [isUpdating, setIsUpdating] = React.useState<undefined | JoinRule>(undefined); | ||
const changeJoinRule = useCallback( | ||
async (newRule: JoinRule) => { | ||
if (isUpdating !== undefined) return; | ||
setIsUpdating(newRule); | ||
await room.client.sendStateEvent( | ||
room.roomId, | ||
EventType.RoomJoinRules, | ||
{ | ||
join_rule: newRule, | ||
} as IJoinRuleEventContent, | ||
"", | ||
); | ||
// Show the dialog for a bit to give the user feedback | ||
setTimeout(() => onFinished(), 500); | ||
}, | ||
[isUpdating, onFinished, room.client, room.roomId], | ||
); | ||
return ( | ||
<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 && canInvite && ( | ||
<Button | ||
kind="secondary" | ||
className="mx_Dialog_nonDialogButton" | ||
disabled={isUpdating === JoinRule.Knock} | ||
onClick={() => changeJoinRule(JoinRule.Knock)} | ||
> | ||
{_t("action|ask_to_join")} | ||
</Button> | ||
)} | ||
<Button | ||
className="mx_Dialog_nonDialogButton" | ||
kind="destructive" | ||
disabled={isUpdating === JoinRule.Public} | ||
onClick={() => changeJoinRule(JoinRule.Public)} | ||
> | ||
{_t("common|public")} | ||
</Button> | ||
</div> | ||
<p>{_t("update_room_access_modal|dont_change_description")}</p> | ||
<div className="mx_JoinRuleDialogButtons"> | ||
<Button | ||
kind="tertiary" | ||
className="mx_Dialog_nonDialogButton" | ||
onClick={() => { | ||
if (isUpdating === undefined) onFinished(); | ||
}} | ||
> | ||
{_t("update_room_access_modal|no_change")} | ||
</Button> | ||
</div> | ||
</BaseDialog> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* | ||
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 { useMemo } from "react"; | ||
import { EventType, JoinRule, Room } from "matrix-js-sdk/src/matrix"; | ||
|
||
import SdkConfig from "../../SdkConfig"; | ||
import { useRoomState } from "../useRoomState"; | ||
|
||
interface GuestAccessInformation { | ||
canInviteGuests: boolean; | ||
guestSpaUrl?: string; | ||
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 => { | ||
const guestSpaUrl = useMemo(() => { | ||
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 { joinRule, canInvite, canChangeJoinRule } = useRoomState(room, (roomState) => ({ | ||
joinRule: room.getJoinRule(), | ||
canInvite: room.canInvite(room.myUserId), | ||
canChangeJoinRule: roomState.maySendStateEvent(EventType.RoomJoinRules, room.myUserId), | ||
})); | ||
const isRoomJoinable = useMemo( | ||
() => joinRule === JoinRule.Public || (joinRule === JoinRule.Knock && canInvite), | ||
[canInvite, joinRule], | ||
); | ||
const canInviteGuests = useMemo( | ||
() => (canChangeJoinRule || isRoomJoinable) && guestSpaUrl !== undefined, | ||
[canChangeJoinRule, isRoomJoinable, guestSpaUrl], | ||
); | ||
|
||
const isRoomJoinableFunction = (): boolean => | ||
room.getJoinRule() === JoinRule.Public || (joinRule === JoinRule.Knock && room.canInvite(room.myUserId)); | ||
return { canInviteGuests, guestSpaUrl, isRoomJoinable: isRoomJoinableFunction, canInvite }; | ||
}; |
Oops, something went wrong.