Migrate to private class properties/methods

This commit is contained in:
Jamie Kyle 2025-01-14 11:11:52 -08:00 committed by GitHub
parent 7dbe57084b
commit aa9f53df57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
100 changed files with 3795 additions and 3944 deletions

View file

@ -189,18 +189,18 @@ export class Timeline extends React.Component<
StateType,
SnapshotType
> {
private readonly containerRef = React.createRef<HTMLDivElement>();
private readonly messagesRef = React.createRef<HTMLDivElement>();
private readonly atBottomDetectorRef = React.createRef<HTMLDivElement>();
private readonly lastSeenIndicatorRef = React.createRef<HTMLDivElement>();
private intersectionObserver?: IntersectionObserver;
readonly #containerRef = React.createRef<HTMLDivElement>();
readonly #messagesRef = React.createRef<HTMLDivElement>();
readonly #atBottomDetectorRef = React.createRef<HTMLDivElement>();
readonly #lastSeenIndicatorRef = React.createRef<HTMLDivElement>();
#intersectionObserver?: IntersectionObserver;
// This is a best guess. It will likely be overridden when the timeline is measured.
private maxVisibleRows = Math.ceil(window.innerHeight / MIN_ROW_HEIGHT);
#maxVisibleRows = Math.ceil(window.innerHeight / MIN_ROW_HEIGHT);
private hasRecentlyScrolledTimeout?: NodeJS.Timeout;
private delayedPeekTimeout?: NodeJS.Timeout;
private peekInterval?: NodeJS.Timeout;
#hasRecentlyScrolledTimeout?: NodeJS.Timeout;
#delayedPeekTimeout?: NodeJS.Timeout;
#peekInterval?: NodeJS.Timeout;
// eslint-disable-next-line react/state-in-constructor
override state: StateType = {
@ -213,31 +213,28 @@ export class Timeline extends React.Component<
widthBreakpoint: WidthBreakpoint.Wide,
};
private onScrollLockChange = (): void => {
#onScrollLockChange = (): void => {
this.setState({
scrollLocked: this.scrollerLock.isLocked(),
scrollLocked: this.#scrollerLock.isLocked(),
});
};
private scrollerLock = createScrollerLock(
'Timeline',
this.onScrollLockChange
);
#scrollerLock = createScrollerLock('Timeline', this.#onScrollLockChange);
private onScroll = (event: UIEvent): void => {
#onScroll = (event: UIEvent): void => {
// When content is removed from the viewport, such as typing indicators leaving
// or messages being edited smaller or deleted, scroll events are generated and
// they are marked as user-generated (isTrusted === true). Actual user generated
// scroll events with movement must scroll a nonbottom state at some point.
const isAtBottom = this.isAtBottom();
const isAtBottom = this.#isAtBottom();
if (event.isTrusted && !isAtBottom) {
this.scrollerLock.onUserInterrupt('onScroll');
this.#scrollerLock.onUserInterrupt('onScroll');
}
// hasRecentlyScrolled is used to show the floating date header, which we only
// want to show when scrolling through history or on conversation first open.
// Checking bottom prevents new messages and typing from showing the header.
if (!this.state.hasRecentlyScrolled && this.isAtBottom()) {
if (!this.state.hasRecentlyScrolled && this.#isAtBottom()) {
return;
}
@ -248,24 +245,24 @@ export class Timeline extends React.Component<
// [0]: https://github.com/facebook/react/blob/29b7b775f2ecf878eaf605be959d959030598b07/packages/react-reconciler/src/ReactUpdateQueue.js#L401-L404
oldState.hasRecentlyScrolled ? null : { hasRecentlyScrolled: true }
);
clearTimeoutIfNecessary(this.hasRecentlyScrolledTimeout);
this.hasRecentlyScrolledTimeout = setTimeout(() => {
clearTimeoutIfNecessary(this.#hasRecentlyScrolledTimeout);
this.#hasRecentlyScrolledTimeout = setTimeout(() => {
this.setState({ hasRecentlyScrolled: false });
}, 3000);
};
private scrollToItemIndex(itemIndex: number): void {
if (this.scrollerLock.isLocked()) {
#scrollToItemIndex(itemIndex: number): void {
if (this.#scrollerLock.isLocked()) {
return;
}
this.messagesRef.current
this.#messagesRef.current
?.querySelector(`[data-item-index="${itemIndex}"]`)
?.scrollIntoViewIfNeeded();
}
private scrollToBottom = (setFocus?: boolean): void => {
if (this.scrollerLock.isLocked()) {
#scrollToBottom = (setFocus?: boolean): void => {
if (this.#scrollerLock.isLocked()) {
return;
}
@ -276,20 +273,20 @@ export class Timeline extends React.Component<
const lastMessageId = items[lastIndex];
targetMessage(lastMessageId, id);
} else {
const containerEl = this.containerRef.current;
const containerEl = this.#containerRef.current;
if (containerEl) {
scrollToBottom(containerEl);
}
}
};
private onClickScrollDownButton = (): void => {
this.scrollerLock.onUserInterrupt('onClickScrollDownButton');
this.scrollDown(false);
#onClickScrollDownButton = (): void => {
this.#scrollerLock.onUserInterrupt('onClickScrollDownButton');
this.#scrollDown(false);
};
private scrollDown = (setFocus?: boolean): void => {
if (this.scrollerLock.isLocked()) {
#scrollDown = (setFocus?: boolean): void => {
if (this.#scrollerLock.isLocked()) {
return;
}
@ -309,7 +306,7 @@ export class Timeline extends React.Component<
}
if (messageLoadingState) {
this.scrollToBottom(setFocus);
this.#scrollToBottom(setFocus);
return;
}
@ -323,10 +320,10 @@ export class Timeline extends React.Component<
const messageId = items[oldestUnseenIndex];
targetMessage(messageId, id);
} else {
this.scrollToItemIndex(oldestUnseenIndex);
this.#scrollToItemIndex(oldestUnseenIndex);
}
} else if (haveNewest) {
this.scrollToBottom(setFocus);
this.#scrollToBottom(setFocus);
} else {
const lastId = last(items);
if (lastId) {
@ -335,8 +332,8 @@ export class Timeline extends React.Component<
}
};
private isAtBottom(): boolean {
const containerEl = this.containerRef.current;
#isAtBottom(): boolean {
const containerEl = this.#containerRef.current;
if (!containerEl) {
return false;
}
@ -346,10 +343,10 @@ export class Timeline extends React.Component<
return isScrolledNearBottom || !hasScrollbars;
}
private updateIntersectionObserver(): void {
const containerEl = this.containerRef.current;
const messagesEl = this.messagesRef.current;
const atBottomDetectorEl = this.atBottomDetectorRef.current;
#updateIntersectionObserver(): void {
const containerEl = this.#containerRef.current;
const messagesEl = this.#messagesRef.current;
const atBottomDetectorEl = this.#atBottomDetectorRef.current;
if (!containerEl || !messagesEl || !atBottomDetectorEl) {
return;
}
@ -368,7 +365,7 @@ export class Timeline extends React.Component<
// We re-initialize the `IntersectionObserver`. We don't want stale references to old
// props, and we care about the order of `IntersectionObserverEntry`s. (We could do
// this another way, but this approach works.)
this.intersectionObserver?.disconnect();
this.#intersectionObserver?.disconnect();
const intersectionRatios = new Map<Element, number>();
@ -445,7 +442,7 @@ export class Timeline extends React.Component<
setIsNearBottom(id, newIsNearBottom);
if (newestBottomVisibleMessageId) {
this.markNewestBottomVisibleMessageRead();
this.#markNewestBottomVisibleMessageRead();
const rowIndex = getRowIndexFromElement(newestBottomVisible);
const maxRowIndex = items.length - 1;
@ -471,15 +468,15 @@ export class Timeline extends React.Component<
}
};
this.intersectionObserver = new IntersectionObserver(
this.#intersectionObserver = new IntersectionObserver(
(entries, observer) => {
assertDev(
this.intersectionObserver === observer,
this.#intersectionObserver === observer,
'observer.disconnect() should prevent callbacks from firing'
);
// Observer was updated from under us
if (this.intersectionObserver !== observer) {
if (this.#intersectionObserver !== observer) {
return;
}
@ -493,13 +490,13 @@ export class Timeline extends React.Component<
for (const child of messagesEl.children) {
if ((child as HTMLElement).dataset.messageId) {
this.intersectionObserver.observe(child);
this.#intersectionObserver.observe(child);
}
}
this.intersectionObserver.observe(atBottomDetectorEl);
this.#intersectionObserver.observe(atBottomDetectorEl);
}
private markNewestBottomVisibleMessageRead = throttle((): void => {
#markNewestBottomVisibleMessageRead = throttle((): void => {
const { id, markMessageRead } = this.props;
const { newestBottomVisibleMessageId } = this.state;
if (newestBottomVisibleMessageId) {
@ -507,36 +504,37 @@ export class Timeline extends React.Component<
}
}, 500);
private setupGroupCallPeekTimeouts(): void {
this.cleanupGroupCallPeekTimeouts();
#setupGroupCallPeekTimeouts(): void {
this.#cleanupGroupCallPeekTimeouts();
this.delayedPeekTimeout = setTimeout(() => {
this.#delayedPeekTimeout = setTimeout(() => {
const { id, peekGroupCallForTheFirstTime } = this.props;
this.delayedPeekTimeout = undefined;
this.#delayedPeekTimeout = undefined;
peekGroupCallForTheFirstTime(id);
}, 500);
this.peekInterval = setInterval(() => {
this.#peekInterval = setInterval(() => {
const { id, peekGroupCallIfItHasMembers } = this.props;
peekGroupCallIfItHasMembers(id);
}, MINUTE);
}
private cleanupGroupCallPeekTimeouts(): void {
const { delayedPeekTimeout, peekInterval } = this;
#cleanupGroupCallPeekTimeouts(): void {
const peekInterval = this.#peekInterval;
const delayedPeekTimeout = this.#delayedPeekTimeout;
clearTimeoutIfNecessary(delayedPeekTimeout);
this.delayedPeekTimeout = undefined;
this.#delayedPeekTimeout = undefined;
if (peekInterval) {
clearInterval(peekInterval);
this.peekInterval = undefined;
this.#peekInterval = undefined;
}
}
public override componentDidMount(): void {
const containerEl = this.containerRef.current;
const messagesEl = this.messagesRef.current;
const containerEl = this.#containerRef.current;
const messagesEl = this.#messagesRef.current;
const { conversationType, isConversationSelected } = this.props;
strictAssert(
// We don't render anything unless the conversation is selected
@ -544,31 +542,31 @@ export class Timeline extends React.Component<
'<Timeline> mounted without some refs'
);
this.updateIntersectionObserver();
this.#updateIntersectionObserver();
window.SignalContext.activeWindowService.registerForActive(
this.markNewestBottomVisibleMessageRead
this.#markNewestBottomVisibleMessageRead
);
if (conversationType === 'group') {
this.setupGroupCallPeekTimeouts();
this.#setupGroupCallPeekTimeouts();
}
}
public override componentWillUnmount(): void {
window.SignalContext.activeWindowService.unregisterForActive(
this.markNewestBottomVisibleMessageRead
this.#markNewestBottomVisibleMessageRead
);
this.intersectionObserver?.disconnect();
this.cleanupGroupCallPeekTimeouts();
this.#intersectionObserver?.disconnect();
this.#cleanupGroupCallPeekTimeouts();
this.props.updateVisibleMessages?.([]);
}
public override getSnapshotBeforeUpdate(
prevProps: Readonly<PropsType>
): SnapshotType {
const containerEl = this.containerRef.current;
const containerEl = this.#containerRef.current;
if (!containerEl) {
return null;
}
@ -579,7 +577,7 @@ export class Timeline extends React.Component<
const scrollAnchor = getScrollAnchorBeforeUpdate(
prevProps,
props,
this.isAtBottom()
this.#isAtBottom()
);
switch (scrollAnchor) {
@ -627,10 +625,10 @@ export class Timeline extends React.Component<
messageLoadingState,
} = this.props;
const containerEl = this.containerRef.current;
if (!this.scrollerLock.isLocked() && containerEl && snapshot) {
const containerEl = this.#containerRef.current;
if (!this.#scrollerLock.isLocked() && containerEl && snapshot) {
if (snapshot === scrollToUnreadIndicator) {
const lastSeenIndicatorEl = this.lastSeenIndicatorRef.current;
const lastSeenIndicatorEl = this.#lastSeenIndicatorRef.current;
if (lastSeenIndicatorEl) {
lastSeenIndicatorEl.scrollIntoView();
} else {
@ -641,7 +639,7 @@ export class Timeline extends React.Component<
);
}
} else if ('scrollToIndex' in snapshot) {
this.scrollToItemIndex(snapshot.scrollToIndex);
this.#scrollToItemIndex(snapshot.scrollToIndex);
} else if ('scrollTop' in snapshot) {
containerEl.scrollTop = snapshot.scrollTop;
} else {
@ -657,12 +655,12 @@ export class Timeline extends React.Component<
oldItems.at(-1) !== newItems.at(-1);
if (haveItemsChanged) {
this.updateIntersectionObserver();
this.#updateIntersectionObserver();
// This condition is somewhat arbitrary.
const numberToKeepAtBottom = this.maxVisibleRows * 2;
const numberToKeepAtBottom = this.#maxVisibleRows * 2;
const shouldDiscardOlderMessages: boolean =
this.isAtBottom() && newItems.length > numberToKeepAtBottom;
this.#isAtBottom() && newItems.length > numberToKeepAtBottom;
if (shouldDiscardOlderMessages) {
discardMessages({
conversationId: id,
@ -676,9 +674,9 @@ export class Timeline extends React.Component<
!messageLoadingState && previousMessageLoadingState
? previousMessageLoadingState
: undefined;
const numberToKeepAtTop = this.maxVisibleRows * 5;
const numberToKeepAtTop = this.#maxVisibleRows * 5;
const shouldDiscardNewerMessages: boolean =
!this.isAtBottom() &&
!this.#isAtBottom() &&
loadingStateThatJustFinished ===
TimelineMessageLoadingState.LoadingOlderMessages &&
newItems.length > numberToKeepAtTop;
@ -691,18 +689,18 @@ export class Timeline extends React.Component<
}
}
if (previousMessageChangeCounter !== messageChangeCounter) {
this.markNewestBottomVisibleMessageRead();
this.#markNewestBottomVisibleMessageRead();
}
if (previousConversationType !== conversationType) {
this.cleanupGroupCallPeekTimeouts();
this.#cleanupGroupCallPeekTimeouts();
if (conversationType === 'group') {
this.setupGroupCallPeekTimeouts();
this.#setupGroupCallPeekTimeouts();
}
}
}
private handleBlur = (event: React.FocusEvent): void => {
#handleBlur = (event: React.FocusEvent): void => {
const { clearTargetedMessage } = this.props;
const { currentTarget } = event;
@ -726,9 +724,7 @@ export class Timeline extends React.Component<
}, 0);
};
private handleKeyDown = (
event: React.KeyboardEvent<HTMLDivElement>
): void => {
#handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
const { targetMessage, targetedMessageId, items, id } = this.props;
const commandKey = get(window, 'platform') === 'darwin' && event.metaKey;
const controlKey = get(window, 'platform') !== 'darwin' && event.ctrlKey;
@ -803,7 +799,7 @@ export class Timeline extends React.Component<
}
if (event.key === 'End' || (commandOrCtrl && event.key === 'ArrowDown')) {
this.scrollDown(true);
this.#scrollDown(true);
event.preventDefault();
event.stopPropagation();
}
@ -939,7 +935,7 @@ export class Timeline extends React.Component<
key="last seen indicator"
count={totalUnseen}
i18n={i18n}
ref={this.lastSeenIndicatorRef}
ref={this.#lastSeenIndicatorRef}
/>
);
} else if (oldestUnseenIndex === nextItemIndex) {
@ -964,7 +960,7 @@ export class Timeline extends React.Component<
>
<ErrorBoundary i18n={i18n} showDebugLog={showDebugLog}>
{renderItem({
containerElementRef: this.containerRef,
containerElementRef: this.#containerRef,
containerWidthBreakpoint: widthBreakpoint,
conversationId: id,
isBlocked,
@ -1098,7 +1094,7 @@ export class Timeline extends React.Component<
}
return (
<ScrollerLockContext.Provider value={this.scrollerLock}>
<ScrollerLockContext.Provider value={this.#scrollerLock}>
<SizeObserver
onSizeChange={size => {
const { isNearBottom } = this.props;
@ -1107,9 +1103,9 @@ export class Timeline extends React.Component<
widthBreakpoint: getWidthBreakpoint(size.width),
});
this.maxVisibleRows = Math.ceil(size.height / MIN_ROW_HEIGHT);
this.#maxVisibleRows = Math.ceil(size.height / MIN_ROW_HEIGHT);
const containerEl = this.containerRef.current;
const containerEl = this.#containerRef.current;
if (containerEl && isNearBottom) {
scrollToBottom(containerEl);
}
@ -1124,8 +1120,8 @@ export class Timeline extends React.Component<
)}
role="presentation"
tabIndex={-1}
onBlur={this.handleBlur}
onKeyDown={this.handleKeyDown}
onBlur={this.#handleBlur}
onKeyDown={this.#handleKeyDown}
ref={ref}
>
{headerElements}
@ -1134,8 +1130,8 @@ export class Timeline extends React.Component<
<main
className="module-timeline__messages__container"
onScroll={this.onScroll}
ref={this.containerRef}
onScroll={this.#onScroll}
ref={this.#containerRef}
>
<div
className={classNames(
@ -1144,7 +1140,7 @@ export class Timeline extends React.Component<
haveOldest && 'module-timeline__messages--have-oldest',
scrollLocked && 'module-timeline__messages--scroll-locked'
)}
ref={this.messagesRef}
ref={this.#messagesRef}
role="list"
>
{haveOldest && (
@ -1162,7 +1158,7 @@ export class Timeline extends React.Component<
<div
className="module-timeline__messages__at-bottom-detector"
ref={this.atBottomDetectorRef}
ref={this.#atBottomDetectorRef}
style={AT_BOTTOM_DETECTOR_STYLE}
/>
</div>
@ -1181,7 +1177,7 @@ export class Timeline extends React.Component<
<ScrollDownButton
variant={ScrollDownButtonVariant.UNREAD_MESSAGES}
count={areUnreadBelowCurrentPosition ? unreadCount : 0}
onClick={this.onClickScrollDownButton}
onClick={this.#onClickScrollDownButton}
i18n={i18n}
/>
</div>