Apply prettier formatting

This commit is contained in:
Michael Weimann 2022-12-12 12:24:14 +01:00
parent 1cac306093
commit 526645c791
No known key found for this signature in database
GPG key ID: 53F535A266BB9584
1576 changed files with 65385 additions and 62478 deletions

View file

@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { forwardRef, ReactNode, KeyboardEvent, Ref } from 'react';
import classNames from 'classnames';
import React, { forwardRef, ReactNode, KeyboardEvent, Ref } from "react";
import classNames from "classnames";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import { _t } from "../../../languageHandler";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import RightPanelStore from '../../../stores/right-panel/RightPanelStore';
import { backLabelForPhase } from '../../../stores/right-panel/RightPanelStorePhases';
import { CardContext } from './context';
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import { backLabelForPhase } from "../../../stores/right-panel/RightPanelStorePhases";
import { CardContext } from "./context";
interface IProps {
header?: ReactNode;
@ -43,64 +43,58 @@ interface IGroupProps {
}
export const Group: React.FC<IGroupProps> = ({ className, title, children }) => {
return <div className={classNames("mx_BaseCard_Group", className)}>
<h1>{ title }</h1>
{ children }
</div>;
return (
<div className={classNames("mx_BaseCard_Group", className)}>
<h1>{title}</h1>
{children}
</div>
);
};
const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(({
closeLabel,
onClose,
onBack,
className,
header,
footer,
withoutScrollContainer,
children,
onKeyDown,
}, ref) => {
let backButton;
const cardHistory = RightPanelStore.instance.roomPhaseHistory;
if (cardHistory.length > 1) {
const prevCard = cardHistory[cardHistory.length - 2];
const onBackClick = (ev: ButtonEvent) => {
onBack?.(ev);
RightPanelStore.instance.popCard();
};
const label = backLabelForPhase(prevCard.phase) ?? _t("Back");
backButton = <AccessibleButton className="mx_BaseCard_back" onClick={onBackClick} title={label} />;
}
const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
({ closeLabel, onClose, onBack, className, header, footer, withoutScrollContainer, children, onKeyDown }, ref) => {
let backButton;
const cardHistory = RightPanelStore.instance.roomPhaseHistory;
if (cardHistory.length > 1) {
const prevCard = cardHistory[cardHistory.length - 2];
const onBackClick = (ev: ButtonEvent) => {
onBack?.(ev);
RightPanelStore.instance.popCard();
};
const label = backLabelForPhase(prevCard.phase) ?? _t("Back");
backButton = <AccessibleButton className="mx_BaseCard_back" onClick={onBackClick} title={label} />;
}
let closeButton;
if (onClose) {
closeButton = <AccessibleButton
data-test-id='base-card-close-button'
className="mx_BaseCard_close"
onClick={onClose}
title={closeLabel || _t("Close")}
/>;
}
let closeButton;
if (onClose) {
closeButton = (
<AccessibleButton
data-test-id="base-card-close-button"
className="mx_BaseCard_close"
onClick={onClose}
title={closeLabel || _t("Close")}
/>
);
}
if (!withoutScrollContainer) {
children = <AutoHideScrollbar>
{ children }
</AutoHideScrollbar>;
}
if (!withoutScrollContainer) {
children = <AutoHideScrollbar>{children}</AutoHideScrollbar>;
}
return (
<CardContext.Provider value={{ isCard: true }}>
<div className={classNames("mx_BaseCard", className)} ref={ref} onKeyDown={onKeyDown}>
<div className="mx_BaseCard_header">
{ backButton }
{ closeButton }
{ header }
return (
<CardContext.Provider value={{ isCard: true }}>
<div className={classNames("mx_BaseCard", className)} ref={ref} onKeyDown={onKeyDown}>
<div className="mx_BaseCard_header">
{backButton}
{closeButton}
{header}
</div>
{children}
{footer && <div className="mx_BaseCard_footer">{footer}</div>}
</div>
{ children }
{ footer && <div className="mx_BaseCard_footer">{ footer }</div> }
</div>
</CardContext.Provider>
);
});
</CardContext.Provider>
);
},
);
export default BaseCard;

View file

@ -23,10 +23,12 @@ import AccessibleButton from "../elements/AccessibleButton";
import Spinner from "../elements/Spinner";
export const PendingActionSpinner = ({ text }) => {
return <div className="mx_EncryptionInfo_spinner">
<Spinner />
{ text }
</div>;
return (
<div className="mx_EncryptionInfo_spinner">
<Spinner />
{text}
</div>
);
};
interface IProps {
@ -50,11 +52,7 @@ const EncryptionInfo: React.FC<IProps> = ({
}: IProps) => {
let content: JSX.Element;
if (waitingForOtherParty && isSelfVerification) {
content = (
<div>
{ _t("To proceed, please accept the verification request on your other device.") }
</div>
);
content = <div>{_t("To proceed, please accept the verification request on your other device.")}</div>;
} else if (waitingForOtherParty || waitingForNetwork) {
let text: string;
if (waitingForOtherParty) {
@ -72,7 +70,7 @@ const EncryptionInfo: React.FC<IProps> = ({
className="mx_UserInfo_wideButton mx_UserInfo_startVerification"
onClick={onStartVerification}
>
{ _t("Start Verification") }
{_t("Start Verification")}
</AccessibleButton>
);
}
@ -81,17 +79,25 @@ const EncryptionInfo: React.FC<IProps> = ({
if (isRoomEncrypted) {
description = (
<div>
<p>{ _t("Messages in this room are end-to-end encrypted.") }</p>
<p>{ _t("Your messages are secured and only you and the recipient have " +
"the unique keys to unlock them.") }</p>
<p>{_t("Messages in this room are end-to-end encrypted.")}</p>
<p>
{_t(
"Your messages are secured and only you and the recipient have " +
"the unique keys to unlock them.",
)}
</p>
</div>
);
} else {
description = (
<div>
<p>{ _t("Messages in this room are not end-to-end encrypted.") }</p>
<p>{ _t("In encrypted rooms, your messages are secured and only you and the recipient have " +
"the unique keys to unlock them.") }</p>
<p>{_t("Messages in this room are not end-to-end encrypted.")}</p>
<p>
{_t(
"In encrypted rooms, your messages are secured and only you and the recipient have " +
"the unique keys to unlock them.",
)}
</p>
</div>
);
}
@ -100,20 +106,26 @@ const EncryptionInfo: React.FC<IProps> = ({
return content;
}
return <React.Fragment>
<div data-test-id='encryption-info-description' className="mx_UserInfo_container">
<h3>{ _t("Encryption") }</h3>
{ description }
</div>
<div className="mx_UserInfo_container">
<h3>{ _t("Verify User") }</h3>
<div>
<p>{ _t("For extra security, verify this user by checking a one-time code on both of your devices.") }</p>
<p>{ _t("To be secure, do this in person or use a trusted way to communicate.") }</p>
{ content }
return (
<React.Fragment>
<div data-test-id="encryption-info-description" className="mx_UserInfo_container">
<h3>{_t("Encryption")}</h3>
{description}
</div>
</div>
</React.Fragment>;
<div className="mx_UserInfo_container">
<h3>{_t("Verify User")}</h3>
<div>
<p>
{_t(
"For extra security, verify this user by checking a one-time code on both of your devices.",
)}
</p>
<p>{_t("To be secure, do this in person or use a trusted way to communicate.")}</p>
{content}
</div>
</div>
</React.Fragment>
);
};
export default EncryptionInfo;

View file

@ -31,7 +31,7 @@ import { ensureDMExists } from "../../../createRoom";
import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
import Modal from "../../../Modal";
import { _t } from "../../../languageHandler";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import ErrorDialog from "../dialogs/ErrorDialog";
@ -48,14 +48,7 @@ interface IProps {
}
const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
const {
verificationRequest,
verificationRequestPromise,
member,
onClose,
layout,
isRoomEncrypted,
} = props;
const { verificationRequest, verificationRequestPromise, member, onClose, layout, isRoomEncrypted } = props;
const [request, setRequest] = useState(verificationRequest);
// state to show a spinner immediately after clicking "start verification",
// before we have a request
@ -87,15 +80,17 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
Modal.createDialog(ErrorDialog, {
headerImage: require("../../../../res/img/e2e/warning-deprecated.svg").default,
title: _t("Your messages are not secure"),
description: <div>
{ _t("One of the following may be compromised:") }
<ul>
<li>{ _t("Your homeserver") }</li>
<li>{ _t("The homeserver the user you're verifying is connected to") }</li>
<li>{ _t("Yours, or the other users' internet connection") }</li>
<li>{ _t("Yours, or the other users' session") }</li>
</ul>
</div>,
description: (
<div>
{_t("One of the following may be compromised:")}
<ul>
<li>{_t("Your homeserver")}</li>
<li>{_t("The homeserver the user you're verifying is connected to")}</li>
<li>{_t("Yours, or the other users' internet connection")}</li>
<li>{_t("Yours, or the other users' session")}</li>
</ul>
</div>
),
onFinished: onClose,
});
return; // don't update phase here as we will be transitioning away from this view shortly
@ -141,9 +136,9 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
const requested =
(!request && isRequesting) ||
(request && (phase === PHASE_REQUESTED || phase === PHASE_UNSENT || phase === undefined));
const isSelfVerification = request ?
request.isSelfVerification :
member.userId === MatrixClientPeg.get().getUserId();
const isSelfVerification = request
? request.isSelfVerification
: member.userId === MatrixClientPeg.get().getUserId();
if (!request || requested) {
const initiatedByMe = (!request && isRequesting) || (request && request.initiatedByMe);
@ -155,7 +150,8 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
isSelfVerification={isSelfVerification}
waitingForOtherParty={requested && initiatedByMe}
waitingForNetwork={requested && !initiatedByMe}
inDialog={layout === "dialog"} />
inDialog={layout === "dialog"}
/>
);
} else {
return (

View file

@ -18,8 +18,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import classNames from 'classnames';
import React from "react";
import classNames from "classnames";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { ButtonEvent } from "../elements/AccessibleButton";
@ -50,14 +50,16 @@ export default class HeaderButton extends React.Component<IProps> {
[`mx_RightPanel_${name}`]: true,
});
return <AccessibleTooltipButton
{...props}
aria-selected={isHighlighted}
role="tab"
title={title}
alignment={Alignment.Bottom}
className={classes}
onClick={onClick}
/>;
return (
<AccessibleTooltipButton
{...props}
aria-selected={isHighlighted}
role="tab"
title={title}
alignment={Alignment.Bottom}
className={classes}
onClick={onClick}
/>
);
}
}

View file

@ -18,17 +18,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import dis from '../../../dispatcher/dispatcher';
import dis from "../../../dispatcher/dispatcher";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import { IRightPanelCardState } from '../../../stores/right-panel/RightPanelStoreIPanelState';
import { UPDATE_EVENT } from '../../../stores/AsyncStore';
import { NotificationColor } from '../../../stores/notifications/NotificationColor';
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
import { IRightPanelCardState } from "../../../stores/right-panel/RightPanelStoreIPanelState";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
export enum HeaderKind {
Room = "room",
Room = "room",
}
interface IState {
@ -97,8 +97,10 @@ export default abstract class HeaderButtons<P = {}> extends React.Component<IPro
public abstract renderButtons(): JSX.Element;
public render() {
return <div className="mx_HeaderButtons" role="tablist">
{ this.renderButtons() }
</div>;
return (
<div className="mx_HeaderButtons" role="tablist">
{this.renderButtons()}
</div>
);
}
}

View file

@ -17,13 +17,13 @@ limitations under the License.
import React, { useCallback, useContext, useEffect, useState } from "react";
import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType, RelationType } from 'matrix-js-sdk/src/@types/event';
import { EventType, RelationType } from "matrix-js-sdk/src/@types/event";
import { logger } from "matrix-js-sdk/src/logger";
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import { Icon as ContextMenuIcon } from '../../../../res/img/element-icons/context-menu.svg';
import { Icon as ContextMenuIcon } from "../../../../res/img/element-icons/context-menu.svg";
import { Icon as EmojiIcon } from "../../../../res/img/element-icons/room/message-bar/emoji.svg";
import { Icon as ReplyIcon } from '../../../../res/img/element-icons/room/message-bar/reply.svg';
import { Icon as ReplyIcon } from "../../../../res/img/element-icons/room/message-bar/reply.svg";
import { _t } from "../../../languageHandler";
import BaseCard from "./BaseCard";
import Spinner from "../elements/Spinner";
@ -35,7 +35,7 @@ import PinnedEventTile from "../rooms/PinnedEventTile";
import { useRoomState } from "../../../hooks/useRoomState";
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
import { ReadPinsEventId } from "./types";
import Heading from '../typography/Heading';
import Heading from "../typography/Heading";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
interface IProps {
@ -47,11 +47,16 @@ interface IProps {
export const usePinnedEvents = (room: Room): string[] => {
const [pinnedEvents, setPinnedEvents] = useState<string[]>([]);
const update = useCallback((ev?: MatrixEvent) => {
if (!room) return;
if (ev && ev.getType() !== EventType.RoomPinnedEvents) return;
setPinnedEvents(room.currentState.getStateEvents(EventType.RoomPinnedEvents, "")?.getContent()?.pinned || []);
}, [room]);
const update = useCallback(
(ev?: MatrixEvent) => {
if (!room) return;
if (ev && ev.getType() !== EventType.RoomPinnedEvents) return;
setPinnedEvents(
room.currentState.getStateEvents(EventType.RoomPinnedEvents, "")?.getContent()?.pinned || [],
);
},
[room],
);
useTypedEventEmitter(room?.currentState, RoomStateEvent.Events, update);
useEffect(() => {
@ -66,12 +71,15 @@ export const usePinnedEvents = (room: Room): string[] => {
export const useReadPinnedEvents = (room: Room): Set<string> => {
const [readPinnedEvents, setReadPinnedEvents] = useState<Set<string>>(new Set());
const update = useCallback((ev?: MatrixEvent) => {
if (!room) return;
if (ev && ev.getType() !== ReadPinsEventId) return;
const readPins = room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids;
setReadPinnedEvents(new Set(readPins || []));
}, [room]);
const update = useCallback(
(ev?: MatrixEvent) => {
if (!room) return;
if (ev && ev.getType() !== ReadPinsEventId) return;
const readPins = room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids;
setReadPinnedEvents(new Set(readPins || []));
},
[room],
);
useTypedEventEmitter(room, RoomEvent.AccountData, update);
useEffect(() => {
@ -86,12 +94,12 @@ export const useReadPinnedEvents = (room: Room): Set<string> => {
const PinnedMessagesCard = ({ room, onClose, permalinkCreator }: IProps) => {
const cli = useContext(MatrixClientContext);
const roomContext = useContext(RoomContext);
const canUnpin = useRoomState(room, state => state.mayClientSendStateEvent(EventType.RoomPinnedEvents, cli));
const canUnpin = useRoomState(room, (state) => state.mayClientSendStateEvent(EventType.RoomPinnedEvents, cli));
const pinnedEventIds = usePinnedEvents(room);
const readPinnedEvents = useReadPinnedEvents(room);
useEffect(() => {
const newlyRead = pinnedEventIds.filter(id => !readPinnedEvents.has(id));
const newlyRead = pinnedEventIds.filter((id) => !readPinnedEvents.has(id));
if (newlyRead.length > 0) {
// clear out any read pinned events which no longer are pinned
cli.setRoomAccountData(room.roomId, ReadPinsEventId, {
@ -100,40 +108,52 @@ const PinnedMessagesCard = ({ room, onClose, permalinkCreator }: IProps) => {
}
}, [cli, room.roomId, pinnedEventIds, readPinnedEvents]);
const pinnedEvents = useAsyncMemo(() => {
const promises = pinnedEventIds.map(async eventId => {
const timelineSet = room.getUnfilteredTimelineSet();
const localEvent = timelineSet?.getTimelineForEvent(eventId)?.getEvents().find(e => e.getId() === eventId);
if (localEvent) return PinningUtils.isPinnable(localEvent) ? localEvent : null;
const pinnedEvents = useAsyncMemo(
() => {
const promises = pinnedEventIds.map(async (eventId) => {
const timelineSet = room.getUnfilteredTimelineSet();
const localEvent = timelineSet
?.getTimelineForEvent(eventId)
?.getEvents()
.find((e) => e.getId() === eventId);
if (localEvent) return PinningUtils.isPinnable(localEvent) ? localEvent : null;
try {
// Fetch the event and latest edit in parallel
const [evJson, { events: [edit] }] = await Promise.all([
cli.fetchRoomEvent(room.roomId, eventId),
cli.relations(room.roomId, eventId, RelationType.Replace, null, { limit: 1 }),
]);
const event = new MatrixEvent(evJson);
if (event.isEncrypted()) {
await cli.decryptEventIfNeeded(event); // TODO await?
try {
// Fetch the event and latest edit in parallel
const [
evJson,
{
events: [edit],
},
] = await Promise.all([
cli.fetchRoomEvent(room.roomId, eventId),
cli.relations(room.roomId, eventId, RelationType.Replace, null, { limit: 1 }),
]);
const event = new MatrixEvent(evJson);
if (event.isEncrypted()) {
await cli.decryptEventIfNeeded(event); // TODO await?
}
if (event && PinningUtils.isPinnable(event)) {
// Inject sender information
event.sender = room.getMember(event.getSender());
// Also inject any edits we've found
if (edit) event.makeReplaced(edit);
return event;
}
} catch (err) {
logger.error("Error looking up pinned event " + eventId + " in room " + room.roomId);
logger.error(err);
}
return null;
});
if (event && PinningUtils.isPinnable(event)) {
// Inject sender information
event.sender = room.getMember(event.getSender());
// Also inject any edits we've found
if (edit) event.makeReplaced(edit);
return event;
}
} catch (err) {
logger.error("Error looking up pinned event " + eventId + " in room " + room.roomId);
logger.error(err);
}
return null;
});
return Promise.all(promises);
}, [cli, room, pinnedEventIds], null);
return Promise.all(promises);
},
[cli, room, pinnedEventIds],
null,
);
let content;
if (!pinnedEvents) {
@ -152,53 +172,72 @@ const PinnedMessagesCard = ({ room, onClose, permalinkCreator }: IProps) => {
};
// show them in reverse, with latest pinned at the top
content = pinnedEvents.filter(Boolean).reverse().map(ev => (
<PinnedEventTile
key={ev.getId()}
event={ev}
onUnpinClicked={canUnpin ? () => onUnpinClicked(ev) : undefined}
permalinkCreator={permalinkCreator}
/>
));
content = pinnedEvents
.filter(Boolean)
.reverse()
.map((ev) => (
<PinnedEventTile
key={ev.getId()}
event={ev}
onUnpinClicked={canUnpin ? () => onUnpinClicked(ev) : undefined}
permalinkCreator={permalinkCreator}
/>
));
} else {
content = <div className="mx_PinnedMessagesCard_empty_wrapper">
<div className="mx_PinnedMessagesCard_empty">
{ /* XXX: We reuse the classes for simplicity, but deliberately not the components for non-interactivity. */ }
<div className="mx_MessageActionBar mx_PinnedMessagesCard_MessageActionBar">
<div className="mx_MessageActionBar_iconButton">
<EmojiIcon />
content = (
<div className="mx_PinnedMessagesCard_empty_wrapper">
<div className="mx_PinnedMessagesCard_empty">
{/* XXX: We reuse the classes for simplicity, but deliberately not the components for non-interactivity. */}
<div className="mx_MessageActionBar mx_PinnedMessagesCard_MessageActionBar">
<div className="mx_MessageActionBar_iconButton">
<EmojiIcon />
</div>
<div className="mx_MessageActionBar_iconButton">
<ReplyIcon />
</div>
<div className="mx_MessageActionBar_iconButton mx_MessageActionBar_optionsButton">
<ContextMenuIcon />
</div>
</div>
<div className="mx_MessageActionBar_iconButton">
<ReplyIcon />
</div>
<div className="mx_MessageActionBar_iconButton mx_MessageActionBar_optionsButton">
<ContextMenuIcon />
</div>
</div>
<Heading size="h4" className="mx_PinnedMessagesCard_empty_header">{ _t("Nothing pinned, yet") }</Heading>
{ _t("If you have permissions, open the menu on any message and select " +
"<b>Pin</b> to stick them here.", {}, {
b: sub => <b>{ sub }</b>,
}) }
<Heading size="h4" className="mx_PinnedMessagesCard_empty_header">
{_t("Nothing pinned, yet")}
</Heading>
{_t(
"If you have permissions, open the menu on any message and select " +
"<b>Pin</b> to stick them here.",
{},
{
b: (sub) => <b>{sub}</b>,
},
)}
</div>
</div>
</div>;
);
}
return <BaseCard
header={<div className="mx_BaseCard_header_title">
<Heading size="h4" className="mx_BaseCard_header_title_heading">{ _t("Pinned messages") }</Heading>
</div>}
className="mx_PinnedMessagesCard"
onClose={onClose}
>
<RoomContext.Provider value={{
...roomContext,
timelineRenderingType: TimelineRenderingType.Pinned,
}}>
{ content }
</RoomContext.Provider>
</BaseCard>;
return (
<BaseCard
header={
<div className="mx_BaseCard_header_title">
<Heading size="h4" className="mx_BaseCard_header_title_heading">
{_t("Pinned messages")}
</Heading>
</div>
}
className="mx_PinnedMessagesCard"
onClose={onClose}
>
<RoomContext.Provider
value={{
...roomContext,
timelineRenderingType: TimelineRenderingType.Pinned,
}}
>
{content}
</RoomContext.Provider>
</BaseCard>
);
};
export default PinnedMessagesCard;

View file

@ -23,14 +23,14 @@ import classNames from "classnames";
import { NotificationCountType, Room, RoomEvent } from "matrix-js-sdk/src/models/room";
import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
import { _t } from '../../../languageHandler';
import HeaderButton from './HeaderButton';
import HeaderButtons, { HeaderKind } from './HeaderButtons';
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import { _t } from "../../../languageHandler";
import HeaderButton from "./HeaderButton";
import HeaderButtons, { HeaderKind } from "./HeaderButtons";
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
import { Action } from "../../../dispatcher/actions";
import { ActionPayload } from "../../../dispatcher/payloads";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import { useReadPinnedEvents, usePinnedEvents } from './PinnedMessagesCard';
import { useReadPinnedEvents, usePinnedEvents } from "./PinnedMessagesCard";
import { showThreadPanel } from "../../../dispatcher/dispatch-actions/threads";
import SettingsStore from "../../../settings/SettingsStore";
import {
@ -65,16 +65,18 @@ const UnreadIndicator = ({ color }: IUnreadIndicatorProps) => {
}
const classes = classNames({
"mx_Indicator": true,
"mx_RightPanel_headerButton_unreadIndicator": true,
"mx_Indicator_bold": color === NotificationColor.Bold,
"mx_Indicator_gray": color === NotificationColor.Grey,
"mx_Indicator_red": color === NotificationColor.Red,
mx_Indicator: true,
mx_RightPanel_headerButton_unreadIndicator: true,
mx_Indicator_bold: color === NotificationColor.Bold,
mx_Indicator_gray: color === NotificationColor.Grey,
mx_Indicator_red: color === NotificationColor.Red,
});
return <>
<div className="mx_RightPanel_headerButton_unreadIndicator_bg" />
<div className={classes} />
</>;
return (
<>
<div className="mx_RightPanel_headerButton_unreadIndicator_bg" />
<div className={classes} />
</>
);
};
interface IHeaderButtonProps {
@ -89,19 +91,21 @@ const PinnedMessagesHeaderButton = ({ room, isHighlighted, onClick }: IHeaderBut
if (!pinnedEvents?.length) return null;
let unreadIndicator;
if (pinnedEvents.some(id => !readPinnedEvents.has(id))) {
if (pinnedEvents.some((id) => !readPinnedEvents.has(id))) {
unreadIndicator = <UnreadIndicator />;
}
return <HeaderButton
name="pinnedMessagesButton"
title={_t("Pinned messages")}
isHighlighted={isHighlighted}
isUnread={!!unreadIndicator}
onClick={onClick}
>
{ unreadIndicator }
</HeaderButton>;
return (
<HeaderButton
name="pinnedMessagesButton"
title={_t("Pinned messages")}
isHighlighted={isHighlighted}
isUnread={!!unreadIndicator}
onClick={onClick}
>
{unreadIndicator}
</HeaderButton>
);
};
const TimelineCardHeaderButton = ({ room, isHighlighted, onClick }: IHeaderButtonProps) => {
@ -113,14 +117,11 @@ const TimelineCardHeaderButton = ({ room, isHighlighted, onClick }: IHeaderButto
case NotificationColor.Red:
unreadIndicator = <UnreadIndicator color={color} />;
}
return <HeaderButton
name="timelineCardButton"
title={_t("Chat")}
isHighlighted={isHighlighted}
onClick={onClick}
>
{ unreadIndicator }
</HeaderButton>;
return (
<HeaderButton name="timelineCardButton" title={_t("Chat")} isHighlighted={isHighlighted} onClick={onClick}>
{unreadIndicator}
</HeaderButton>
);
};
interface IProps {
@ -129,10 +130,7 @@ interface IProps {
}
export default class RoomHeaderButtons extends HeaderButtons<IProps> {
private static readonly THREAD_PHASES = [
RightPanelPhases.ThreadPanel,
RightPanelPhases.ThreadView,
];
private static readonly THREAD_PHASES = [RightPanelPhases.ThreadPanel, RightPanelPhases.ThreadView];
private threadNotificationState: ThreadsRoomNotificationState | null;
private globalNotificationState: SummarizedNotificationState;
@ -144,9 +142,10 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
constructor(props: IProps) {
super(props, HeaderKind.Room);
this.threadNotificationState = !this.supportsThreadNotifications && this.props.room
? RoomNotificationStateStore.instance.getThreadsRoomState(this.props.room)
: null;
this.threadNotificationState =
!this.supportsThreadNotifications && this.props.room
? RoomNotificationStateStore.instance.getThreadsRoomState(this.props.room)
: null;
this.globalNotificationState = RoomNotificationStateStore.instance.globalState;
}
@ -209,9 +208,10 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
if (payload.action === Action.ViewUser) {
if (payload.member) {
if (payload.push) {
RightPanelStore.instance.pushCard(
{ phase: RightPanelPhases.RoomMemberInfo, state: { member: payload.member } },
);
RightPanelStore.instance.pushCard({
phase: RightPanelPhases.RoomMemberInfo,
state: { member: payload.member },
});
} else {
RightPanelStore.instance.setCards([
{ phase: RightPanelPhases.RoomSummary },
@ -276,24 +276,29 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
const rightPanelPhaseButtons: Map<RightPanelPhases, any> = new Map();
if (SettingsStore.getValue("feature_pinning")) {
rightPanelPhaseButtons.set(RightPanelPhases.PinnedMessages,
rightPanelPhaseButtons.set(
RightPanelPhases.PinnedMessages,
<PinnedMessagesHeaderButton
key="pinnedMessagesButton"
room={this.props.room}
isHighlighted={this.isPhase(RightPanelPhases.PinnedMessages)}
onClick={this.onPinnedMessagesClicked} />,
onClick={this.onPinnedMessagesClicked}
/>,
);
}
rightPanelPhaseButtons.set(RightPanelPhases.Timeline,
rightPanelPhaseButtons.set(
RightPanelPhases.Timeline,
<TimelineCardHeaderButton
key="timelineButton"
room={this.props.room}
isHighlighted={this.isPhase(RightPanelPhases.Timeline)}
onClick={this.onTimelineCardClicked} />,
onClick={this.onTimelineCardClicked}
/>,
);
rightPanelPhaseButtons.set(RightPanelPhases.ThreadPanel,
SettingsStore.getValue("feature_thread")
? <HeaderButton
rightPanelPhaseButtons.set(
RightPanelPhases.ThreadPanel,
SettingsStore.getValue("feature_thread") ? (
<HeaderButton
key={RightPanelPhases.ThreadPanel}
name="threadsButton"
data-testid="threadsButton"
@ -304,39 +309,42 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
>
<UnreadIndicator color={this.state.threadNotificationColor} />
</HeaderButton>
: null,
) : null,
);
rightPanelPhaseButtons.set(RightPanelPhases.NotificationPanel,
rightPanelPhaseButtons.set(
RightPanelPhases.NotificationPanel,
<HeaderButton
key="notifsButton"
name="notifsButton"
title={_t('Notifications')}
title={_t("Notifications")}
isHighlighted={this.isPhase(RightPanelPhases.NotificationPanel)}
onClick={this.onNotificationsClicked}
isUnread={this.globalNotificationState.color === NotificationColor.Red}
>
{ this.globalNotificationState.color === NotificationColor.Red ?
<UnreadIndicator color={this.globalNotificationState.color} /> :
null }
{this.globalNotificationState.color === NotificationColor.Red ? (
<UnreadIndicator color={this.globalNotificationState.color} />
) : null}
</HeaderButton>,
);
rightPanelPhaseButtons.set(RightPanelPhases.RoomSummary,
rightPanelPhaseButtons.set(
RightPanelPhases.RoomSummary,
<HeaderButton
key="roomSummaryButton"
name="roomSummaryButton"
title={_t('Room info')}
title={_t("Room info")}
isHighlighted={this.isPhase(ROOM_INFO_PHASES)}
onClick={this.onRoomSummaryClicked}
/>,
);
return <>
{
Array.from(rightPanelPhaseButtons.keys()).map((phase) =>
(this.props.excludedRightPanelPhaseButtons?.includes(phase)
return (
<>
{Array.from(rightPanelPhaseButtons.keys()).map((phase) =>
this.props.excludedRightPanelPhaseButtons?.includes(phase)
? null
: rightPanelPhaseButtons.get(phase)))
}
</>;
: rightPanelPhaseButtons.get(phase),
)}
</>
);
}
}

View file

@ -19,15 +19,15 @@ import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { useIsEncrypted } from '../../../hooks/useIsEncrypted';
import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
import BaseCard, { Group } from "./BaseCard";
import { _t } from '../../../languageHandler';
import { _t } from "../../../languageHandler";
import RoomAvatar from "../avatars/RoomAvatar";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
import Modal from "../../../Modal";
import ShareDialog from '../dialogs/ShareDialog';
import ShareDialog from "../dialogs/ShareDialog";
import { useEventEmitter } from "../../../hooks/useEventEmitter";
import WidgetUtils from "../../../utils/WidgetUtils";
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
@ -67,12 +67,14 @@ interface IButtonProps {
}
const Button: React.FC<IButtonProps> = ({ children, className, onClick }) => {
return <AccessibleButton
className={classNames("mx_BaseCard_Button mx_RoomSummaryCard_Button", className)}
onClick={onClick}
>
{ children }
</AccessibleButton>;
return (
<AccessibleButton
className={classNames("mx_BaseCard_Button mx_RoomSummaryCard_Button", className)}
onClick={onClick}
>
{children}
</AccessibleButton>
);
};
export const useWidgets = (room: Room) => {
@ -114,20 +116,26 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
const isPinned = WidgetLayoutStore.instance.isInContainer(room, app, Container.Top);
const togglePin = isPinned
? () => { WidgetLayoutStore.instance.moveToContainer(room, app, Container.Right); }
: () => { WidgetLayoutStore.instance.moveToContainer(room, app, Container.Top); };
? () => {
WidgetLayoutStore.instance.moveToContainer(room, app, Container.Right);
}
: () => {
WidgetLayoutStore.instance.moveToContainer(room, app, Container.Top);
};
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>();
let contextMenu;
if (menuDisplayed) {
const rect = handle.current.getBoundingClientRect();
contextMenu = <WidgetContextMenu
chevronFace={ChevronFace.None}
right={UIStore.instance.windowWidth - rect.right}
bottom={UIStore.instance.windowHeight - rect.top}
onFinished={closeMenu}
app={app}
/>;
contextMenu = (
<WidgetContextMenu
chevronFace={ChevronFace.None}
right={UIStore.instance.windowWidth - rect.right}
bottom={UIStore.instance.windowHeight - rect.top}
onFinished={closeMenu}
app={app}
/>
);
}
const cannotPin = !isPinned && !WidgetLayoutStore.instance.canAddToContainer(room, Container.Top);
@ -141,8 +149,12 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
const isMaximised = WidgetLayoutStore.instance.isInContainer(room, app, Container.Center);
const toggleMaximised = isMaximised
? () => { WidgetLayoutStore.instance.moveToContainer(room, app, Container.Right); }
: () => { WidgetLayoutStore.instance.moveToContainer(room, app, Container.Center); };
? () => {
WidgetLayoutStore.instance.moveToContainer(room, app, Container.Right);
}
: () => {
WidgetLayoutStore.instance.moveToContainer(room, app, Container.Center);
};
const maximiseTitle = isMaximised ? _t("Close") : _t("Maximise");
@ -150,7 +162,7 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
if (isPinned) {
openTitle = _t("Unpin this widget to view it in this panel");
} else if (isMaximised) {
openTitle =_t("Close this widget to view it in this panel");
openTitle = _t("Close this widget to view it in this panel");
}
const classes = classNames("mx_BaseCard_Button mx_RoomSummaryCard_Button", {
@ -158,47 +170,51 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
mx_RoomSummaryCard_Button_maximised: isMaximised,
});
return <div className={classes} ref={handle}>
<AccessibleTooltipButton
className="mx_RoomSummaryCard_icon_app"
onClick={onOpenWidgetClick}
// only show a tooltip if the widget is pinned
title={openTitle}
forceHide={!(isPinned || isMaximised)}
disabled={isPinned || isMaximised}
>
<WidgetAvatar app={app} />
<span>{ name }</span>
{ subtitle }
</AccessibleTooltipButton>
return (
<div className={classes} ref={handle}>
<AccessibleTooltipButton
className="mx_RoomSummaryCard_icon_app"
onClick={onOpenWidgetClick}
// only show a tooltip if the widget is pinned
title={openTitle}
forceHide={!(isPinned || isMaximised)}
disabled={isPinned || isMaximised}
>
<WidgetAvatar app={app} />
<span>{name}</span>
{subtitle}
</AccessibleTooltipButton>
{ canModifyWidget && <ContextMenuTooltipButton
className="mx_RoomSummaryCard_app_options"
isExpanded={menuDisplayed}
onClick={openMenu}
title={_t("Options")}
/> }
{canModifyWidget && (
<ContextMenuTooltipButton
className="mx_RoomSummaryCard_app_options"
isExpanded={menuDisplayed}
onClick={openMenu}
title={_t("Options")}
/>
)}
<AccessibleTooltipButton
className="mx_RoomSummaryCard_app_pinToggle"
onClick={togglePin}
title={pinTitle}
disabled={cannotPin}
/>
<AccessibleTooltipButton
className="mx_RoomSummaryCard_app_maximiseToggle"
onClick={toggleMaximised}
title={maximiseTitle}
/>
<AccessibleTooltipButton
className="mx_RoomSummaryCard_app_pinToggle"
onClick={togglePin}
title={pinTitle}
disabled={cannotPin}
/>
<AccessibleTooltipButton
className="mx_RoomSummaryCard_app_maximiseToggle"
onClick={toggleMaximised}
title={maximiseTitle}
/>
{ contextMenu }
</div>;
{contextMenu}
</div>
);
};
const AppsSection: React.FC<IAppsSectionProps> = ({ room }) => {
const apps = useWidgets(room);
// Filter out virtual widgets
const realApps = useMemo(() => apps.filter(app => app.eventId !== undefined), [apps]);
const realApps = useMemo(() => apps.filter((app) => app.eventId !== undefined), [apps]);
const onManageIntegrations = () => {
const managers = IntegrationManagers.sharedInstance();
@ -214,18 +230,22 @@ const AppsSection: React.FC<IAppsSectionProps> = ({ room }) => {
if (realApps.length > 0 && WidgetLayoutStore.instance.canCopyLayoutToRoom(room)) {
copyLayoutBtn = (
<AccessibleButton kind="link" onClick={() => WidgetLayoutStore.instance.copyLayoutToRoom(room)}>
{ _t("Set my room layout for everyone") }
{_t("Set my room layout for everyone")}
</AccessibleButton>
);
}
return <Group className="mx_RoomSummaryCard_appsGroup" title={_t("Widgets")}>
{ realApps.map(app => <AppRow key={app.id} app={app} room={room} />) }
{ copyLayoutBtn }
<AccessibleButton kind="link" onClick={onManageIntegrations}>
{ realApps.length > 0 ? _t("Edit widgets, bridges & bots") : _t("Add widgets, bridges & bots") }
</AccessibleButton>
</Group>;
return (
<Group className="mx_RoomSummaryCard_appsGroup" title={_t("Widgets")}>
{realApps.map((app) => (
<AppRow key={app.id} app={app} room={room} />
))}
{copyLayoutBtn}
<AccessibleButton kind="link" onClick={onManageIntegrations}>
{realApps.length > 0 ? _t("Edit widgets, bridges & bots") : _t("Add widgets, bridges & bots")}
</AccessibleButton>
</Group>
);
};
const onRoomMembersClick = (ev: ButtonEvent) => {
@ -266,76 +286,71 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, onClose }) => {
const e2eStatus = roomContext.e2eStatus;
const videoRoomsEnabled = useFeatureEnabled("feature_video_rooms");
const elementCallVideoRoomsEnabled = useFeatureEnabled("feature_element_call_video_rooms");
const isVideoRoom = videoRoomsEnabled && (
room.isElementVideoRoom() || (elementCallVideoRoomsEnabled && room.isCallRoom())
);
const isVideoRoom =
videoRoomsEnabled && (room.isElementVideoRoom() || (elementCallVideoRoomsEnabled && room.isCallRoom()));
const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || "";
const header = <React.Fragment>
<div className="mx_RoomSummaryCard_avatar" role="presentation">
<RoomAvatar room={room} height={54} width={54} viewAvatarOnClick />
<TextWithTooltip
tooltip={isRoomEncrypted ? _t("Encrypted") : _t("Not encrypted")}
class={classNames("mx_RoomSummaryCard_e2ee", {
mx_RoomSummaryCard_e2ee_normal: isRoomEncrypted,
mx_RoomSummaryCard_e2ee_warning: isRoomEncrypted && e2eStatus === E2EStatus.Warning,
mx_RoomSummaryCard_e2ee_verified: isRoomEncrypted && e2eStatus === E2EStatus.Verified,
})}
/>
</div>
const header = (
<React.Fragment>
<div className="mx_RoomSummaryCard_avatar" role="presentation">
<RoomAvatar room={room} height={54} width={54} viewAvatarOnClick />
<TextWithTooltip
tooltip={isRoomEncrypted ? _t("Encrypted") : _t("Not encrypted")}
class={classNames("mx_RoomSummaryCard_e2ee", {
mx_RoomSummaryCard_e2ee_normal: isRoomEncrypted,
mx_RoomSummaryCard_e2ee_warning: isRoomEncrypted && e2eStatus === E2EStatus.Warning,
mx_RoomSummaryCard_e2ee_verified: isRoomEncrypted && e2eStatus === E2EStatus.Verified,
})}
/>
</div>
<RoomName room={room}>
{ name => (
<h2 title={name}>
{ name }
</h2>
) }
</RoomName>
<div className="mx_RoomSummaryCard_alias" title={alias}>
{ alias }
</div>
</React.Fragment>;
<RoomName room={room}>{(name) => <h2 title={name}>{name}</h2>}</RoomName>
<div className="mx_RoomSummaryCard_alias" title={alias}>
{alias}
</div>
</React.Fragment>
);
const memberCount = useRoomMemberCount(room);
const pinningEnabled = useFeatureEnabled("feature_pinning");
const pinCount = usePinnedEvents(pinningEnabled && room)?.length;
return <BaseCard header={header} className="mx_RoomSummaryCard" onClose={onClose}>
<Group title={_t("About")} className="mx_RoomSummaryCard_aboutGroup">
<Button className="mx_RoomSummaryCard_icon_people" onClick={onRoomMembersClick}>
{ _t("People") }
<span className="mx_BaseCard_Button_sublabel">
{ memberCount }
</span>
</Button>
{ !isVideoRoom && <Button className="mx_RoomSummaryCard_icon_files" onClick={onRoomFilesClick}>
{ _t("Files") }
</Button> }
{ pinningEnabled && !isVideoRoom &&
<Button className="mx_RoomSummaryCard_icon_pins" onClick={onRoomPinsClick}>
{ _t("Pinned") }
{ pinCount > 0 && <span className="mx_BaseCard_Button_sublabel">
{ pinCount }
</span> }
</Button> }
{ !isVideoRoom && <Button className="mx_RoomSummaryCard_icon_export" onClick={onRoomExportClick}>
{ _t("Export chat") }
</Button> }
<Button className="mx_RoomSummaryCard_icon_share" onClick={onShareRoomClick}>
{ _t("Share room") }
</Button>
<Button className="mx_RoomSummaryCard_icon_settings" onClick={onRoomSettingsClick}>
{ _t("Room settings") }
</Button>
</Group>
return (
<BaseCard header={header} className="mx_RoomSummaryCard" onClose={onClose}>
<Group title={_t("About")} className="mx_RoomSummaryCard_aboutGroup">
<Button className="mx_RoomSummaryCard_icon_people" onClick={onRoomMembersClick}>
{_t("People")}
<span className="mx_BaseCard_Button_sublabel">{memberCount}</span>
</Button>
{!isVideoRoom && (
<Button className="mx_RoomSummaryCard_icon_files" onClick={onRoomFilesClick}>
{_t("Files")}
</Button>
)}
{pinningEnabled && !isVideoRoom && (
<Button className="mx_RoomSummaryCard_icon_pins" onClick={onRoomPinsClick}>
{_t("Pinned")}
{pinCount > 0 && <span className="mx_BaseCard_Button_sublabel">{pinCount}</span>}
</Button>
)}
{!isVideoRoom && (
<Button className="mx_RoomSummaryCard_icon_export" onClick={onRoomExportClick}>
{_t("Export chat")}
</Button>
)}
<Button className="mx_RoomSummaryCard_icon_share" onClick={onShareRoomClick}>
{_t("Share room")}
</Button>
<Button className="mx_RoomSummaryCard_icon_settings" onClick={onRoomSettingsClick}>
{_t("Room settings")}
</Button>
</Group>
{
SettingsStore.getValue(UIFeature.Widgets)
&& !isVideoRoom
&& shouldShowComponent(UIComponent.AddIntegrations)
&& <AppsSection room={room} />
}
</BaseCard>;
{SettingsStore.getValue(UIFeature.Widgets) &&
!isVideoRoom &&
shouldShowComponent(UIComponent.AddIntegrations) && <AppsSection room={room} />}
</BaseCard>
);
};
export default RoomSummaryCard;

View file

@ -14,34 +14,34 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { IEventRelation, MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { EventTimelineSet } from 'matrix-js-sdk/src/models/event-timeline-set';
import { NotificationCountType, Room } from 'matrix-js-sdk/src/models/room';
import { Thread } from 'matrix-js-sdk/src/models/thread';
import React from "react";
import { IEventRelation, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventTimelineSet } from "matrix-js-sdk/src/models/event-timeline-set";
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
import { Thread } from "matrix-js-sdk/src/models/thread";
import BaseCard from "./BaseCard";
import ResizeNotifier from '../../../utils/ResizeNotifier';
import MessageComposer from '../rooms/MessageComposer';
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import { Layout } from '../../../settings/enums/Layout';
import TimelinePanel from '../../structures/TimelinePanel';
import { E2EStatus } from '../../../utils/ShieldUtils';
import EditorStateTransfer from '../../../utils/EditorStateTransfer';
import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext';
import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler';
import { ActionPayload } from '../../../dispatcher/payloads';
import { Action } from '../../../dispatcher/actions';
import ContentMessages from '../../../ContentMessages';
import UploadBar from '../../structures/UploadBar';
import SettingsStore from '../../../settings/SettingsStore';
import JumpToBottomButton from '../rooms/JumpToBottomButton';
import ResizeNotifier from "../../../utils/ResizeNotifier";
import MessageComposer from "../rooms/MessageComposer";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { Layout } from "../../../settings/enums/Layout";
import TimelinePanel from "../../structures/TimelinePanel";
import { E2EStatus } from "../../../utils/ShieldUtils";
import EditorStateTransfer from "../../../utils/EditorStateTransfer";
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
import dis from "../../../dispatcher/dispatcher";
import { _t } from "../../../languageHandler";
import { ActionPayload } from "../../../dispatcher/payloads";
import { Action } from "../../../dispatcher/actions";
import ContentMessages from "../../../ContentMessages";
import UploadBar from "../../structures/UploadBar";
import SettingsStore from "../../../settings/SettingsStore";
import JumpToBottomButton from "../rooms/JumpToBottomButton";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import Measured from '../elements/Measured';
import Heading from '../typography/Heading';
import { UPDATE_EVENT } from '../../../stores/AsyncStore';
import { SdkContextClass } from '../../../contexts/SDKContext';
import Measured from "../elements/Measured";
import Heading from "../typography/Heading";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import { SdkContextClass } from "../../../contexts/SDKContext";
interface IProps {
room: Room;
@ -93,10 +93,10 @@ export default class TimelineCard extends React.Component<IProps, IState> {
public componentDidMount(): void {
SdkContextClass.instance.roomViewStore.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
this.dispatcherRef = dis.register(this.onAction);
this.readReceiptsSettingWatcher = SettingsStore.watchSetting("showReadReceipts", null, (...[,,, value]) =>
this.readReceiptsSettingWatcher = SettingsStore.watchSetting("showReadReceipts", null, (...[, , , value]) =>
this.setState({ showReadReceipts: value as boolean }),
);
this.layoutWatcherRef = SettingsStore.watchSetting("layout", null, (...[,,, value]) =>
this.layoutWatcherRef = SettingsStore.watchSetting("layout", null, (...[, , , value]) =>
this.setState({ layout: value as Layout }),
);
}
@ -127,13 +127,16 @@ export default class TimelineCard extends React.Component<IProps, IState> {
private onAction = (payload: ActionPayload): void => {
switch (payload.action) {
case Action.EditEvent:
this.setState({
editState: payload.event ? new EditorStateTransfer(payload.event) : null,
}, () => {
if (payload.event) {
this.timelinePanel.current?.scrollToEventIfNeeded(payload.event.getId());
}
});
this.setState(
{
editState: payload.event ? new EditorStateTransfer(payload.event) : null,
},
() => {
if (payload.event) {
this.timelinePanel.current?.scrollToEventIfNeeded(payload.event.getId());
}
},
);
break;
default:
break;
@ -186,22 +189,26 @@ export default class TimelineCard extends React.Component<IProps, IState> {
};
private renderTimelineCardHeader = (): JSX.Element => {
return <div className="mx_BaseCard_header_title">
<Heading size="h4" className="mx_BaseCard_header_title_heading">{ _t("Chat") }</Heading>
</div>;
return (
<div className="mx_BaseCard_header_title">
<Heading size="h4" className="mx_BaseCard_header_title_heading">
{_t("Chat")}
</Heading>
</div>
);
};
public render(): JSX.Element {
const highlightedEventId = this.state.isInitialEventHighlighted
? this.state.initialEventId
: null;
const highlightedEventId = this.state.isInitialEventHighlighted ? this.state.initialEventId : null;
let jumpToBottom;
if (!this.state.atEndOfLiveTimeline) {
jumpToBottom = (<JumpToBottomButton
highlight={this.props.room.getUnreadNotificationCount(NotificationCountType.Highlight) > 0}
onScrollToBottomClick={this.jumpToLiveTimeline}
/>);
jumpToBottom = (
<JumpToBottomButton
highlight={this.props.room.getUnreadNotificationCount(NotificationCountType.Highlight) > 0}
onScrollToBottomClick={this.jumpToLiveTimeline}
/>
);
}
const isUploading = ContentMessages.sharedInstance().getCurrentUploads(this.props.composerRelation).length > 0;
@ -210,12 +217,14 @@ export default class TimelineCard extends React.Component<IProps, IState> {
const showComposer = myMembership === "join";
return (
<RoomContext.Provider value={{
...this.context,
timelineRenderingType: this.props.timelineRenderingType ?? this.context.timelineRenderingType,
liveTimeline: this.props.timelineSet.getLiveTimeline(),
narrow: this.state.narrow,
}}>
<RoomContext.Provider
value={{
...this.context,
timelineRenderingType: this.props.timelineRenderingType ?? this.context.timelineRenderingType,
liveTimeline: this.props.timelineSet.getLiveTimeline(),
narrow: this.state.narrow,
}}
>
<BaseCard
className={this.props.classNames}
onClose={this.props.onClose}
@ -223,12 +232,9 @@ export default class TimelineCard extends React.Component<IProps, IState> {
header={this.renderTimelineCardHeader()}
ref={this.card}
>
<Measured
sensor={this.card.current}
onMeasurement={this.onMeasurement}
/>
<Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />
<div className="mx_TimelineCard_timeline">
{ jumpToBottom }
{jumpToBottom}
<TimelinePanel
ref={this.timelinePanel}
showReadReceipts={this.state.showReadReceipts}
@ -253,11 +259,9 @@ export default class TimelineCard extends React.Component<IProps, IState> {
/>
</div>
{ isUploading && (
<UploadBar room={this.props.room} relation={this.props.composerRelation} />
) }
{isUploading && <UploadBar room={this.props.room} relation={this.props.composerRelation} />}
{ showComposer && (
{showComposer && (
<MessageComposer
room={this.props.room}
relation={this.props.composerRelation}
@ -267,7 +271,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
e2eStatus={this.props.e2eStatus}
compact={true}
/>
) }
)}
</BaseCard>
</RoomContext.Provider>
);

File diff suppressed because it is too large Load diff

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import React from "react";
import { verificationMethods } from 'matrix-js-sdk/src/crypto';
import { verificationMethods } from "matrix-js-sdk/src/crypto";
import { QrCodeEvent, ReciprocateQRCode, SCAN_QR_CODE_METHOD } from "matrix-js-sdk/src/crypto/verification/QRCode";
import {
Phase,
@ -68,52 +68,64 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
const showQR: boolean = request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD);
const brand = SdkConfig.get().brand;
const noCommonMethodError: JSX.Element = !showSAS && !showQR ?
<p>{ _t(
"The device you are trying to verify doesn't support scanning a " +
"QR code or emoji verification, which is what %(brand)s supports. Try " +
"with a different client.",
{ brand },
) }</p> :
null;
const noCommonMethodError: JSX.Element =
!showSAS && !showQR ? (
<p>
{_t(
"The device you are trying to verify doesn't support scanning a " +
"QR code or emoji verification, which is what %(brand)s supports. Try " +
"with a different client.",
{ brand },
)}
</p>
) : null;
if (this.props.layout === 'dialog') {
if (this.props.layout === "dialog") {
// HACK: This is a terrible idea.
let qrBlockDialog: JSX.Element;
let sasBlockDialog: JSX.Element;
if (showQR) {
qrBlockDialog =
<div className='mx_VerificationPanel_QRPhase_startOption'>
<p>{ _t("Scan this unique code") }</p>
qrBlockDialog = (
<div className="mx_VerificationPanel_QRPhase_startOption">
<p>{_t("Scan this unique code")}</p>
<VerificationQRCode qrCodeData={request.qrCodeData} />
</div>;
</div>
);
}
if (showSAS) {
sasBlockDialog = <div className='mx_VerificationPanel_QRPhase_startOption'>
<p>{ _t("Compare unique emoji") }</p>
<span className='mx_VerificationPanel_QRPhase_helpText'>
{ _t("Compare a unique set of emoji if you don't have a camera on either device") }
</span>
<AccessibleButton disabled={this.state.emojiButtonClicked} onClick={this.startSAS} kind='primary'>
{ _t("Start") }
</AccessibleButton>
</div>;
sasBlockDialog = (
<div className="mx_VerificationPanel_QRPhase_startOption">
<p>{_t("Compare unique emoji")}</p>
<span className="mx_VerificationPanel_QRPhase_helpText">
{_t("Compare a unique set of emoji if you don't have a camera on either device")}
</span>
<AccessibleButton
disabled={this.state.emojiButtonClicked}
onClick={this.startSAS}
kind="primary"
>
{_t("Start")}
</AccessibleButton>
</div>
);
}
const or = qrBlockDialog && sasBlockDialog ?
<div className='mx_VerificationPanel_QRPhase_betweenText'>
{ _t("%(qrCode)s or %(emojiCompare)s", {
emojiCompare: "",
qrCode: "",
}) }
</div> : null;
const or =
qrBlockDialog && sasBlockDialog ? (
<div className="mx_VerificationPanel_QRPhase_betweenText">
{_t("%(qrCode)s or %(emojiCompare)s", {
emojiCompare: "",
qrCode: "",
})}
</div>
) : null;
return (
<div>
{ _t("Verify this device by completing one of the following:") }
<div className='mx_VerificationPanel_QRPhase_startOptions'>
{ qrBlockDialog }
{ or }
{ sasBlockDialog }
{ noCommonMethodError }
{_t("Verify this device by completing one of the following:")}
<div className="mx_VerificationPanel_QRPhase_startOptions">
{qrBlockDialog}
{or}
{sasBlockDialog}
{noCommonMethodError}
</div>
</div>
);
@ -121,50 +133,58 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
let qrBlock: JSX.Element;
if (showQR) {
qrBlock = <div className="mx_UserInfo_container">
<h3>{ _t("Verify by scanning") }</h3>
<p>{ _t("Ask %(displayName)s to scan your code:", {
displayName: (member as User).displayName || (member as RoomMember).name || member.userId,
}) }</p>
qrBlock = (
<div className="mx_UserInfo_container">
<h3>{_t("Verify by scanning")}</h3>
<p>
{_t("Ask %(displayName)s to scan your code:", {
displayName: (member as User).displayName || (member as RoomMember).name || member.userId,
})}
</p>
<div className="mx_VerificationPanel_qrCode">
<VerificationQRCode qrCodeData={request.qrCodeData} />
<div className="mx_VerificationPanel_qrCode">
<VerificationQRCode qrCodeData={request.qrCodeData} />
</div>
</div>
</div>;
);
}
let sasBlock: JSX.Element;
if (showSAS) {
const disabled = this.state.emojiButtonClicked;
const sasLabel = showQR ?
_t("If you can't scan the code above, verify by comparing unique emoji.") :
_t("Verify by comparing unique emoji.");
const sasLabel = showQR
? _t("If you can't scan the code above, verify by comparing unique emoji.")
: _t("Verify by comparing unique emoji.");
// Note: mx_VerificationPanel_verifyByEmojiButton is for the end-to-end tests
sasBlock = <div className="mx_UserInfo_container">
<h3>{ _t("Verify by emoji") }</h3>
<p>{ sasLabel }</p>
<AccessibleButton
disabled={disabled}
kind="primary"
className="mx_UserInfo_wideButton mx_VerificationPanel_verifyByEmojiButton"
onClick={this.startSAS}
>
{ _t("Verify by emoji") }
</AccessibleButton>
</div>;
sasBlock = (
<div className="mx_UserInfo_container">
<h3>{_t("Verify by emoji")}</h3>
<p>{sasLabel}</p>
<AccessibleButton
disabled={disabled}
kind="primary"
className="mx_UserInfo_wideButton mx_VerificationPanel_verifyByEmojiButton"
onClick={this.startSAS}
>
{_t("Verify by emoji")}
</AccessibleButton>
</div>
);
}
const noCommonMethodBlock = noCommonMethodError ?
<div className="mx_UserInfo_container">{ noCommonMethodError }</div> :
null;
const noCommonMethodBlock = noCommonMethodError ? (
<div className="mx_UserInfo_container">{noCommonMethodError}</div>
) : null;
// TODO: add way to open camera to scan a QR code
return <React.Fragment>
{ qrBlock }
{ sasBlock }
{ noCommonMethodBlock }
</React.Fragment>;
return (
<React.Fragment>
{qrBlock}
{sasBlock}
{noCommonMethodBlock}
</React.Fragment>
);
}
private onReciprocateYesClick = () => {
@ -184,41 +204,49 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
private renderQRReciprocatePhase() {
const { member, request } = this.props;
const description = request.isSelfVerification ?
_t("Almost there! Is your other device showing the same shield?") :
_t("Almost there! Is %(displayName)s showing the same shield?", {
displayName: (member as User).displayName || (member as RoomMember).name || member.userId,
});
const description = request.isSelfVerification
? _t("Almost there! Is your other device showing the same shield?")
: _t("Almost there! Is %(displayName)s showing the same shield?", {
displayName: (member as User).displayName || (member as RoomMember).name || member.userId,
});
let body: JSX.Element;
if (this.state.reciprocateQREvent) {
// Element Web doesn't support scanning yet, so assume here we're the client being scanned.
body = <React.Fragment>
<p>{ description }</p>
<E2EIcon isUser={true} status={E2EState.Verified} size={128} hideTooltip={true} />
<div className="mx_VerificationPanel_reciprocateButtons">
<AccessibleButton
kind="danger"
disabled={this.state.reciprocateButtonClicked}
onClick={this.onReciprocateNoClick}
>
{ _t("No") }
</AccessibleButton>
<AccessibleButton
kind="primary"
disabled={this.state.reciprocateButtonClicked}
onClick={this.onReciprocateYesClick}
>
{ _t("Yes") }
</AccessibleButton>
</div>
</React.Fragment>;
body = (
<React.Fragment>
<p>{description}</p>
<E2EIcon isUser={true} status={E2EState.Verified} size={128} hideTooltip={true} />
<div className="mx_VerificationPanel_reciprocateButtons">
<AccessibleButton
kind="danger"
disabled={this.state.reciprocateButtonClicked}
onClick={this.onReciprocateNoClick}
>
{_t("No")}
</AccessibleButton>
<AccessibleButton
kind="primary"
disabled={this.state.reciprocateButtonClicked}
onClick={this.onReciprocateYesClick}
>
{_t("Yes")}
</AccessibleButton>
</div>
</React.Fragment>
);
} else {
body = <p><Spinner /></p>;
body = (
<p>
<Spinner />
</p>
);
}
return <div className="mx_UserInfo_container mx_VerificationPanel_reciprocate_section">
<h3>{ _t("Verify by scanning") }</h3>
{ body }
</div>;
return (
<div className="mx_UserInfo_container mx_VerificationPanel_reciprocate_section">
<h3>{_t("Verify by scanning")}</h3>
{body}
</div>
);
}
private renderVerifiedPhase() {
@ -243,7 +271,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
description = _t("You've successfully verified your device!");
} else {
description = _t("You've successfully verified %(deviceName)s (%(deviceId)s)!", {
deviceName: device ? device.getDisplayName() : '',
deviceName: device ? device.getDisplayName() : "",
deviceId: this.props.request.channel.deviceId,
});
}
@ -255,11 +283,11 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
return (
<div className="mx_UserInfo_container mx_VerificationPanel_verified_section">
<p>{ description }</p>
<p>{description}</p>
<E2EIcon isUser={true} status={E2EState.Verified} size={128} hideTooltip={true} />
{ text ? <p>{ text }</p> : null }
{text ? <p>{text}</p> : null}
<AccessibleButton kind="primary" className="mx_UserInfo_wideButton" onClick={this.props.onClose}>
{ _t("Got it") }
{_t("Got it")}
</AccessibleButton>
</div>
);
@ -293,11 +321,11 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
return (
<div className="mx_UserInfo_container">
<h3>{ _t("Verification cancelled") }</h3>
<p>{ text }</p>
<h3>{_t("Verification cancelled")}</h3>
<p>{text}</p>
<AccessibleButton kind="primary" className="mx_UserInfo_wideButton" onClick={this.props.onClose}>
{ _t("Got it") }
{_t("Got it")}
</AccessibleButton>
</div>
);
@ -316,7 +344,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
case verificationMethods.RECIPROCATE_QR_CODE:
return this.renderQRReciprocatePhase();
case verificationMethods.SAS: {
const emojis = this.state.sasEvent ?
const emojis = this.state.sasEvent ? (
<VerificationShowSas
displayName={displayName}
device={this.getDevice()}
@ -325,10 +353,11 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
onDone={this.onSasMatchesClick}
inDialog={this.props.inDialog}
isSelf={request.isSelfVerification}
/> : <Spinner />;
return <div className="mx_UserInfo_container">
{ emojis }
</div>;
/>
) : (
<Spinner />
);
return <div className="mx_UserInfo_container">{emojis}</div>;
}
default:
return null;

View file

@ -28,7 +28,7 @@ import WidgetContextMenu from "../context_menus/WidgetContextMenu";
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
import UIStore from "../../../stores/UIStore";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import Heading from '../typography/Heading';
import Heading from "../typography/Heading";
interface IProps {
room: Room;
@ -40,7 +40,7 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
const cli = useContext(MatrixClientContext);
const apps = useWidgets(room);
const app = apps.find(a => a.id === widgetId);
const app = apps.find((a) => a.id === widgetId);
const isRight = app && WidgetLayoutStore.instance.isInContainer(room, app, Container.Right);
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
@ -69,35 +69,36 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
);
}
const header = <div className="mx_BaseCard_header_title">
<Heading size="h4" className="mx_BaseCard_header_title_heading">{ WidgetUtils.getWidgetName(app) }</Heading>
<ContextMenuButton
className="mx_BaseCard_header_title_button--option"
inputRef={handle}
onClick={openMenu}
isExpanded={menuDisplayed}
label={_t("Options")}
/>
{ contextMenu }
</div>;
const header = (
<div className="mx_BaseCard_header_title">
<Heading size="h4" className="mx_BaseCard_header_title_heading">
{WidgetUtils.getWidgetName(app)}
</Heading>
<ContextMenuButton
className="mx_BaseCard_header_title_button--option"
inputRef={handle}
onClick={openMenu}
isExpanded={menuDisplayed}
label={_t("Options")}
/>
{contextMenu}
</div>
);
return <BaseCard
header={header}
className="mx_WidgetCard"
onClose={onClose}
withoutScrollContainer
>
<AppTile
app={app}
fullWidth
showMenubar={false}
room={room}
userId={cli.getUserId()}
creatorUserId={app.creatorUserId}
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
waitForIframeLoad={app.waitForIframeLoad}
/>
</BaseCard>;
return (
<BaseCard header={header} className="mx_WidgetCard" onClose={onClose} withoutScrollContainer>
<AppTile
app={app}
fullWidth
showMenubar={false}
room={room}
userId={cli.getUserId()}
creatorUserId={app.creatorUserId}
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
waitForIframeLoad={app.waitForIframeLoad}
/>
</BaseCard>
);
};
export default WidgetCard;