Skip to content

Timeline: Replace ScrollPanel with Virtuoso POC #29958

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
"react-string-replace": "^1.1.1",
"react-transition-group": "^4.4.1",
"react-virtualized": "^9.22.5",
"react-virtuoso": "^4.12.7",
"rfc4648": "^1.4.0",
"sanitize-filename": "^1.6.3",
"sanitize-html": "2.16.0",
Expand Down
403 changes: 280 additions & 123 deletions src/components/structures/MessagePanel.tsx

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/components/structures/TimelinePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
}

private get messagePanelDiv(): HTMLDivElement | null {
return this.messagePanel.current?.scrollPanel.current?.divScroll ?? null;
return null;
// return this.messagePanel.current?.scrollPanel.current?.divScroll ?? null;
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/components/views/messages/IBodyProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ export interface IBodyProps {
// Set to `true` to disable interactions (e.g. video controls) and to remove controls from the tab order.
// This may be useful when displaying a preview of the event.
inhibitInteraction?: boolean;
isScrolling?: boolean;
}
16 changes: 10 additions & 6 deletions src/components/views/messages/MessageEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
getRelationsForEvent: this.props.getRelationsForEvent,
isSeeingThroughMessageHiddenForModeration: this.props.isSeeingThroughMessageHiddenForModeration,
inhibitInteraction: this.props.inhibitInteraction,
isScrolling: this.props.isScrolling,
};
if (hasCaption) {
return <CaptionBody {...bodyProps} WrappedBodyType={BodyType} />;
Expand All @@ -320,9 +321,12 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
const CaptionBody: React.FunctionComponent<IBodyProps & { WrappedBodyType: React.ComponentType<IBodyProps> }> = ({
WrappedBodyType,
...props
}) => (
<div className="mx_EventTile_content">
<WrappedBodyType {...props} />
<TextualBody {...{ ...props, ref: undefined }} />
</div>
);
}) => {
console.log(`CaptionBody isScrolling${props.isScrolling}`);
return (
<div className="mx_EventTile_content">
<WrappedBodyType {...props} />
<TextualBody {...{ ...props, ref: undefined }} />
</div>
);
};
5 changes: 4 additions & 1 deletion src/components/views/messages/TextualBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
nextProps.editState !== this.props.editState ||
nextState.links !== this.state.links ||
nextState.widgetHidden !== this.state.widgetHidden ||
nextProps.isSeeingThroughMessageHiddenForModeration !== this.props.isSeeingThroughMessageHiddenForModeration
nextProps.isSeeingThroughMessageHiddenForModeration !==
this.props.isSeeingThroughMessageHiddenForModeration ||
nextProps.isScrolling !== this.props.isScrolling
);
}

Expand Down Expand Up @@ -378,6 +380,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
links={this.state.links}
mxEvent={this.props.mxEvent}
onCancelClick={this.onCancelClick}
isScrolling={this.props.isScrolling}
/>
);
}
Expand Down
51 changes: 29 additions & 22 deletions src/components/views/rooms/EventTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ export interface EventTileProps {
inhibitInteraction?: boolean;

ref?: Ref<UnwrappedEventTile>;

isScrolling?: boolean;
}

interface IState {
Expand Down Expand Up @@ -1049,7 +1051,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
needsSenderProfile = true;
}

if (this.props.mxEvent.sender && avatarSize !== null) {
if (this.props.mxEvent.sender && avatarSize !== null && !this.props.isScrolling) {
let member: RoomMember | null = null;
// set member to receiver (target) if it is a 3PID invite
// so that the correct avatar is shown as the text is
Expand Down Expand Up @@ -1077,7 +1079,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
);
}

if (needsSenderProfile && this.props.hideSender !== true) {
if (needsSenderProfile && this.props.hideSender !== true && !this.props.isScrolling) {
if (
this.context.timelineRenderingType === TimelineRenderingType.Room ||
this.context.timelineRenderingType === TimelineRenderingType.Search ||
Expand All @@ -1093,19 +1095,20 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
}

const showMessageActionBar = !isEditing && !this.props.forExport;
const actionBar = showMessageActionBar ? (
<MessageActionBar
mxEvent={this.props.mxEvent}
reactions={this.state.reactions}
permalinkCreator={this.props.permalinkCreator}
getTile={this.getTile}
getReplyChain={this.getReplyChain}
onFocusChange={this.onActionBarFocusChange}
isQuoteExpanded={isQuoteExpanded}
toggleThreadExpanded={() => this.setQuoteExpanded(!isQuoteExpanded)}
getRelationsForEvent={this.props.getRelationsForEvent}
/>
) : undefined;
const actionBar =
showMessageActionBar && !this.props.isScrolling ? (
<MessageActionBar
mxEvent={this.props.mxEvent}
reactions={this.state.reactions}
permalinkCreator={this.props.permalinkCreator}
getTile={this.getTile}
getReplyChain={this.getReplyChain}
onFocusChange={this.onActionBarFocusChange}
isQuoteExpanded={isQuoteExpanded}
toggleThreadExpanded={() => this.setQuoteExpanded(!isQuoteExpanded)}
getRelationsForEvent={this.props.getRelationsForEvent}
/>
) : undefined;

const showTimestamp =
this.props.mxEvent.getTs() &&
Expand Down Expand Up @@ -1168,14 +1171,16 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
) : null;

const useIRCLayout = this.props.layout === Layout.IRC;
const groupTimestamp = !useIRCLayout ? linkedTimestamp : null;
const ircTimestamp = useIRCLayout ? linkedTimestamp : null;
const groupTimestamp = !useIRCLayout && !this.props.isScrolling ? linkedTimestamp : null;
const ircTimestamp = useIRCLayout && !this.props.isScrolling ? linkedTimestamp : null;
const bubbleTimestamp = this.props.layout === Layout.Bubble ? messageTimestamp : undefined;
const groupPadlock = !useIRCLayout && !isBubbleMessage && this.renderE2EPadlock();
const ircPadlock = useIRCLayout && !isBubbleMessage && this.renderE2EPadlock();
const groupPadlock = !useIRCLayout && !isBubbleMessage && !this.props.isScrolling && this.renderE2EPadlock();
const ircPadlock = useIRCLayout && !isBubbleMessage && !this.props.isScrolling && this.renderE2EPadlock();

let msgOption: JSX.Element | undefined;
if (this.shouldShowSentReceipt || this.shouldShowSendingReceipt) {
if (this.props.isScrolling) {
msgOption = undefined;
} else if (this.shouldShowSentReceipt || this.shouldShowSendingReceipt) {
msgOption = <SentReceipt messageState={this.props.mxEvent.getAssociatedStatus()} />;
} else if (this.props.showReadReceipts) {
msgOption = (
Expand All @@ -1192,7 +1197,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
let replyChain: JSX.Element | undefined;
if (
haveRendererForEvent(this.props.mxEvent, MatrixClientPeg.safeGet(), this.context.showHiddenEvents) &&
shouldDisplayReply(this.props.mxEvent)
shouldDisplayReply(this.props.mxEvent) &&
!this.props.isScrolling
) {
replyChain = (
<ReplyChain
Expand Down Expand Up @@ -1405,6 +1411,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
}

default: {
const contextMenu = this.props.isScrolling ? null : this.renderContextMenu();
// Pinned, Room, Search
// tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers
return React.createElement(
Expand All @@ -1429,7 +1436,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
{ircPadlock}
{avatar}
<div className={lineClasses} key="mx_EventTile_line" onContextMenu={this.onContextMenu}>
{this.renderContextMenu()}
{contextMenu}
{groupTimestamp}
{groupPadlock}
{replyChain}
Expand Down
49 changes: 26 additions & 23 deletions src/components/views/rooms/LinkPreviewGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ interface IProps {
links: string[]; // the URLs to be previewed
mxEvent: MatrixEvent; // the Event associated with the preview
onCancelClick(): void; // called when the preview's cancel ('hide') button is clicked
isScrolling?: boolean;
}

const LinkPreviewGroup: React.FC<IProps> = ({ links, mxEvent, onCancelClick }) => {
const LinkPreviewGroup: React.FC<IProps> = ({ links, mxEvent, onCancelClick, isScrolling }) => {
const cli = useContext(MatrixClientContext);
const [expanded, toggleExpanded] = useStateToggle();
const [mediaVisible] = useMediaVisible(mxEvent.getId(), mxEvent.getRoomId());
Expand Down Expand Up @@ -55,28 +56,30 @@ const LinkPreviewGroup: React.FC<IProps> = ({ links, mxEvent, onCancelClick }) =
}

return (
<div className="mx_LinkPreviewGroup">
{showPreviews.map(([link, preview], i) => (
<LinkPreviewWidget
mediaVisible={mediaVisible}
key={link}
link={link}
preview={preview}
mxEvent={mxEvent}
>
{i === 0 ? (
<AccessibleButton
className="mx_LinkPreviewGroup_hide"
onClick={onCancelClick}
aria-label={_t("timeline|url_preview|close")}
>
<CloseIcon width="20px" height="20px" />
</AccessibleButton>
) : undefined}
</LinkPreviewWidget>
))}
{toggleButton}
</div>
!isScrolling && (
<div className="mx_LinkPreviewGroup">
{showPreviews.map(([link, preview], i) => (
<LinkPreviewWidget
mediaVisible={mediaVisible}
key={link}
link={link}
preview={preview}
mxEvent={mxEvent}
>
{i === 0 ? (
<AccessibleButton
className="mx_LinkPreviewGroup_hide"
onClick={onCancelClick}
aria-label={_t("timeline|url_preview|close")}
>
<CloseIcon width="20px" height="20px" />
</AccessibleButton>
) : undefined}
</LinkPreviewWidget>
))}
{toggleButton}
</div>
)
);
};

Expand Down
3 changes: 3 additions & 0 deletions src/events/EventTileFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export interface EventTileTypeProps
| "callEventGrouper"
| "isSeeingThroughMessageHiddenForModeration"
| "inhibitInteraction"
| "isScrolling"
> {
ref?: React.RefObject<any>; // `any` because it's effectively impossible to convince TS of a reasonable type
timestamp?: JSX.Element;
Expand Down Expand Up @@ -278,6 +279,7 @@ export function renderTile(
isSeeingThroughMessageHiddenForModeration,
timestamp,
inhibitInteraction,
isScrolling,
} = props;

switch (renderType) {
Expand Down Expand Up @@ -313,6 +315,7 @@ export function renderTile(
isSeeingThroughMessageHiddenForModeration,
timestamp,
inhibitInteraction,
isScrolling,
});
}
}
Expand Down
10 changes: 8 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3720,15 +3720,16 @@
classnames "^2.5.1"
vaul "^1.0.0"

"@vector-im/matrix-wysiwyg-wasm@link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.3-cc54d8b3e9472bcd8e622126ba364ee31952cd8a-integrity/node_modules/bindings/wysiwyg-wasm":
"@vector-im/matrix-wysiwyg-wasm@link:../../Library/Caches/Yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.3-cc54d8b3e9472bcd8e622126ba364ee31952cd8a-integrity/node_modules/bindings/wysiwyg-wasm":
version "0.0.0"
uid ""

"@vector-im/[email protected]":
version "2.38.3"
resolved "https://registry.yarnpkg.com/@vector-im/matrix-wysiwyg/-/matrix-wysiwyg-2.38.3.tgz#cc54d8b3e9472bcd8e622126ba364ee31952cd8a"
integrity sha512-fqo8P55Vc/t0vxpFar9RDJN5gKEjJmzrLo+O4piDbFda6VrRoqrWAtiu0Au0g6B4hRDPKIuFupk8v9Ja7q8Hvg==
dependencies:
"@vector-im/matrix-wysiwyg-wasm" "link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.3-cc54d8b3e9472bcd8e622126ba364ee31952cd8a-integrity/node_modules/bindings/wysiwyg-wasm"
"@vector-im/matrix-wysiwyg-wasm" "link:../../Library/Caches/Yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.3-cc54d8b3e9472bcd8e622126ba364ee31952cd8a-integrity/node_modules/bindings/wysiwyg-wasm"

"@webassemblyjs/[email protected]", "@webassemblyjs/ast@^1.14.1":
version "1.14.1"
Expand Down Expand Up @@ -11060,6 +11061,11 @@ react-virtualized@^9.22.5:
prop-types "^15.7.2"
react-lifecycles-compat "^3.0.4"

react-virtuoso@^4.12.7:
version "4.12.7"
resolved "https://registry.yarnpkg.com/react-virtuoso/-/react-virtuoso-4.12.7.tgz#300f2585c61d213d4d422420f0d43ffc9674e6f5"
integrity sha512-njJp764he6Fi1p89PUW0k2kbyWu9w/y+MwdxmwK2kvdwwzVDbz2c2wMj5xdSruBFVgFTsI7Z85hxZR7aSHBrbQ==

react@^19.0.0:
version "19.1.0"
resolved "https://registry.yarnpkg.com/react/-/react-19.1.0.tgz#926864b6c48da7627f004795d6cce50e90793b75"
Expand Down
Loading