Refactor Reactions Row Button to shared-components (#31993)
* Refactoring of ReactionRowButton to shared component MVVM * Removal of old component and creation of unit tests * Update * Update tests * Update tests to mimic VM * Update Lint Spacing * Added onKeyDown to follow wcag rules * Remove Unused code * Update screenshots * Removal of unessecery test and story * Update snapshot * Refactor reactions row VMs to granular setters and merge cheap snapshot updates * Elist Fix * Revert ReactionRowButtonToolTip Test * Fix ReactionsRowButtonViewModel tooltip sync to use tooltip setProps * Add dedicated ReactionsRowButtonViewModel unit tests for setters, tooltip sync, and click actions * Better Wording On Functions * Update snapshot * Update packages/shared-components/src/message-body/ReactionsRowButton/ReactionsRowButtonView.tsx Co-authored-by: Florian Duros <florian.duros@ormaz.fr> * use native button and tighten view model * Update Snapshots + small fixes on reactionrow * Removal of Null on viewmodel and adapting ReactionRow * Update test and removal of unused test since me MVVMD ReactionRowButton * align assertions with refactored update behavior * FIx issue with classNames component * Update snapshot * Removal of old test snapshot * Update Snapshot * Implement Css + Snapshot Updates * Update Snapshot and css to match old component style * restore MatrixClientContext fallback in ReactionsRow for export/test rendering * restore client fallback in ReactionsRow to preserve export rendering * Remove Unused Pcss FIle * Update Css * Update misstake always having button default to disabled render * Remove unsimiler css to original component * Update Snapshot to reflect css adjustments * Update css * Update font to compund * Update css to reflect old component * Update css to compund * Update Snapshot and css * Update css * Update HTML snapshot * Update css * Update Css * Update snapshots * Update HTML snapshot * Update css + snapshot * Update HTML snapshot * Removal of mx css * Update snapshot based on css removal * Update Html snapshot * Apply suggestion from @florianduros Co-authored-by: Florian Duros <florian.duros@ormaz.fr> * remove setContext from ReactionsRowButtonViewModel * Update packages/shared-components/src/message-body/ReactionsRowButton/ReactionsRowButtonView.tsx Co-authored-by: Florian Duros <florian.duros@ormaz.fr> * add tooltipVm to ReactionsRowButtonViewSnapshot * added compound token variables * remove className from content and count inner elements * use useMatrixClientContext() directly for ReactionsRowButtonViewModel * Update snapshots * Update snapshot + fix Typescript error on test file * Removal of line-height in css * Added line-height back and removed font: inherit; * derive ReactionsRowButton className/ariaLabel types from HTML button attrs * Update packages/shared-components/src/message-body/ReactionsRowButton/ReactionsRowButtonView.tsx Co-authored-by: Florian Duros <florian.duros@ormaz.fr> * Update src/viewmodels/message-body/ReactionsRowButtonViewModel.ts Co-authored-by: Florian Duros <florian.duros@ormaz.fr> * Update src/viewmodels/message-body/ReactionsRowButtonViewModel.ts Co-authored-by: Florian Duros <florian.duros@ormaz.fr> * Update test/viewmodels/message-body/ReactionsRowButtonViewModel-test.tsx Co-authored-by: Florian Duros <florian.duros@ormaz.fr> * Update snapshots and lint issues * Update model to respond to changes * Update aria label on view --------- Co-authored-by: Florian Duros <florian.duros@ormaz.fr>
This commit is contained in:
@@ -6,22 +6,24 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX, type SyntheticEvent } from "react";
|
||||
import React, { useEffect, type JSX, type SyntheticEvent } from "react";
|
||||
import classNames from "classnames";
|
||||
import { type MatrixEvent, MatrixEventEvent, type Relations, RelationsEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { uniqBy } from "lodash";
|
||||
import { UnstableValue } from "matrix-js-sdk/src/NamespacedValue";
|
||||
import { ReactionAddIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import { ReactionsRowButtonView, useCreateAutoDisposedViewModel } from "@element-hq/web-shared-components";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { isContentActionable } from "../../../utils/EventUtils";
|
||||
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
|
||||
import ContextMenu, { aboveLeftOf, useContextMenu } from "../../structures/ContextMenu";
|
||||
import ReactionPicker from "../emojipicker/ReactionPicker";
|
||||
import ReactionsRowButton from "./ReactionsRowButton";
|
||||
import RoomContext from "../../../contexts/RoomContext";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { ReactionsRowButtonViewModel } from "../../../viewmodels/message-body/ReactionsRowButtonViewModel";
|
||||
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
|
||||
|
||||
// The maximum number of reactions to initially show on a message.
|
||||
const MAX_ITEMS_WHEN_LIMITED = 8;
|
||||
@@ -64,6 +66,52 @@ const ReactButton: React.FC<IProps> = ({ mxEvent, reactions }) => {
|
||||
);
|
||||
};
|
||||
|
||||
interface ReactionsRowButtonItemProps {
|
||||
mxEvent: MatrixEvent;
|
||||
content: string;
|
||||
count: number;
|
||||
reactionEvents: MatrixEvent[];
|
||||
myReactionEvent?: MatrixEvent;
|
||||
disabled?: boolean;
|
||||
customReactionImagesEnabled?: boolean;
|
||||
}
|
||||
|
||||
const ReactionsRowButtonItem: React.FC<ReactionsRowButtonItemProps> = (props) => {
|
||||
const client = useMatrixClientContext();
|
||||
|
||||
const vm = useCreateAutoDisposedViewModel(
|
||||
() =>
|
||||
new ReactionsRowButtonViewModel({
|
||||
client,
|
||||
mxEvent: props.mxEvent,
|
||||
content: props.content,
|
||||
count: props.count,
|
||||
reactionEvents: props.reactionEvents,
|
||||
myReactionEvent: props.myReactionEvent,
|
||||
disabled: props.disabled,
|
||||
customReactionImagesEnabled: props.customReactionImagesEnabled,
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
vm.setReactionData(props.content, props.reactionEvents, props.customReactionImagesEnabled);
|
||||
}, [props.content, props.reactionEvents, props.customReactionImagesEnabled, vm]);
|
||||
|
||||
useEffect(() => {
|
||||
vm.setCount(props.count);
|
||||
}, [props.count, vm]);
|
||||
|
||||
useEffect(() => {
|
||||
vm.setMyReactionEvent(props.myReactionEvent);
|
||||
}, [props.myReactionEvent, vm]);
|
||||
|
||||
useEffect(() => {
|
||||
vm.setDisabled(props.disabled);
|
||||
}, [props.disabled, vm]);
|
||||
|
||||
return <ReactionsRowButtonView vm={vm} />;
|
||||
};
|
||||
|
||||
interface IProps {
|
||||
// The event we're displaying reactions for
|
||||
mxEvent: MatrixEvent;
|
||||
@@ -186,7 +234,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
||||
return mxEvent.getRelation()?.key === content;
|
||||
});
|
||||
return (
|
||||
<ReactionsRowButton
|
||||
<ReactionsRowButtonItem
|
||||
key={content}
|
||||
content={content}
|
||||
count={deduplicatedEvents.length}
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2019-2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
import { EventType, type MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
import { ReactionsRowButtonTooltipView } from "@element-hq/web-shared-components";
|
||||
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { formatList } from "../../../utils/FormattingUtils";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import { ReactionsRowButtonTooltipViewModel } from "../../../viewmodels/message-body/ReactionsRowButtonTooltipViewModel";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { REACTION_SHORTCODE_KEY } from "./ReactionsRow";
|
||||
|
||||
export interface IProps {
|
||||
// The event we're displaying reactions for
|
||||
mxEvent: MatrixEvent;
|
||||
// The reaction content / key / emoji
|
||||
content: string;
|
||||
// The count of votes for this key
|
||||
count: number;
|
||||
// A list of Matrix reaction events for this key
|
||||
reactionEvents: MatrixEvent[];
|
||||
// A possible Matrix event if the current user has voted for this type
|
||||
myReactionEvent?: MatrixEvent;
|
||||
// Whether to prevent quick-reactions by clicking on this reaction
|
||||
disabled?: boolean;
|
||||
// Whether to render custom image reactions
|
||||
customReactionImagesEnabled?: boolean;
|
||||
}
|
||||
|
||||
export default class ReactionsRowButton extends React.PureComponent<IProps> {
|
||||
public static contextType = MatrixClientContext;
|
||||
declare public context: React.ContextType<typeof MatrixClientContext>;
|
||||
|
||||
private reactionsRowButtonTooltipViewModel: ReactionsRowButtonTooltipViewModel;
|
||||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
||||
super(props, context);
|
||||
this.reactionsRowButtonTooltipViewModel = new ReactionsRowButtonTooltipViewModel({
|
||||
client: context,
|
||||
mxEvent: props.mxEvent,
|
||||
content: props.content,
|
||||
reactionEvents: props.reactionEvents,
|
||||
customReactionImagesEnabled: props.customReactionImagesEnabled,
|
||||
});
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: IProps): void {
|
||||
if (
|
||||
prevProps.mxEvent !== this.props.mxEvent ||
|
||||
prevProps.content !== this.props.content ||
|
||||
prevProps.reactionEvents !== this.props.reactionEvents ||
|
||||
prevProps.customReactionImagesEnabled !== this.props.customReactionImagesEnabled
|
||||
) {
|
||||
// View model bails out if derived snapshot hasn't changed.
|
||||
this.reactionsRowButtonTooltipViewModel.setProps({
|
||||
client: this.context,
|
||||
mxEvent: this.props.mxEvent,
|
||||
content: this.props.content,
|
||||
reactionEvents: this.props.reactionEvents,
|
||||
customReactionImagesEnabled: this.props.customReactionImagesEnabled,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
this.reactionsRowButtonTooltipViewModel.dispose();
|
||||
}
|
||||
|
||||
public onClick = (): void => {
|
||||
const { mxEvent, myReactionEvent, content } = this.props;
|
||||
if (myReactionEvent) {
|
||||
this.context.redactEvent(mxEvent.getRoomId()!, myReactionEvent.getId()!);
|
||||
} else {
|
||||
this.context.sendEvent(mxEvent.getRoomId()!, EventType.Reaction, {
|
||||
"m.relates_to": {
|
||||
rel_type: RelationType.Annotation,
|
||||
event_id: mxEvent.getId()!,
|
||||
key: content,
|
||||
},
|
||||
});
|
||||
dis.dispatch({ action: "message_sent" });
|
||||
}
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const { mxEvent, content, count, reactionEvents, myReactionEvent } = this.props;
|
||||
|
||||
const classes = classNames({
|
||||
mx_ReactionsRowButton: true,
|
||||
mx_ReactionsRowButton_selected: !!myReactionEvent,
|
||||
});
|
||||
|
||||
const room = this.context.getRoom(mxEvent.getRoomId());
|
||||
let label: string | undefined;
|
||||
let customReactionName: string | undefined;
|
||||
if (room) {
|
||||
const senders: string[] = [];
|
||||
for (const reactionEvent of reactionEvents) {
|
||||
const member = room.getMember(reactionEvent.getSender()!);
|
||||
senders.push(member?.name || reactionEvent.getSender()!);
|
||||
customReactionName =
|
||||
(this.props.customReactionImagesEnabled &&
|
||||
REACTION_SHORTCODE_KEY.findIn(reactionEvent.getContent())) ||
|
||||
undefined;
|
||||
}
|
||||
|
||||
const reactors = formatList(senders, 6);
|
||||
if (content) {
|
||||
label = _t("timeline|reactions|label", {
|
||||
reactors,
|
||||
content: customReactionName || content,
|
||||
});
|
||||
} else {
|
||||
label = reactors;
|
||||
}
|
||||
}
|
||||
|
||||
let reactionContent = (
|
||||
<span className="mx_ReactionsRowButton_content" aria-hidden="true">
|
||||
{content}
|
||||
</span>
|
||||
);
|
||||
if (this.props.customReactionImagesEnabled && content.startsWith("mxc://")) {
|
||||
const imageSrc = mediaFromMxc(content).srcHttp;
|
||||
if (imageSrc) {
|
||||
reactionContent = (
|
||||
<img
|
||||
className="mx_ReactionsRowButton_content"
|
||||
alt={customReactionName || _t("timeline|reactions|custom_reaction_fallback_label")}
|
||||
src={imageSrc}
|
||||
width="16"
|
||||
height="16"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ReactionsRowButtonTooltipView vm={this.reactionsRowButtonTooltipViewModel}>
|
||||
<AccessibleButton
|
||||
className={classes}
|
||||
aria-label={label}
|
||||
onClick={this.onClick}
|
||||
disabled={this.props.disabled}
|
||||
>
|
||||
{reactionContent}
|
||||
<span className="mx_ReactionsRowButton_count" aria-hidden="true">
|
||||
{count}
|
||||
</span>
|
||||
</AccessibleButton>
|
||||
</ReactionsRowButtonTooltipView>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user