Merge branch 'develop' of https://github.com/yaya-usman/matrix-react-sdk into favouriteMessages_Panel
This commit is contained in:
commit
a53f7f8302
70 changed files with 2195 additions and 996 deletions
|
@ -14,13 +14,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import React, { HTMLProps, useContext } from 'react';
|
||||
import { Beacon, BeaconEvent } from 'matrix-js-sdk/src/matrix';
|
||||
import { LocationAssetType } from 'matrix-js-sdk/src/@types/location';
|
||||
|
||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
||||
import { humanizeTime } from '../../../utils/humanize';
|
||||
import { preventDefaultWrapper } from '../../../utils/NativeEventUtils';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import MemberAvatar from '../avatars/MemberAvatar';
|
||||
import BeaconStatus from './BeaconStatus';
|
||||
|
@ -32,7 +33,7 @@ interface Props {
|
|||
beacon: Beacon;
|
||||
}
|
||||
|
||||
const BeaconListItem: React.FC<Props> = ({ beacon }) => {
|
||||
const BeaconListItem: React.FC<Props & HTMLProps<HTMLLIElement>> = ({ beacon, ...rest }) => {
|
||||
const latestLocationState = useEventEmitterState(
|
||||
beacon,
|
||||
BeaconEvent.LocationUpdate,
|
||||
|
@ -52,7 +53,7 @@ const BeaconListItem: React.FC<Props> = ({ beacon }) => {
|
|||
|
||||
const humanizedUpdateTime = humanizeTime(latestLocationState.timestamp);
|
||||
|
||||
return <li className='mx_BeaconListItem'>
|
||||
return <li className='mx_BeaconListItem' {...rest}>
|
||||
{ isSelfLocation ?
|
||||
<MemberAvatar
|
||||
className='mx_BeaconListItem_avatar'
|
||||
|
@ -69,7 +70,11 @@ const BeaconListItem: React.FC<Props> = ({ beacon }) => {
|
|||
label={beaconMember?.name || beacon.beaconInfo.description || beacon.beaconInfoOwner}
|
||||
displayStatus={BeaconDisplayStatus.Active}
|
||||
>
|
||||
<ShareLatestLocation latestLocationState={latestLocationState} />
|
||||
{ /* eat events from interactive share buttons
|
||||
so parent click handlers are not triggered */ }
|
||||
<div className='mx_BeaconListItem_interactions' onClick={preventDefaultWrapper(() => {})}>
|
||||
<ShareLatestLocation latestLocationState={latestLocationState} />
|
||||
</div>
|
||||
</BeaconStatus>
|
||||
<span className='mx_BeaconListItem_lastUpdated'>{ _t("Updated %(humanizedUpdateTime)s", { humanizedUpdateTime }) }</span>
|
||||
</div>
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||
import {
|
||||
Beacon,
|
||||
|
@ -45,7 +45,16 @@ interface IProps extends IDialogProps {
|
|||
roomId: Room['roomId'];
|
||||
matrixClient: MatrixClient;
|
||||
// open the map centered on this beacon's location
|
||||
focusBeacon?: Beacon;
|
||||
initialFocusedBeacon?: Beacon;
|
||||
}
|
||||
|
||||
// track the 'focused time' as ts
|
||||
// to make it possible to refocus the same beacon
|
||||
// as the beacon location may change
|
||||
// or the map may move around
|
||||
interface FocusedBeaconState {
|
||||
ts: number;
|
||||
beacon?: Beacon;
|
||||
}
|
||||
|
||||
const getBoundsCenter = (bounds: Bounds): string | undefined => {
|
||||
|
@ -59,31 +68,52 @@ const getBoundsCenter = (bounds: Bounds): string | undefined => {
|
|||
});
|
||||
};
|
||||
|
||||
const useInitialMapPosition = (liveBeacons: Beacon[], focusBeacon?: Beacon): {
|
||||
const useInitialMapPosition = (liveBeacons: Beacon[], { beacon, ts }: FocusedBeaconState): {
|
||||
bounds?: Bounds; centerGeoUri: string;
|
||||
} => {
|
||||
const bounds = useRef<Bounds | undefined>(getBeaconBounds(liveBeacons));
|
||||
const centerGeoUri = useRef<string>(
|
||||
focusBeacon?.latestLocationState?.uri ||
|
||||
getBoundsCenter(bounds.current),
|
||||
const [bounds, setBounds] = useState<Bounds | undefined>(getBeaconBounds(liveBeacons));
|
||||
const [centerGeoUri, setCenterGeoUri] = useState<string>(
|
||||
beacon?.latestLocationState?.uri ||
|
||||
getBoundsCenter(bounds),
|
||||
);
|
||||
return { bounds: bounds.current, centerGeoUri: centerGeoUri.current };
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
// this check ignores the first initial focused beacon state
|
||||
// as centering logic on map zooms to show everything
|
||||
// instead of focusing down
|
||||
ts !== 0 &&
|
||||
// only set focus to a known location
|
||||
beacon?.latestLocationState?.uri
|
||||
) {
|
||||
// append custom `mxTs` parameter to geoUri
|
||||
// so map is triggered to refocus on this uri
|
||||
// event if it was previously the center geouri
|
||||
// but the map have moved/zoomed
|
||||
setCenterGeoUri(`${beacon?.latestLocationState?.uri};mxTs=${Date.now()}`);
|
||||
setBounds(getBeaconBounds([beacon]));
|
||||
}
|
||||
}, [beacon, ts]);
|
||||
|
||||
return { bounds, centerGeoUri };
|
||||
};
|
||||
|
||||
/**
|
||||
* Dialog to view live beacons maximised
|
||||
*/
|
||||
const BeaconViewDialog: React.FC<IProps> = ({
|
||||
focusBeacon,
|
||||
initialFocusedBeacon,
|
||||
roomId,
|
||||
matrixClient,
|
||||
onFinished,
|
||||
}) => {
|
||||
const liveBeacons = useLiveBeacons(roomId, matrixClient);
|
||||
const [focusedBeaconState, setFocusedBeaconState] =
|
||||
useState<FocusedBeaconState>({ beacon: initialFocusedBeacon, ts: 0 });
|
||||
|
||||
const [isSidebarOpen, setSidebarOpen] = useState(false);
|
||||
|
||||
const { bounds, centerGeoUri } = useInitialMapPosition(liveBeacons, focusBeacon);
|
||||
const { bounds, centerGeoUri } = useInitialMapPosition(liveBeacons, focusedBeaconState);
|
||||
|
||||
const [mapDisplayError, setMapDisplayError] = useState<Error>();
|
||||
|
||||
|
@ -94,6 +124,10 @@ const BeaconViewDialog: React.FC<IProps> = ({
|
|||
}
|
||||
}, [mapDisplayError]);
|
||||
|
||||
const onBeaconListItemClick = (beacon: Beacon) => {
|
||||
setFocusedBeaconState({ beacon, ts: Date.now() });
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
className='mx_BeaconViewDialog'
|
||||
|
@ -144,7 +178,7 @@ const BeaconViewDialog: React.FC<IProps> = ({
|
|||
</MapFallback>
|
||||
}
|
||||
{ isSidebarOpen ?
|
||||
<DialogSidebar beacons={liveBeacons} requestClose={() => setSidebarOpen(false)} /> :
|
||||
<DialogSidebar beacons={liveBeacons} onBeaconClick={onBeaconListItemClick} requestClose={() => setSidebarOpen(false)} /> :
|
||||
<AccessibleButton
|
||||
kind='primary'
|
||||
onClick={() => setSidebarOpen(true)}
|
||||
|
|
|
@ -26,9 +26,14 @@ import BeaconListItem from './BeaconListItem';
|
|||
interface Props {
|
||||
beacons: Beacon[];
|
||||
requestClose: () => void;
|
||||
onBeaconClick: (beacon: Beacon) => void;
|
||||
}
|
||||
|
||||
const DialogSidebar: React.FC<Props> = ({ beacons, requestClose }) => {
|
||||
const DialogSidebar: React.FC<Props> = ({
|
||||
beacons,
|
||||
onBeaconClick,
|
||||
requestClose,
|
||||
}) => {
|
||||
return <div className='mx_DialogSidebar'>
|
||||
<div className='mx_DialogSidebar_header'>
|
||||
<Heading size='h4'>{ _t('View List') }</Heading>
|
||||
|
@ -36,13 +41,17 @@ const DialogSidebar: React.FC<Props> = ({ beacons, requestClose }) => {
|
|||
className='mx_DialogSidebar_closeButton'
|
||||
onClick={requestClose}
|
||||
title={_t('Close sidebar')}
|
||||
data-test-id='dialog-sidebar-close'
|
||||
data-testid='dialog-sidebar-close'
|
||||
>
|
||||
<CloseIcon className='mx_DialogSidebar_closeButtonIcon' />
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<ol className='mx_DialogSidebar_list'>
|
||||
{ beacons.map((beacon) => <BeaconListItem key={beacon.identifier} beacon={beacon} />) }
|
||||
{ beacons.map((beacon) => <BeaconListItem
|
||||
key={beacon.identifier}
|
||||
beacon={beacon}
|
||||
onClick={() => onBeaconClick(beacon)}
|
||||
/>) }
|
||||
</ol>
|
||||
</div>;
|
||||
};
|
||||
|
|
|
@ -19,6 +19,7 @@ import React, { HTMLProps } from 'react';
|
|||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { useOwnLiveBeacons } from '../../../utils/beacon';
|
||||
import { preventDefaultWrapper } from '../../../utils/NativeEventUtils';
|
||||
import BeaconStatus from './BeaconStatus';
|
||||
import { BeaconDisplayStatus } from './displayStatus';
|
||||
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
|
||||
|
@ -45,14 +46,6 @@ const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({
|
|||
onResetLocationPublishError,
|
||||
} = useOwnLiveBeacons([beacon?.identifier]);
|
||||
|
||||
// eat events here to avoid 1) the map and 2) reply or thread tiles
|
||||
// moving under the beacon status on stop/retry click
|
||||
const preventDefaultWrapper = (callback: () => void) => (e?: ButtonEvent) => {
|
||||
e?.stopPropagation();
|
||||
e?.preventDefault();
|
||||
callback();
|
||||
};
|
||||
|
||||
// combine display status with errors that only occur for user's own beacons
|
||||
const ownDisplayStatus = hasLocationPublishError || hasStopSharingError ?
|
||||
BeaconDisplayStatus.Error :
|
||||
|
@ -68,7 +61,9 @@ const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({
|
|||
{ ownDisplayStatus === BeaconDisplayStatus.Active && <AccessibleButton
|
||||
data-test-id='beacon-status-stop-beacon'
|
||||
kind='link'
|
||||
onClick={preventDefaultWrapper(onStopSharing)}
|
||||
// eat events here to avoid 1) the map and 2) reply or thread tiles
|
||||
// moving under the beacon status on stop/retry click
|
||||
onClick={preventDefaultWrapper<ButtonEvent>(onStopSharing)}
|
||||
className='mx_OwnBeaconStatus_button mx_OwnBeaconStatus_destructiveButton'
|
||||
disabled={stoppingInProgress}
|
||||
>
|
||||
|
@ -78,6 +73,8 @@ const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({
|
|||
{ hasLocationPublishError && <AccessibleButton
|
||||
data-test-id='beacon-status-reset-wire-error'
|
||||
kind='link'
|
||||
// eat events here to avoid 1) the map and 2) reply or thread tiles
|
||||
// moving under the beacon status on stop/retry click
|
||||
onClick={preventDefaultWrapper(onResetLocationPublishError)}
|
||||
className='mx_OwnBeaconStatus_button mx_OwnBeaconStatus_destructiveButton'
|
||||
>
|
||||
|
@ -87,6 +84,8 @@ const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({
|
|||
{ hasStopSharingError && <AccessibleButton
|
||||
data-test-id='beacon-status-stop-beacon-retry'
|
||||
kind='link'
|
||||
// eat events here to avoid 1) the map and 2) reply or thread tiles
|
||||
// moving under the beacon status on stop/retry click
|
||||
onClick={preventDefaultWrapper(onStopSharing)}
|
||||
className='mx_OwnBeaconStatus_button mx_OwnBeaconStatus_destructiveButton'
|
||||
>
|
||||
|
|
|
@ -91,6 +91,7 @@ import { PublicRoomResultDetails } from "./PublicRoomResultDetails";
|
|||
import { RoomResultContextMenus } from "./RoomResultContextMenus";
|
||||
import { RoomContextDetails } from "../../rooms/RoomContextDetails";
|
||||
import { TooltipOption } from "./TooltipOption";
|
||||
import { isLocalRoom } from "../../../../utils/localRoom/isLocalRoom";
|
||||
|
||||
const MAX_RECENT_SEARCHES = 10;
|
||||
const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons
|
||||
|
@ -243,6 +244,9 @@ export const useWebSearchMetrics = (numResults: number, queryLength: number, via
|
|||
|
||||
const findVisibleRooms = (cli: MatrixClient) => {
|
||||
return cli.getVisibleRooms().filter(room => {
|
||||
// Do not show local rooms
|
||||
if (isLocalRoom(room)) return false;
|
||||
|
||||
// TODO we may want to put invites in their own list
|
||||
return room.getMyMembership() === "join" || room.getMyMembership() == "invite";
|
||||
});
|
||||
|
@ -395,7 +399,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
|
||||
possibleResults.forEach(entry => {
|
||||
if (isRoomResult(entry)) {
|
||||
if (!entry.room.normalizedName.includes(normalizedQuery) &&
|
||||
if (!entry.room.normalizedName?.includes(normalizedQuery) &&
|
||||
!entry.room.getCanonicalAlias()?.toLowerCase().includes(lcQuery) &&
|
||||
!entry.query?.some(q => q.includes(lcQuery))
|
||||
) return; // bail, does not match query
|
||||
|
@ -603,6 +607,16 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
}
|
||||
if (isPublicRoomResult(result)) {
|
||||
const clientRoom = cli.getRoom(result.publicRoom.room_id);
|
||||
// Element Web currently does not allow guests to join rooms, so we
|
||||
// instead show them view buttons for all rooms. If the room is not
|
||||
// world readable, a modal will appear asking you to register first. If
|
||||
// it is readable, the preview appears as normal.
|
||||
const showViewButton = (
|
||||
clientRoom?.getMyMembership() === "join" ||
|
||||
result.publicRoom.world_readable ||
|
||||
cli.isGuest()
|
||||
);
|
||||
|
||||
const listener = (ev) => {
|
||||
const { publicRoom } = result;
|
||||
viewRoom({
|
||||
|
@ -618,11 +632,11 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
onClick={listener}
|
||||
endAdornment={
|
||||
<AccessibleButton
|
||||
kind={clientRoom ? "primary" : "primary_outline"}
|
||||
kind={showViewButton ? "primary_outline" : "primary"}
|
||||
onClick={listener}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{ _t(clientRoom ? "View" : "Join") }
|
||||
{ showViewButton ? _t("View") : _t("Join") }
|
||||
</AccessibleButton>}
|
||||
aria-labelledby={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_name`}
|
||||
aria-describedby={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_alias`}
|
||||
|
|
|
@ -80,6 +80,13 @@ const useMapWithStyle = ({ id, centerGeoUri, onError, interactive, bounds }) =>
|
|||
interface MapProps {
|
||||
id: string;
|
||||
interactive?: boolean;
|
||||
/**
|
||||
* set map center to geoUri coords
|
||||
* Center will only be set to valid geoUri
|
||||
* this prop is only simply diffed by useEffect, so to trigger *recentering* of the same geoUri
|
||||
* append the uri with a var not used by the geoUri spec
|
||||
* eg a timestamp: `geo:54,42;mxTs=123`
|
||||
*/
|
||||
centerGeoUri?: string;
|
||||
bounds?: Bounds;
|
||||
className?: string;
|
||||
|
|
|
@ -24,6 +24,7 @@ import EventTileBubble from "./EventTileBubble";
|
|||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import { objectHasDiff } from "../../../utils/objects";
|
||||
import { isLocalRoom } from '../../../utils/localRoom/isLocalRoom';
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
|
@ -46,12 +47,15 @@ const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent, timestamp
|
|||
if (content.algorithm === ALGORITHM && isRoomEncrypted) {
|
||||
let subtitle: string;
|
||||
const dmPartner = DMRoomMap.shared().getUserIdForRoomId(roomId);
|
||||
const room = cli?.getRoom(roomId);
|
||||
if (prevContent.algorithm === ALGORITHM) {
|
||||
subtitle = _t("Some encryption parameters have been changed.");
|
||||
} else if (dmPartner) {
|
||||
const displayName = cli?.getRoom(roomId)?.getMember(dmPartner)?.rawDisplayName || dmPartner;
|
||||
const displayName = room.getMember(dmPartner)?.rawDisplayName || dmPartner;
|
||||
subtitle = _t("Messages here are end-to-end encrypted. " +
|
||||
"Verify %(displayName)s in their profile - tap on their avatar.", { displayName });
|
||||
} else if (isLocalRoom(room)) {
|
||||
subtitle = _t("Messages in this chat will be end-to-end encrypted.");
|
||||
} else {
|
||||
subtitle = _t("Messages in this room are end-to-end encrypted. " +
|
||||
"When people join, you can verify them in their profile, just tap on their avatar.");
|
||||
|
|
|
@ -162,7 +162,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
|
|||
{
|
||||
roomId: mxEvent.getRoomId(),
|
||||
matrixClient,
|
||||
focusBeacon: beacon,
|
||||
initialFocusedBeacon: beacon,
|
||||
isMapDisplayError,
|
||||
},
|
||||
"mx_BeaconViewDialog_wrapper",
|
||||
|
|
|
@ -80,6 +80,7 @@ import { haveRendererForEvent, isMessageEvent, renderTile } from "../../../event
|
|||
import ThreadSummary, { ThreadMessagePreview } from "./ThreadSummary";
|
||||
import { ReadReceiptGroup } from './ReadReceiptGroup';
|
||||
import { useTooltip } from "../../../utils/useTooltip";
|
||||
import { isLocalRoom } from '../../../utils/localRoom/isLocalRoom';
|
||||
|
||||
export type GetRelationsForEvent = (eventId: string, relationType: string, eventType: string) => Relations;
|
||||
|
||||
|
@ -766,6 +767,9 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
private renderE2EPadlock() {
|
||||
const ev = this.props.mxEvent;
|
||||
|
||||
// no icon for local rooms
|
||||
if (isLocalRoom(ev.getRoomId())) return;
|
||||
|
||||
// event could not be decrypted
|
||||
if (ev.getContent().msgtype === 'm.bad.encrypted') {
|
||||
return <E2ePadlockUndecryptable />;
|
||||
|
|
|
@ -38,6 +38,7 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
|||
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
||||
import { UIComponent } from "../../../settings/UIFeature";
|
||||
import { privateShouldBeEncrypted } from "../../../utils/rooms";
|
||||
import { LocalRoom } from "../../../models/LocalRoom";
|
||||
|
||||
function hasExpectedEncryptionSettings(matrixClient: MatrixClient, room: Room): boolean {
|
||||
const isEncrypted: boolean = matrixClient.isRoomEncrypted(room.roomId);
|
||||
|
@ -49,11 +50,19 @@ const NewRoomIntro = () => {
|
|||
const cli = useContext(MatrixClientContext);
|
||||
const { room, roomId } = useContext(RoomContext);
|
||||
|
||||
const dmPartner = DMRoomMap.shared().getUserIdForRoomId(roomId);
|
||||
const isLocalRoom = room instanceof LocalRoom;
|
||||
const dmPartner = isLocalRoom
|
||||
? room.targets[0]?.userId
|
||||
: DMRoomMap.shared().getUserIdForRoomId(roomId);
|
||||
|
||||
let body;
|
||||
if (dmPartner) {
|
||||
let introMessage = _t("This is the beginning of your direct message history with <displayName/>.");
|
||||
let caption;
|
||||
if ((room.getJoinedMemberCount() + room.getInvitedMemberCount()) === 2) {
|
||||
|
||||
if (isLocalRoom) {
|
||||
introMessage = _t("Send your first message to invite <displayName/> to chat");
|
||||
} else if ((room.getJoinedMemberCount() + room.getInvitedMemberCount()) === 2) {
|
||||
caption = _t("Only the two of you are in this conversation, unless either of you invites anyone to join.");
|
||||
}
|
||||
|
||||
|
@ -75,7 +84,7 @@ const NewRoomIntro = () => {
|
|||
|
||||
<h2>{ room.name }</h2>
|
||||
|
||||
<p>{ _t("This is the beginning of your direct message history with <displayName/>.", {}, {
|
||||
<p>{ _t(introMessage, {}, {
|
||||
displayName: () => <b>{ displayName }</b>,
|
||||
}) }</p>
|
||||
{ caption && <p>{ caption }</p> }
|
||||
|
@ -200,7 +209,7 @@ const NewRoomIntro = () => {
|
|||
);
|
||||
|
||||
let subButton;
|
||||
if (room.currentState.mayClientSendStateEvent(EventType.RoomEncryption, MatrixClientPeg.get())) {
|
||||
if (room.currentState.mayClientSendStateEvent(EventType.RoomEncryption, MatrixClientPeg.get()) && !isLocalRoom) {
|
||||
subButton = (
|
||||
<AccessibleButton kind='link_inline' onClick={openRoomSettings}>{ _t("Enable encryption in settings.") }</AccessibleButton>
|
||||
);
|
||||
|
|
|
@ -65,6 +65,8 @@ interface IProps {
|
|||
appsShown: boolean;
|
||||
searchInfo: ISearchInfo;
|
||||
excludedRightPanelPhaseButtons?: Array<RightPanelPhases>;
|
||||
showButtons?: boolean;
|
||||
enableRoomOptionsMenu?: boolean;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -76,6 +78,8 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
editing: false,
|
||||
inRoom: false,
|
||||
excludedRightPanelPhaseButtons: [],
|
||||
showButtons: true,
|
||||
enableRoomOptionsMenu: true,
|
||||
};
|
||||
|
||||
static contextType = RoomContext;
|
||||
|
@ -130,81 +134,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
this.setState({ contextMenuPosition: null });
|
||||
};
|
||||
|
||||
public render() {
|
||||
let searchStatus = null;
|
||||
|
||||
// don't display the search count until the search completes and
|
||||
// gives us a valid (possibly zero) searchCount.
|
||||
if (this.props.searchInfo &&
|
||||
this.props.searchInfo.searchCount !== undefined &&
|
||||
this.props.searchInfo.searchCount !== null) {
|
||||
searchStatus = <div className="mx_RoomHeader_searchStatus">
|
||||
{ _t("(~%(count)s results)", { count: this.props.searchInfo.searchCount }) }
|
||||
</div>;
|
||||
}
|
||||
|
||||
// XXX: this is a bit inefficient - we could just compare room.name for 'Empty room'...
|
||||
let settingsHint = false;
|
||||
const members = this.props.room ? this.props.room.getJoinedMembers() : undefined;
|
||||
if (members) {
|
||||
if (members.length === 1 && members[0].userId === MatrixClientPeg.get().credentials.userId) {
|
||||
const nameEvent = this.props.room.currentState.getStateEvents('m.room.name', '');
|
||||
if (!nameEvent || !nameEvent.getContent().name) {
|
||||
settingsHint = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let oobName = _t("Join Room");
|
||||
if (this.props.oobData && this.props.oobData.name) {
|
||||
oobName = this.props.oobData.name;
|
||||
}
|
||||
|
||||
let contextMenu: JSX.Element;
|
||||
if (this.state.contextMenuPosition && this.props.room) {
|
||||
contextMenu = (
|
||||
<RoomContextMenu
|
||||
{...contextMenuBelow(this.state.contextMenuPosition)}
|
||||
room={this.props.room}
|
||||
onFinished={this.onContextMenuCloseClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const textClasses = classNames('mx_RoomHeader_nametext', { mx_RoomHeader_settingsHint: settingsHint });
|
||||
const name = (
|
||||
<ContextMenuTooltipButton
|
||||
className="mx_RoomHeader_name"
|
||||
onClick={this.onContextMenuOpenClick}
|
||||
isExpanded={!!this.state.contextMenuPosition}
|
||||
title={_t("Room options")}
|
||||
>
|
||||
<RoomName room={this.props.room}>
|
||||
{ (name) => {
|
||||
const roomName = name || oobName;
|
||||
return <div dir="auto" className={textClasses} title={roomName}>{ roomName }</div>;
|
||||
} }
|
||||
</RoomName>
|
||||
{ this.props.room && <div className="mx_RoomHeader_chevron" /> }
|
||||
{ contextMenu }
|
||||
</ContextMenuTooltipButton>
|
||||
);
|
||||
|
||||
const topicElement = <RoomTopic
|
||||
room={this.props.room}
|
||||
className="mx_RoomHeader_topic"
|
||||
/>;
|
||||
|
||||
let roomAvatar;
|
||||
if (this.props.room) {
|
||||
roomAvatar = <DecoratedRoomAvatar
|
||||
room={this.props.room}
|
||||
avatarSize={24}
|
||||
oobData={this.props.oobData}
|
||||
viewAvatarOnClick={true}
|
||||
/>;
|
||||
}
|
||||
|
||||
private renderButtons(): JSX.Element[] {
|
||||
const buttons: JSX.Element[] = [];
|
||||
|
||||
if (this.props.inRoom &&
|
||||
|
@ -269,10 +199,105 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
buttons.push(inviteButton);
|
||||
}
|
||||
|
||||
const rightRow =
|
||||
<div className="mx_RoomHeader_buttons">
|
||||
{ buttons }
|
||||
return buttons;
|
||||
}
|
||||
|
||||
private renderName(oobName) {
|
||||
let contextMenu: JSX.Element;
|
||||
if (this.state.contextMenuPosition && this.props.room) {
|
||||
contextMenu = (
|
||||
<RoomContextMenu
|
||||
{...contextMenuBelow(this.state.contextMenuPosition)}
|
||||
room={this.props.room}
|
||||
onFinished={this.onContextMenuCloseClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// XXX: this is a bit inefficient - we could just compare room.name for 'Empty room'...
|
||||
let settingsHint = false;
|
||||
const members = this.props.room ? this.props.room.getJoinedMembers() : undefined;
|
||||
if (members) {
|
||||
if (members.length === 1 && members[0].userId === MatrixClientPeg.get().credentials.userId) {
|
||||
const nameEvent = this.props.room.currentState.getStateEvents('m.room.name', '');
|
||||
if (!nameEvent || !nameEvent.getContent().name) {
|
||||
settingsHint = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const textClasses = classNames('mx_RoomHeader_nametext', { mx_RoomHeader_settingsHint: settingsHint });
|
||||
const roomName = <RoomName room={this.props.room}>
|
||||
{ (name) => {
|
||||
const roomName = name || oobName;
|
||||
return <div dir="auto" className={textClasses} title={roomName}>{ roomName }</div>;
|
||||
} }
|
||||
</RoomName>;
|
||||
|
||||
if (this.props.enableRoomOptionsMenu) {
|
||||
return (
|
||||
<ContextMenuTooltipButton
|
||||
className="mx_RoomHeader_name"
|
||||
onClick={this.onContextMenuOpenClick}
|
||||
isExpanded={!!this.state.contextMenuPosition}
|
||||
title={_t("Room options")}
|
||||
>
|
||||
{ roomName }
|
||||
{ this.props.room && <div className="mx_RoomHeader_chevron" /> }
|
||||
{ contextMenu }
|
||||
</ContextMenuTooltipButton>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="mx_RoomHeader_name mx_RoomHeader_name--textonly">
|
||||
{ roomName }
|
||||
</div>;
|
||||
}
|
||||
|
||||
public render() {
|
||||
let searchStatus = null;
|
||||
|
||||
// don't display the search count until the search completes and
|
||||
// gives us a valid (possibly zero) searchCount.
|
||||
if (this.props.searchInfo &&
|
||||
this.props.searchInfo.searchCount !== undefined &&
|
||||
this.props.searchInfo.searchCount !== null) {
|
||||
searchStatus = <div className="mx_RoomHeader_searchStatus">
|
||||
{ _t("(~%(count)s results)", { count: this.props.searchInfo.searchCount }) }
|
||||
</div>;
|
||||
}
|
||||
|
||||
let oobName = _t("Join Room");
|
||||
if (this.props.oobData && this.props.oobData.name) {
|
||||
oobName = this.props.oobData.name;
|
||||
}
|
||||
|
||||
const name = this.renderName(oobName);
|
||||
|
||||
const topicElement = <RoomTopic
|
||||
room={this.props.room}
|
||||
className="mx_RoomHeader_topic"
|
||||
/>;
|
||||
|
||||
let roomAvatar;
|
||||
if (this.props.room) {
|
||||
roomAvatar = <DecoratedRoomAvatar
|
||||
room={this.props.room}
|
||||
avatarSize={24}
|
||||
oobData={this.props.oobData}
|
||||
viewAvatarOnClick={true}
|
||||
/>;
|
||||
}
|
||||
|
||||
let buttons;
|
||||
if (this.props.showButtons) {
|
||||
buttons = <React.Fragment>
|
||||
<div className="mx_RoomHeader_buttons">
|
||||
{ this.renderButtons() }
|
||||
</div>
|
||||
<RoomHeaderButtons room={this.props.room} excludedRightPanelPhaseButtons={this.props.excludedRightPanelPhaseButtons} />
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
const e2eIcon = this.props.e2eStatus ? <E2EIcon status={this.props.e2eStatus} /> : undefined;
|
||||
|
||||
|
@ -294,8 +319,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
{ searchStatus }
|
||||
{ topicElement }
|
||||
{ betaPill }
|
||||
{ rightRow }
|
||||
<RoomHeaderButtons room={this.props.room} excludedRightPanelPhaseButtons={this.props.excludedRightPanelPhaseButtons} />
|
||||
{ buttons }
|
||||
</div>
|
||||
<RoomLiveShareWarning roomId={this.props.room.roomId} />
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue