Merge branch 'develop' into johannes/webpack-5

This commit is contained in:
Johannes Marbach 2023-11-16 18:59:39 +01:00
commit d6ea051e46
40 changed files with 1956 additions and 1238 deletions

View file

@ -136,6 +136,9 @@ export class PosthogAnalytics {
private authenticationType: Signup["authenticationType"] = "Other";
private watchSettingRef?: string;
// Will be set when the matrixClient is passed to the analytics object (e.g. on login).
private currentCryptoBackend?: "Rust" | "Legacy" = undefined;
public static get instance(): PosthogAnalytics {
if (!this._instance) {
this._instance = new PosthogAnalytics(posthog);
@ -170,6 +173,7 @@ export class PosthogAnalytics {
SettingsStore.monitorSetting("layout", null);
SettingsStore.monitorSetting("useCompactLayout", null);
this.onLayoutUpdated();
this.updateCryptoSuperProperty();
}
private onLayoutUpdated = (): void => {
@ -278,6 +282,8 @@ export class PosthogAnalytics {
this.registerSuperProperties(this.platformSuperProperties);
}
this.anonymity = anonymity;
// update anyhow, no-op if not enabled or Disabled.
this.updateCryptoSuperProperty();
}
private static getRandomAnalyticsId(): string {
@ -367,7 +373,28 @@ export class PosthogAnalytics {
this.registerSuperProperties(this.platformSuperProperties);
}
private updateCryptoSuperProperty(): void {
if (!this.enabled || this.anonymity === Anonymity.Disabled) return;
// Update super property for cryptoSDK in posthog.
// This property will be subsequently passed in every event.
if (this.currentCryptoBackend) {
this.registerSuperProperties({ cryptoSDK: this.currentCryptoBackend });
}
}
public async updateAnonymityFromSettings(client: MatrixClient, pseudonymousOptIn: boolean): Promise<void> {
// Temporary until we have migration code to switch crypto sdk.
if (client.getCrypto()) {
const cryptoVersion = client.getCrypto()!.getVersion();
// version for rust is something like "Rust SDK 0.6.0 (9c6b550), Vodozemac 0.5.0"
// for legacy it will be 'Olm x.x.x"
if (cryptoVersion.includes("Rust SDK")) {
this.currentCryptoBackend = "Rust";
} else {
this.currentCryptoBackend = "Legacy";
}
}
// Update this.anonymity based on the user's analytics opt-in settings
const anonymity = pseudonymousOptIn ? Anonymity.Pseudonymous : Anonymity.Disabled;
this.setAnonymity(anonymity);
@ -379,7 +406,8 @@ export class PosthogAnalytics {
}
if (anonymity !== Anonymity.Disabled) {
await PosthogAnalytics.instance.updatePlatformSuperProperties();
await this.updatePlatformSuperProperties();
this.updateCryptoSuperProperty();
}
}

View file

@ -16,7 +16,6 @@ limitations under the License.
import React, { useContext } from "react";
import { Room } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { IProps as IContextMenuProps } from "../../structures/ContextMenu";
import IconizedContextMenu, {
@ -30,7 +29,6 @@ import { ButtonEvent } from "../elements/AccessibleButton";
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
import dis from "../../../dispatcher/dispatcher";
import RoomListActions from "../../../actions/RoomListActions";
import { EchoChamber } from "../../../stores/local-echo/EchoChamber";
import { RoomNotifState } from "../../../RoomNotifs";
import Modal from "../../../Modal";
@ -52,6 +50,7 @@ import { SdkContextClass } from "../../../contexts/SDKContext";
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
import { UIComponent } from "../../../settings/UIFeature";
import { DeveloperToolsOption } from "./DeveloperToolsOption";
import { tagRoom } from "../../../utils/room/tagRoom";
interface IProps extends IContextMenuProps {
room: Room;
@ -333,15 +332,7 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
ev.preventDefault();
ev.stopPropagation();
if (tagId === DefaultTagID.Favourite || tagId === DefaultTagID.LowPriority) {
const inverseTag = tagId === DefaultTagID.Favourite ? DefaultTagID.LowPriority : DefaultTagID.Favourite;
const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId);
const removeTag = isApplied ? tagId : inverseTag;
const addTag = isApplied ? null : tagId;
dis.dispatch(RoomListActions.tagRoom(cli, room, removeTag, addTag, 0));
} else {
logger.warn(`Unexpected tag ${tagId} applied to ${room.roomId}`);
}
tagRoom(room, tagId);
const action = getKeyBindingsManager().getAccessibilityAction(ev as React.KeyboardEvent);
switch (action) {

View file

@ -90,7 +90,7 @@ const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
<div className="mx_BaseCard_header">
{backButton}
{closeButton}
{header}
<div className="mx_BaseCard_headerProp">{header}</div>
</div>
)}
{children}

View file

@ -16,25 +16,35 @@ limitations under the License.
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
import classNames from "classnames";
import { EventType, JoinRule, Room } from "matrix-js-sdk/src/matrix";
import { Badge, Heading, Text, Tooltip } from "@vector-im/compound-web";
import { MenuItem, Tooltip, Separator, ToggleMenuItem, Text, Badge, Heading } from "@vector-im/compound-web";
import { Icon as SearchIcon } from "@vector-im/compound-design-tokens/icons/search.svg";
import { Icon as FavouriteIcon } from "@vector-im/compound-design-tokens/icons/favourite-off.svg";
import { Icon as UserAddIcon } from "@vector-im/compound-design-tokens/icons/user-add.svg";
import { Icon as UserProfileSolidIcon } from "@vector-im/compound-design-tokens/icons/user-profile-solid.svg";
import { Icon as LinkIcon } from "@vector-im/compound-design-tokens/icons/link.svg";
import { Icon as SettingsIcon } from "@vector-im/compound-design-tokens/icons/settings.svg";
import { Icon as ExportArchiveIcon } from "@vector-im/compound-design-tokens/icons/export-archive.svg";
import { Icon as LeaveIcon } from "@vector-im/compound-design-tokens/icons/leave.svg";
import { Icon as FilesIcon } from "@vector-im/compound-design-tokens/icons/files.svg";
import { Icon as PollsIcon } from "@vector-im/compound-design-tokens/icons/polls.svg";
import { Icon as PinIcon } from "@vector-im/compound-design-tokens/icons/pin-off.svg";
import { Icon as LockIcon } from "@vector-im/compound-design-tokens/icons/lock.svg";
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 MatrixClientContext from "../../../contexts/MatrixClientContext";
import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
import BaseCard, { Group } from "./BaseCard";
import { _t } from "../../../languageHandler";
import RoomAvatar from "../avatars/RoomAvatar";
import AccessibleButton, { ButtonEvent, IAccessibleButtonProps } from "../elements/AccessibleButton";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
import Modal from "../../../Modal";
import ShareDialog from "../dialogs/ShareDialog";
import { useEventEmitter } from "../../../hooks/useEventEmitter";
import { useEventEmitter, useEventEmitterState } from "../../../hooks/useEventEmitter";
import WidgetUtils from "../../../utils/WidgetUtils";
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore";
@ -47,7 +57,6 @@ import RoomContext from "../../../contexts/RoomContext";
import { UIComponent, UIFeature } from "../../../settings/UIFeature";
import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
import { WidgetContextMenu } from "../context_menus/WidgetContextMenu";
import { useRoomMemberCount } from "../../../hooks/useRoomMembers";
import { useFeatureEnabled } from "../../../hooks/useSettings";
import { usePinnedEvents } from "./PinnedMessagesCard";
import { Container, MAX_PINNED, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
@ -59,6 +68,11 @@ import PosthogTrackers from "../../../PosthogTrackers";
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
import { PollHistoryDialog } from "../dialogs/PollHistoryDialog";
import { Flex } from "../../utils/Flex";
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
import { DefaultTagID } from "../../../stores/room-list/models";
import { tagRoom } from "../../../utils/room/tagRoom";
import { canInviteTo } from "../../../utils/room/canInviteTo";
import { inviteToRoom } from "../../../utils/room/inviteToRoom";
import { useAccountData } from "../../../hooks/useAccountData";
import { useRoomState } from "../../../hooks/useRoomState";
@ -73,23 +87,6 @@ interface IAppsSectionProps {
room: Room;
}
interface IButtonProps extends IAccessibleButtonProps {
className: string;
onClick(ev: ButtonEvent): void;
}
const Button: React.FC<IButtonProps> = ({ children, className, onClick, ...props }) => {
return (
<AccessibleButton
{...props}
className={classNames("mx_BaseCard_Button mx_RoomSummaryCard_Button", className)}
onClick={onClick}
>
{children}
</AccessibleButton>
);
};
export const useWidgets = (room: Room): IApp[] => {
const [apps, setApps] = useState<IApp[]>(() => WidgetStore.instance.getApps(room.roomId));
@ -263,11 +260,6 @@ const AppsSection: React.FC<IAppsSectionProps> = ({ room }) => {
);
};
const onRoomMembersClick = (ev: ButtonEvent): void => {
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.RoomMemberList }, true);
PosthogTrackers.trackInteraction("WebRightPanelRoomInfoPeopleButton", ev);
};
const onRoomFilesClick = (): void => {
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.FilePanel }, true);
};
@ -304,6 +296,18 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, on
});
};
const onLeaveRoomClick = (): void => {
defaultDispatcher.dispatch({
action: "leave_room",
room_id: room.roomId,
});
};
const onRoomMembersClick = (ev: ButtonEvent): void => {
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.RoomMemberList }, true);
PosthogTrackers.trackInteraction("WebRightPanelRoomInfoPeopleButton", ev);
};
const isRoomEncrypted = useIsEncrypted(cli, room);
const roomContext = useContext(RoomContext);
const e2eStatus = roomContext.e2eStatus;
@ -383,10 +387,14 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, on
</header>
);
const memberCount = useRoomMemberCount(room);
const pinningEnabled = useFeatureEnabled("feature_pinning");
const pinCount = usePinnedEvents(pinningEnabled ? room : undefined)?.length;
const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () =>
RoomListStore.instance.getTagsForRoom(room),
);
const isFavorite = roomTags.includes(DefaultTagID.Favourite);
return (
<BaseCard header={null} className="mx_RoomSummaryCard" onClose={onClose}>
<Flex
@ -417,43 +425,58 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, on
</Flex>
{header}
<Group title={_t("common|about")} className="mx_RoomSummaryCard_aboutGroup">
<Button className="mx_RoomSummaryCard_icon_people" onClick={onRoomMembersClick}>
{_t("common|people")}
<span className="mx_BaseCard_Button_sublabel">{memberCount}</span>
</Button>
{!isVideoRoom && (
<Button className="mx_RoomSummaryCard_icon_files" onClick={onRoomFilesClick}>
{_t("right_panel|files_button")}
</Button>
)}
{!isVideoRoom && (
<Button className="mx_RoomSummaryCard_icon_poll" onClick={onRoomPollHistoryClick}>
{_t("right_panel|polls_button")}
</Button>
)}
{pinningEnabled && !isVideoRoom && (
<Button className="mx_RoomSummaryCard_icon_pins" onClick={onRoomPinsClick}>
{_t("right_panel|pinned_messages_button")}
{pinCount > 0 && <span className="mx_BaseCard_Button_sublabel">{pinCount}</span>}
</Button>
)}
{!isVideoRoom && (
<Button className="mx_RoomSummaryCard_icon_export" onClick={onRoomExportClick}>
{_t("right_panel|export_chat_button")}
</Button>
)}
<Button
data-testid="shareRoomButton"
className="mx_RoomSummaryCard_icon_share"
onClick={onShareRoomClick}
>
{_t("right_panel|share_button")}
</Button>
<Button className="mx_RoomSummaryCard_icon_settings" onClick={onRoomSettingsClick}>
{_t("right_panel|settings_button")}
</Button>
</Group>
<Separator />
<ToggleMenuItem
Icon={FavouriteIcon}
label={_t("room|context_menu|favourite")}
checked={isFavorite}
onChange={() => tagRoom(room, DefaultTagID.Favourite)}
/>
<MenuItem
Icon={UserAddIcon}
label={_t("action|invite")}
disabled={!canInviteTo(room)}
onClick={() => inviteToRoom(room)}
/>
<MenuItem Icon={LinkIcon} label={_t("action|copy_link")} onClick={onShareRoomClick} />
<MenuItem Icon={SettingsIcon} label={_t("common|settings")} onClick={onRoomSettingsClick} />
<Separator />
<MenuItem
// this icon matches the legacy implementation
// and is a short term solution until legacy room header is removed
Icon={UserProfileSolidIcon}
label={_t("common|people")}
onClick={onRoomMembersClick}
/>
{!isVideoRoom && (
<>
<MenuItem Icon={FilesIcon} label={_t("right_panel|files_button")} onClick={onRoomFilesClick} />
<MenuItem
Icon={PollsIcon}
label={_t("right_panel|polls_button")}
onClick={onRoomPollHistoryClick}
/>
{pinningEnabled && (
<MenuItem
Icon={PinIcon}
label={_t("right_panel|pinned_messages_button")}
onClick={onRoomPinsClick}
>
<Text as="span" size="sm">
{pinCount}
</Text>
</MenuItem>
)}
<MenuItem Icon={ExportArchiveIcon} label={_t("export_chat|title")} onClick={onRoomExportClick} />
</>
)}
<Separator />
<MenuItem Icon={LeaveIcon} kind="critical" label={_t("action|leave_room")} onClick={onLeaveRoomClick} />
{SettingsStore.getValue(UIFeature.Widgets) &&
!isVideoRoom &&

View file

@ -16,7 +16,7 @@ limitations under the License.
import React, { useEffect, useMemo, useState } from "react";
import { Body as BodyText, IconButton, Tooltip } from "@vector-im/compound-web";
import { Icon as VideoCallIcon } from "@vector-im/compound-design-tokens/icons/video-call.svg";
import { Icon as VideoCallIcon } from "@vector-im/compound-design-tokens/icons/video-call-solid.svg";
import { Icon as VoiceCallIcon } from "@vector-im/compound-design-tokens/icons/voice-call.svg";
import { Icon as ThreadsIcon } from "@vector-im/compound-design-tokens/icons/threads-solid.svg";
import { Icon as NotificationsIcon } from "@vector-im/compound-design-tokens/icons/notifications-solid.svg";
@ -49,6 +49,7 @@ import RoomAvatar from "../avatars/RoomAvatar";
import { formatCount } from "../../../utils/FormattingUtils";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import { Linkify, topicToHtml } from "../../../HtmlUtils";
import PosthogTrackers from "../../../PosthogTrackers";
/**
* A helper to transform a notification color to the what the Compound Icon Button
@ -193,6 +194,15 @@ export default function RoomHeader({
</Tooltip>
);
})}
<Tooltip label={!videoCallDisabledReason ? _t("voip|video_call") : videoCallDisabledReason!}>
<IconButton
disabled={!!videoCallDisabledReason}
aria-label={!videoCallDisabledReason ? _t("voip|video_call") : videoCallDisabledReason!}
onClick={videoCallClick}
>
<VideoCallIcon />
</IconButton>
</Tooltip>
{!useElementCallExclusively && (
<Tooltip label={!voiceCallDisabledReason ? _t("voip|voice_call") : voiceCallDisabledReason!}>
<IconButton
@ -204,21 +214,14 @@ export default function RoomHeader({
</IconButton>
</Tooltip>
)}
<Tooltip label={!videoCallDisabledReason ? _t("voip|video_call") : videoCallDisabledReason!}>
<IconButton
disabled={!!videoCallDisabledReason}
aria-label={!videoCallDisabledReason ? _t("voip|video_call") : videoCallDisabledReason!}
onClick={videoCallClick}
>
<VideoCallIcon />
</IconButton>
</Tooltip>
<Tooltip label={_t("common|threads")}>
<IconButton
indicator={notificationColorToIndicator(threadNotifications)}
onClick={(evt) => {
evt.stopPropagation();
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.ThreadPanel);
PosthogTrackers.trackInteraction("WebRoomHeaderButtonsThreadsButton", evt);
}}
aria-label={_t("common|threads")}
>

View file

@ -418,7 +418,6 @@
"spaceinvaders_message": "sends space invaders"
},
"common": {
"about": "About",
"access_token": "Access Token",
"accessibility": "Accessibility",
"advanced": "Advanced",
@ -1854,8 +1853,6 @@
"room_summary_card": {
"title": "Room info"
},
"settings_button": "Room settings",
"share_button": "Share room",
"thread_list": {
"context_menu_label": "Thread options"
},

View file

@ -623,6 +623,7 @@ export class ElementCall extends Call {
public static readonly MEMBER_EVENT_TYPE = new NamespacedValue(null, EventType.GroupCallMemberPrefix);
public readonly STUCK_DEVICE_TIMEOUT_MS = 1000 * 60 * 60; // 1 hour
private settingsStoreCallEncryptionWatcher: string | null = null;
private terminationTimer: number | null = null;
private _layout = Layout.Tile;
public get layout(): Layout {
@ -633,11 +634,7 @@ export class ElementCall extends Call {
this.emit(CallEvent.Layout, value);
}
private static createOrGetCallWidget(roomId: string, client: MatrixClient): IApp {
const ecWidget = WidgetStore.instance.getApps(roomId).find((app) => WidgetType.CALL.matches(app.type));
if (ecWidget) {
return ecWidget;
}
private static generateWidgetUrl(client: MatrixClient, roomId: string): URL {
const accountAnalyticsData = client.getAccountData(PosthogAnalytics.ANALYTICS_EVENT_TYPE);
// The analyticsID is passed directly to element call (EC) since this codepath is only for EC and no other widget.
// We really don't want the same analyticID's for the EC and EW posthog instances (Data on posthog should be limited/anonymized as much as possible).
@ -683,6 +680,20 @@ export class ElementCall extends Call {
const url = new URL(SdkConfig.get("element_call").url ?? DEFAULTS.element_call.url!);
url.pathname = "/room";
url.hash = `#?${params.toString()}`;
return url;
}
private static createOrGetCallWidget(roomId: string, client: MatrixClient): IApp {
const ecWidget = WidgetStore.instance.getApps(roomId).find((app) => WidgetType.CALL.matches(app.type));
const url = ElementCall.generateWidgetUrl(client, roomId);
if (ecWidget) {
// always update the url because even if the widget is already created
// we might have settings changes that update the widget.
ecWidget.url = url.toString();
return ecWidget;
}
// To use Element Call without touching room state, we create a virtual
// widget (one that doesn't have a corresponding state event)
@ -698,12 +709,21 @@ export class ElementCall extends Call {
roomId,
);
}
private onCallEncryptionSettingsChange(): void {
this.widget.url = ElementCall.generateWidgetUrl(this.client, this.roomId).toString();
}
private constructor(public session: MatrixRTCSession, widget: IApp, client: MatrixClient) {
super(widget, client);
this.session.on(MatrixRTCSessionEvent.MembershipsChanged, this.onMembershipChanged);
this.client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, this.onRTCSessionEnded);
SettingsStore.watchSetting(
"feature_disable_call_per_sender_encryption",
null,
this.onCallEncryptionSettingsChange.bind(this),
);
this.updateParticipants();
}
@ -784,6 +804,9 @@ export class ElementCall extends Call {
this.session.off(MatrixRTCSessionEvent.MembershipsChanged, this.onMembershipChanged);
this.client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionEnded, this.onRTCSessionEnded);
if (this.settingsStoreCallEncryptionWatcher) {
SettingsStore.unwatchSetting(this.settingsStoreCallEncryptionWatcher);
}
if (this.terminationTimer !== null) {
clearTimeout(this.terminationTimer);
this.terminationTimer = null;

View file

@ -0,0 +1,33 @@
/*
Copyright 2023 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 { JoinRule, Room } from "matrix-js-sdk/src/matrix";
import { shouldShowComponent } from "../../customisations/helpers/UIComponents";
import { UIComponent } from "../../settings/UIFeature";
/**
* Can a user invite new members to the room
* @param room
* @returns whether the user can invite new members to the room
*/
export function canInviteTo(room: Room): boolean {
const client = room.client;
const canInvite =
!!room.canInvite(client.getSafeUserId()) || !!(room.isSpaceRoom() && room.getJoinRule() === JoinRule.Public);
return canInvite && room.getMyMembership() === "join" && shouldShowComponent(UIComponent.InviteUsers);
}

View file

@ -0,0 +1,36 @@
/*
Copyright 2023 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 { Room } from "matrix-js-sdk/src/matrix";
import dis from "../../dispatcher/dispatcher";
/**
* Invite to a room and prompts guests to registers
* @param room
*/
export function inviteToRoom(room: Room): void {
if (room.client.isGuest()) {
dis.dispatch({ action: "require_registration" });
return;
}
// open the room inviter
dis.dispatch({
action: "view_invite",
roomId: room.roomId,
});
}

40
src/utils/room/tagRoom.ts Normal file
View file

@ -0,0 +1,40 @@
/*
Copyright 2023 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 { Room } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import RoomListStore from "../../stores/room-list/RoomListStore";
import { DefaultTagID, TagID } from "../../stores/room-list/models";
import RoomListActions from "../../actions/RoomListActions";
import dis from "../../dispatcher/dispatcher";
/**
* Toggle tag for a given room
* @param room The room to tag
* @param tagId The tag to invert
*/
export function tagRoom(room: Room, tagId: TagID): void {
if (tagId === DefaultTagID.Favourite || tagId === DefaultTagID.LowPriority) {
const inverseTag = tagId === DefaultTagID.Favourite ? DefaultTagID.LowPriority : DefaultTagID.Favourite;
const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId);
const removeTag = isApplied ? tagId : inverseTag;
const addTag = isApplied ? null : tagId;
dis.dispatch(RoomListActions.tagRoom(room.client, room, removeTag, addTag, 0));
} else {
logger.warn(`Unexpected tag ${tagId} applied to ${room.roomId}`);
}
}