Files
ThreadNet-Web/src/components/views/rooms/PinnedEventTile.tsx
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

242 lines
9.0 KiB
TypeScript
Raw Normal View History

2017-09-29 13:14:56 -06:00
/*
Copyright 2017 Travis Ralston
2021-05-25 15:44:39 +01:00
Copyright 2021 The Matrix.org Foundation C.I.C.
2017-09-29 13:14:56 -06:00
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 React, { JSX, useCallback, useState } from "react";
import { EventTimeline, EventType, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import { IconButton, Menu, MenuItem, Separator, Tooltip } from "@vector-im/compound-web";
import { Icon as ViewIcon } from "@vector-im/compound-design-tokens/icons/visibility-on.svg";
import { Icon as UnpinIcon } from "@vector-im/compound-design-tokens/icons/unpin.svg";
import { Icon as ForwardIcon } from "@vector-im/compound-design-tokens/icons/forward.svg";
import { Icon as TriggerIcon } from "@vector-im/compound-design-tokens/icons/overflow-horizontal.svg";
import { Icon as DeleteIcon } from "@vector-im/compound-design-tokens/icons/delete.svg";
import { Icon as ThreadIcon } from "@vector-im/compound-design-tokens/icons/threads.svg";
import classNames from "classnames";
2021-05-25 15:44:39 +01:00
2020-05-13 20:41:41 -06:00
import dis from "../../../dispatcher/dispatcher";
2021-11-25 17:49:43 -03:00
import { Action } from "../../../dispatcher/actions";
2017-09-29 13:14:56 -06:00
import MessageEvent from "../messages/MessageEvent";
import MemberAvatar from "../avatars/MemberAvatar";
import { _t } from "../../../languageHandler";
2021-05-26 13:51:17 +01:00
import { getUserNameColorClass } from "../../../utils/FormattingUtils";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import { useRoomState } from "../../../hooks/useRoomState";
import { isContentActionable } from "../../../utils/EventUtils";
import { getForwardableEvent } from "../../../events";
import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwardDialogPayload";
import { createRedactEventDialog } from "../dialogs/ConfirmRedactDialog";
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
import PinningUtils from "../../../utils/PinningUtils.ts";
2017-09-29 13:14:56 -06:00
const AVATAR_SIZE = "32px";
/**
* Properties for {@link PinnedEventTile}.
*/
interface PinnedEventTileProps {
/**
* The event to display.
*/
2021-05-26 13:51:17 +01:00
event: MatrixEvent;
/**
* The permalink creator to use.
*/
permalinkCreator: RoomPermalinkCreator;
/**
* The room the event is in.
*/
room: Room;
2021-05-25 15:44:39 +01:00
}
2020-08-29 12:14:16 +01:00
/**
* A pinned event tile.
*/
export function PinnedEventTile({ event, room, permalinkCreator }: PinnedEventTileProps): JSX.Element {
const sender = event.getSender();
if (!sender) {
throw new Error("Pinned event unexpectedly has no sender");
}
2021-05-26 13:51:17 +01:00
const isInThread = Boolean(event.threadRootId);
const displayThreadInfo = !event.isThreadRoot && isInThread;
return (
<div className="mx_PinnedEventTile" role="listitem">
<div>
2021-05-26 13:51:17 +01:00
<MemberAvatar
className="mx_PinnedEventTile_senderAvatar"
member={event.sender}
size={AVATAR_SIZE}
fallbackUserId={sender}
2021-05-26 13:51:17 +01:00
/>
</div>
<div className="mx_PinnedEventTile_wrapper">
<div className="mx_PinnedEventTile_top">
<Tooltip label={event.sender?.name || sender}>
<span className={classNames("mx_PinnedEventTile_sender", getUserNameColorClass(sender))}>
{event.sender?.name || sender}
</span>
</Tooltip>
<PinMenu event={event} room={room} permalinkCreator={permalinkCreator} />
</div>
<MessageEvent
mxEvent={event}
maxImageHeight={150}
onHeightChanged={() => {}} // we need to give this, apparently
permalinkCreator={permalinkCreator}
replacingEventId={event.replacingEventId()}
/>
{displayThreadInfo && (
<div className="mx_PinnedEventTile_thread">
<ThreadIcon />
{_t(
"right_panel|pinned_messages|reply_thread",
{},
{
link: (sub) => (
<button
type="button"
onClick={() => {
if (!event.threadRootId) return;
const rootEvent = room.findEventById(event.threadRootId);
if (!rootEvent) return;
dis.dispatch<ShowThreadPayload>({
action: Action.ShowThread,
rootEvent: rootEvent,
push: true,
});
}}
>
{sub}
</button>
),
},
)}
</div>
)}
</div>
</div>
);
}
/**
* Properties for {@link PinMenu}.
*/
interface PinMenuProps extends PinnedEventTileProps {}
/**
* A popover menu with actions on the pinned event
*/
function PinMenu({ event, room, permalinkCreator }: PinMenuProps): JSX.Element {
const [open, setOpen] = useState(false);
const matrixClient = useMatrixClientContext();
/**
* View the event in the timeline.
*/
const onViewInTimeline = useCallback(() => {
dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
event_id: event.getId(),
highlighted: true,
room_id: event.getRoomId(),
metricsTrigger: undefined, // room doesn't change
});
}, [event]);
/**
* Whether the client can unpin the event.
* If the room state change, we want to check again the permission
*/
const canUnpin = useRoomState(room, () => PinningUtils.canUnpin(matrixClient, event));
/**
* Unpin the event.
* @param event
*/
const onUnpin = useCallback(async (): Promise<void> => {
await PinningUtils.pinOrUnpinEvent(matrixClient, event);
}, [event, matrixClient]);
const contentActionable = isContentActionable(event);
// Get the forwardable event for the given event
const forwardableEvent = contentActionable && getForwardableEvent(event, matrixClient);
/**
* Open the forward dialog.
*/
const onForward = useCallback(() => {
if (forwardableEvent) {
dis.dispatch<OpenForwardDialogPayload>({
action: Action.OpenForwardDialog,
event: forwardableEvent,
permalinkCreator: permalinkCreator,
});
}
}, [forwardableEvent, permalinkCreator]);
/**
* Whether the client can redact the event.
*/
const canRedact =
room
.getLiveTimeline()
.getState(EventTimeline.FORWARDS)
?.maySendRedactionForEvent(event, matrixClient.getSafeUserId()) &&
event.getType() !== EventType.RoomServerAcl &&
event.getType() !== EventType.RoomEncryption;
/**
* Redact the event.
*/
const onRedact = useCallback(
(): void =>
createRedactEventDialog({
mxEvent: event,
}),
[event],
);
return (
<Menu
open={open}
onOpenChange={setOpen}
showTitle={false}
title={_t("right_panel|pinned_messages|menu")}
side="right"
align="start"
trigger={
<IconButton size="24px" aria-label={_t("right_panel|pinned_messages|menu")}>
<TriggerIcon />
</IconButton>
}
>
<MenuItem Icon={ViewIcon} label={_t("right_panel|pinned_messages|view")} onSelect={onViewInTimeline} />
{canUnpin && <MenuItem Icon={UnpinIcon} label={_t("action|unpin")} onSelect={onUnpin} />}
{forwardableEvent && <MenuItem Icon={ForwardIcon} label={_t("action|forward")} onSelect={onForward} />}
{canRedact && (
<>
<Separator />
<MenuItem kind="critical" Icon={DeleteIcon} label={_t("action|delete")} onSelect={onRedact} />
</>
)}
</Menu>
);
2020-08-29 12:14:16 +01:00
}