Migrate to private class properties/methods

This commit is contained in:
Jamie Kyle 2025-01-14 11:11:52 -08:00 committed by GitHub
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

@ -47,8 +47,8 @@ export class ErrorBoundary extends React.PureComponent<Props, State> {
return (
<div
className={CSS_MODULE}
onClick={this.onClick.bind(this)}
onKeyDown={this.onKeyDown.bind(this)}
onClick={this.#onClick.bind(this)}
onKeyDown={this.#onKeyDown.bind(this)}
role="button"
tabIndex={0}
>
@ -62,24 +62,24 @@ export class ErrorBoundary extends React.PureComponent<Props, State> {
);
}
private onClick(event: React.MouseEvent): void {
#onClick(event: React.MouseEvent): void {
event.stopPropagation();
event.preventDefault();
this.onAction();
this.#onAction();
}
private onKeyDown(event: React.KeyboardEvent): void {
#onKeyDown(event: React.KeyboardEvent): void {
if (event.key !== 'Enter' && event.key !== ' ') {
return;
}
event.stopPropagation();
event.preventDefault();
this.onAction();
this.#onAction();
}
private onAction(): void {
#onAction(): void {
const { showDebugLog } = this.props;
showDebugLog();
}

View file

@ -410,11 +410,11 @@ export class Message extends React.PureComponent<Props, State> {
public reactionsContainerRef: React.RefObject<HTMLDivElement> =
React.createRef();
private hasSelectedTextRef: React.MutableRefObject<boolean> = {
#hasSelectedTextRef: React.MutableRefObject<boolean> = {
current: false,
};
private metadataRef: React.RefObject<HTMLDivElement> = React.createRef();
#metadataRef: React.RefObject<HTMLDivElement> = React.createRef();
public reactionsContainerRefMerger = createRefMerger();
@ -432,7 +432,7 @@ export class Message extends React.PureComponent<Props, State> {
super(props);
this.state = {
metadataWidth: this.guessMetadataWidth(),
metadataWidth: this.#guessMetadataWidth(),
expiring: false,
expired: false,
@ -447,7 +447,7 @@ export class Message extends React.PureComponent<Props, State> {
showOutgoingGiftBadgeModal: false,
hasDeleteForEveryoneTimerExpired:
this.getTimeRemainingForDeleteForEveryone() <= 0,
this.#getTimeRemainingForDeleteForEveryone() <= 0,
};
}
@ -474,7 +474,7 @@ export class Message extends React.PureComponent<Props, State> {
return state;
}
private hasReactions(): boolean {
#hasReactions(): boolean {
const { reactions } = this.props;
return Boolean(reactions && reactions.length);
}
@ -518,7 +518,7 @@ export class Message extends React.PureComponent<Props, State> {
window.ConversationController?.onConvoMessageMount(conversationId);
this.startTargetedTimer();
this.startDeleteForEveryoneTimerIfApplicable();
this.#startDeleteForEveryoneTimerIfApplicable();
this.startGiftBadgeInterval();
const { isTargeted } = this.props;
@ -543,7 +543,7 @@ export class Message extends React.PureComponent<Props, State> {
checkForAccount(contact.firstNumber);
}
document.addEventListener('selectionchange', this.handleSelectionChange);
document.addEventListener('selectionchange', this.#handleSelectionChange);
}
public override componentWillUnmount(): void {
@ -553,14 +553,17 @@ export class Message extends React.PureComponent<Props, State> {
clearTimeoutIfNecessary(this.deleteForEveryoneTimeout);
clearTimeoutIfNecessary(this.giftBadgeInterval);
this.toggleReactionViewer(true);
document.removeEventListener('selectionchange', this.handleSelectionChange);
document.removeEventListener(
'selectionchange',
this.#handleSelectionChange
);
}
public override componentDidUpdate(prevProps: Readonly<Props>): void {
const { isTargeted, status, timestamp } = this.props;
this.startTargetedTimer();
this.startDeleteForEveryoneTimerIfApplicable();
this.#startDeleteForEveryoneTimerIfApplicable();
if (!prevProps.isTargeted && isTargeted) {
this.setFocus();
@ -586,7 +589,7 @@ export class Message extends React.PureComponent<Props, State> {
}
}
private getMetadataPlacement(
#getMetadataPlacement(
{
attachments,
attachmentDroppedDueToSize,
@ -634,11 +637,11 @@ export class Message extends React.PureComponent<Props, State> {
return MetadataPlacement.InlineWithText;
}
if (this.canRenderStickerLikeEmoji()) {
if (this.#canRenderStickerLikeEmoji()) {
return MetadataPlacement.Bottom;
}
if (this.shouldShowJoinButton()) {
if (this.#shouldShowJoinButton()) {
return MetadataPlacement.Bottom;
}
@ -653,7 +656,7 @@ export class Message extends React.PureComponent<Props, State> {
* This will probably guess wrong, but it's valuable to get close to the real value
* because it can reduce layout jumpiness.
*/
private guessMetadataWidth(): number {
#guessMetadataWidth(): number {
const { direction, expirationLength, isSMS, status, isEditedMessage } =
this.props;
@ -714,12 +717,12 @@ export class Message extends React.PureComponent<Props, State> {
}));
}
private getTimeRemainingForDeleteForEveryone(): number {
#getTimeRemainingForDeleteForEveryone(): number {
const { timestamp } = this.props;
return Math.max(timestamp - Date.now() + DAY, 0);
}
private startDeleteForEveryoneTimerIfApplicable(): void {
#startDeleteForEveryoneTimerIfApplicable(): void {
const { canDeleteForEveryone } = this.props;
const { hasDeleteForEveryoneTimerExpired } = this.state;
if (
@ -733,7 +736,7 @@ export class Message extends React.PureComponent<Props, State> {
this.deleteForEveryoneTimeout = setTimeout(() => {
this.setState({ hasDeleteForEveryoneTimerExpired: true });
delete this.deleteForEveryoneTimeout;
}, this.getTimeRemainingForDeleteForEveryone());
}, this.#getTimeRemainingForDeleteForEveryone());
}
public checkExpired(): void {
@ -761,12 +764,12 @@ export class Message extends React.PureComponent<Props, State> {
}
}
private areLinksEnabled(): boolean {
#areLinksEnabled(): boolean {
const { isMessageRequestAccepted, isBlocked } = this.props;
return isMessageRequestAccepted && !isBlocked;
}
private shouldRenderAuthor(): boolean {
#shouldRenderAuthor(): boolean {
const { author, conversationType, direction, shouldCollapseAbove } =
this.props;
return Boolean(
@ -777,7 +780,7 @@ export class Message extends React.PureComponent<Props, State> {
);
}
private canRenderStickerLikeEmoji(): boolean {
#canRenderStickerLikeEmoji(): boolean {
const {
attachments,
bodyRanges,
@ -799,7 +802,7 @@ export class Message extends React.PureComponent<Props, State> {
);
}
private updateMetadataWidth = (newMetadataWidth: number): void => {
#updateMetadataWidth = (newMetadataWidth: number): void => {
this.setState(({ metadataWidth }) => ({
// We don't want text to jump around if the metadata shrinks, but we want to make
// sure we have enough room.
@ -807,16 +810,16 @@ export class Message extends React.PureComponent<Props, State> {
}));
};
private handleSelectionChange = () => {
#handleSelectionChange = () => {
const selection = document.getSelection();
if (selection != null && !selection.isCollapsed) {
this.hasSelectedTextRef.current = true;
this.#hasSelectedTextRef.current = true;
}
};
private renderMetadata(): ReactNode {
#renderMetadata(): ReactNode {
let isInline: boolean;
const metadataPlacement = this.getMetadataPlacement();
const metadataPlacement = this.#getMetadataPlacement();
switch (metadataPlacement) {
case MetadataPlacement.NotRendered:
case MetadataPlacement.RenderedByMessageAudioComponent:
@ -854,7 +857,7 @@ export class Message extends React.PureComponent<Props, State> {
timestamp,
} = this.props;
const isStickerLike = isSticker || this.canRenderStickerLikeEmoji();
const isStickerLike = isSticker || this.#canRenderStickerLikeEmoji();
return (
<MessageMetadata
@ -874,9 +877,9 @@ export class Message extends React.PureComponent<Props, State> {
isShowingImage={this.isShowingImage()}
isSticker={isStickerLike}
isTapToViewExpired={isTapToViewExpired}
onWidthMeasured={isInline ? this.updateMetadataWidth : undefined}
onWidthMeasured={isInline ? this.#updateMetadataWidth : undefined}
pushPanelForConversation={pushPanelForConversation}
ref={this.metadataRef}
ref={this.#metadataRef}
retryMessageSend={retryMessageSend}
showEditHistoryModal={showEditHistoryModal}
status={status}
@ -886,7 +889,7 @@ export class Message extends React.PureComponent<Props, State> {
);
}
private renderAuthor(): ReactNode {
#renderAuthor(): ReactNode {
const {
author,
contactNameColor,
@ -896,7 +899,7 @@ export class Message extends React.PureComponent<Props, State> {
isTapToViewExpired,
} = this.props;
if (!this.shouldRenderAuthor()) {
if (!this.#shouldRenderAuthor()) {
return null;
}
@ -951,7 +954,7 @@ export class Message extends React.PureComponent<Props, State> {
const { imageBroken } = this.state;
const collapseMetadata =
this.getMetadataPlacement() === MetadataPlacement.NotRendered;
this.#getMetadataPlacement() === MetadataPlacement.NotRendered;
if (!attachments || !attachments[0]) {
return null;
@ -960,7 +963,7 @@ export class Message extends React.PureComponent<Props, State> {
// For attachments which aren't full-frame
const withContentBelow = Boolean(text || attachmentDroppedDueToSize);
const withContentAbove = Boolean(quote) || this.shouldRenderAuthor();
const withContentAbove = Boolean(quote) || this.#shouldRenderAuthor();
const displayImage = canDisplayImage(attachments);
if (displayImage && !imageBroken) {
@ -1203,7 +1206,7 @@ export class Message extends React.PureComponent<Props, State> {
? i18n('icu:message--call-link-description')
: undefined);
const isClickable = this.areLinksEnabled();
const isClickable = this.#areLinksEnabled();
const className = classNames(
'module-message__link-preview',
@ -1371,7 +1374,7 @@ export class Message extends React.PureComponent<Props, State> {
const maybeSpacer = text
? undefined
: this.getMetadataPlacement() === MetadataPlacement.InlineWithText && (
: this.#getMetadataPlacement() === MetadataPlacement.InlineWithText && (
<MessageTextMetadataSpacer metadataWidth={metadataWidth} />
);
@ -1456,12 +1459,12 @@ export class Message extends React.PureComponent<Props, State> {
)}
>
{description}
{this.getMetadataPlacement() ===
{this.#getMetadataPlacement() ===
MetadataPlacement.InlineWithText && (
<MessageTextMetadataSpacer metadataWidth={metadataWidth} />
)}
</div>
{this.renderMetadata()}
{this.#renderMetadata()}
</div>
</div>
);
@ -1569,7 +1572,7 @@ export class Message extends React.PureComponent<Props, State> {
{buttonContents}
</div>
</button>
{this.renderMetadata()}
{this.#renderMetadata()}
{showOutgoingGiftBadgeModal ? (
<OutgoingGiftBadgeModal
i18n={i18n}
@ -1763,7 +1766,7 @@ export class Message extends React.PureComponent<Props, State> {
conversationType === 'group' && direction === 'incoming';
const withContentBelow =
withCaption ||
this.getMetadataPlacement() !== MetadataPlacement.NotRendered;
this.#getMetadataPlacement() !== MetadataPlacement.NotRendered;
const otherContent =
(contact && contact.firstNumber && contact.serviceId) || withCaption;
@ -1833,7 +1836,7 @@ export class Message extends React.PureComponent<Props, State> {
);
}
private renderAvatar(): ReactNode {
#renderAvatar(): ReactNode {
const {
author,
conversationId,
@ -1854,7 +1857,7 @@ export class Message extends React.PureComponent<Props, State> {
<div
className={classNames('module-message__author-avatar-container', {
'module-message__author-avatar-container--with-reactions':
this.hasReactions(),
this.#hasReactions(),
})}
>
{shouldCollapseBelow ? (
@ -1887,7 +1890,7 @@ export class Message extends React.PureComponent<Props, State> {
);
}
private getContents(): string | undefined {
#getContents(): string | undefined {
const { deletedForEveryone, direction, i18n, status, text } = this.props;
if (deletedForEveryone) {
@ -1920,7 +1923,7 @@ export class Message extends React.PureComponent<Props, State> {
} = this.props;
const { metadataWidth } = this.state;
const contents = this.getContents();
const contents = this.#getContents();
if (!contents) {
return null;
@ -1950,10 +1953,10 @@ export class Message extends React.PureComponent<Props, State> {
const range = window.getSelection()?.getRangeAt(0);
if (
clickCount === 3 &&
this.metadataRef.current &&
range?.intersectsNode(this.metadataRef.current)
this.#metadataRef.current &&
range?.intersectsNode(this.#metadataRef.current)
) {
range.setEndBefore(this.metadataRef.current);
range.setEndBefore(this.#metadataRef.current);
}
}}
onDoubleClick={(event: React.MouseEvent) => {
@ -1965,7 +1968,7 @@ export class Message extends React.PureComponent<Props, State> {
<MessageBodyReadMore
bodyRanges={bodyRanges}
direction={direction}
disableLinks={!this.areLinksEnabled()}
disableLinks={!this.#areLinksEnabled()}
displayLimit={displayLimit}
i18n={i18n}
id={id}
@ -1988,14 +1991,14 @@ export class Message extends React.PureComponent<Props, State> {
text={contents || ''}
textAttachment={textAttachment}
/>
{this.getMetadataPlacement() === MetadataPlacement.InlineWithText && (
{this.#getMetadataPlacement() === MetadataPlacement.InlineWithText && (
<MessageTextMetadataSpacer metadataWidth={metadataWidth} />
)}
</div>
);
}
private shouldShowJoinButton(): boolean {
#shouldShowJoinButton(): boolean {
const { previews } = this.props;
if (previews?.length !== 1) {
@ -2006,10 +2009,10 @@ export class Message extends React.PureComponent<Props, State> {
return Boolean(onlyPreview.isCallLink);
}
private renderAction(): JSX.Element | null {
#renderAction(): JSX.Element | null {
const { direction, activeCallConversationId, i18n, previews } = this.props;
if (this.shouldShowJoinButton()) {
if (this.#shouldShowJoinButton()) {
const firstPreview = previews[0];
const inAnotherCall = Boolean(
activeCallConversationId &&
@ -2044,7 +2047,7 @@ export class Message extends React.PureComponent<Props, State> {
return null;
}
private renderError(): ReactNode {
#renderError(): ReactNode {
const { status, direction } = this.props;
if (
@ -2205,7 +2208,7 @@ export class Message extends React.PureComponent<Props, State> {
} = this.props;
const collapseMetadata =
this.getMetadataPlacement() === MetadataPlacement.NotRendered;
this.#getMetadataPlacement() === MetadataPlacement.NotRendered;
const withContentBelow = !collapseMetadata;
const withContentAbove =
!collapseMetadata &&
@ -2243,7 +2246,7 @@ export class Message extends React.PureComponent<Props, State> {
);
}
private popperPreventOverflowModifier(): Partial<PreventOverflowModifier> {
#popperPreventOverflowModifier(): Partial<PreventOverflowModifier> {
const { containerElementRef } = this.props;
return {
name: 'preventOverflow',
@ -2302,7 +2305,7 @@ export class Message extends React.PureComponent<Props, State> {
public renderReactions(outgoing: boolean): JSX.Element | null {
const { getPreferredBadge, reactions = [], i18n, theme } = this.props;
if (!this.hasReactions()) {
if (!this.#hasReactions()) {
return null;
}
@ -2465,7 +2468,7 @@ export class Message extends React.PureComponent<Props, State> {
<Popper
placement={popperPlacement}
strategy="fixed"
modifiers={[this.popperPreventOverflowModifier()]}
modifiers={[this.#popperPreventOverflowModifier()]}
>
{({ ref, style }) => (
<ReactionViewer
@ -2495,7 +2498,7 @@ export class Message extends React.PureComponent<Props, State> {
return (
<>
{this.renderText()}
{this.renderMetadata()}
{this.#renderMetadata()}
</>
);
}
@ -2508,7 +2511,7 @@ export class Message extends React.PureComponent<Props, State> {
return (
<>
{this.renderTapToView()}
{this.renderMetadata()}
{this.#renderMetadata()}
</>
);
}
@ -2523,8 +2526,8 @@ export class Message extends React.PureComponent<Props, State> {
{this.renderPayment()}
{this.renderEmbeddedContact()}
{this.renderText()}
{this.renderAction()}
{this.renderMetadata()}
{this.#renderAction()}
{this.#renderMetadata()}
{this.renderSendMessageButton()}
</>
);
@ -2740,7 +2743,7 @@ export class Message extends React.PureComponent<Props, State> {
const isAttachmentPending = this.isAttachmentPending();
const width = this.getWidth();
const isEmojiOnly = this.canRenderStickerLikeEmoji();
const isEmojiOnly = this.#canRenderStickerLikeEmoji();
const isStickerLike = isSticker || isEmojiOnly;
// If it's a mostly-normal gray incoming text box, we don't want to darken it as much
@ -2773,7 +2776,7 @@ export class Message extends React.PureComponent<Props, State> {
isTapToViewError
? 'module-message__container--with-tap-to-view-error'
: null,
this.hasReactions() ? 'module-message__container--with-reactions' : null,
this.#hasReactions() ? 'module-message__container--with-reactions' : null,
deletedForEveryone
? 'module-message__container--deleted-for-everyone'
: null
@ -2806,7 +2809,7 @@ export class Message extends React.PureComponent<Props, State> {
}}
tabIndex={-1}
>
{this.renderAuthor()}
{this.#renderAuthor()}
<div dir={TextDirectionToDirAttribute[textDirection]}>
{this.renderContents()}
</div>
@ -2890,13 +2893,13 @@ export class Message extends React.PureComponent<Props, State> {
} else {
wrapperProps = {
onMouseDown: () => {
this.hasSelectedTextRef.current = false;
this.#hasSelectedTextRef.current = false;
},
// We use `onClickCapture` here and preven default/stop propagation to
// prevent other click handlers from firing.
onClickCapture: event => {
if (isMacOS ? event.metaKey : event.ctrlKey) {
if (this.hasSelectedTextRef.current) {
if (this.#hasSelectedTextRef.current) {
return;
}
@ -2964,8 +2967,8 @@ export class Message extends React.PureComponent<Props, State> {
// eslint-disable-next-line react/no-unknown-property
inert={isSelectMode ? '' : undefined}
>
{this.renderError()}
{this.renderAvatar()}
{this.#renderError()}
{this.#renderAvatar()}
{this.renderContainer()}
{renderMenu?.()}
</div>

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>

View file

@ -34,29 +34,24 @@ export type LeftPaneArchivePropsType =
| (LeftPaneArchiveBasePropsType & LeftPaneSearchPropsType);
export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsType> {
private readonly archivedConversations: ReadonlyArray<ConversationListItemPropsType>;
private readonly isSearchingGlobally: boolean;
private readonly searchConversation: undefined | ConversationType;
private readonly searchTerm: string;
private readonly searchHelper: undefined | LeftPaneSearchHelper;
private readonly startSearchCounter: number;
readonly #archivedConversations: ReadonlyArray<ConversationListItemPropsType>;
readonly #isSearchingGlobally: boolean;
readonly #searchConversation: undefined | ConversationType;
readonly #searchTerm: string;
readonly #searchHelper: undefined | LeftPaneSearchHelper;
readonly #startSearchCounter: number;
constructor(props: Readonly<LeftPaneArchivePropsType>) {
super();
this.archivedConversations = props.archivedConversations;
this.isSearchingGlobally = props.isSearchingGlobally;
this.searchConversation = props.searchConversation;
this.searchTerm = props.searchTerm;
this.startSearchCounter = props.startSearchCounter;
this.#archivedConversations = props.archivedConversations;
this.#isSearchingGlobally = props.isSearchingGlobally;
this.#searchConversation = props.searchConversation;
this.#searchTerm = props.searchTerm;
this.#startSearchCounter = props.startSearchCounter;
if ('conversationResults' in props) {
this.searchHelper = new LeftPaneSearchHelper(props);
this.#searchHelper = new LeftPaneSearchHelper(props);
}
}
@ -100,7 +95,7 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
updateSearchTerm: (searchTerm: string) => unknown;
showConversation: ShowConversationType;
}>): ReactChild | null {
if (!this.searchConversation) {
if (!this.#searchConversation) {
return null;
}
@ -111,11 +106,11 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
endConversationSearch={endConversationSearch}
endSearch={endSearch}
i18n={i18n}
isSearchingGlobally={this.isSearchingGlobally}
searchConversation={this.searchConversation}
searchTerm={this.searchTerm}
isSearchingGlobally={this.#isSearchingGlobally}
searchConversation={this.#searchConversation}
searchTerm={this.#searchTerm}
showConversation={showConversation}
startSearchCounter={this.startSearchCounter}
startSearchCounter={this.#startSearchCounter}
updateSearchTerm={updateSearchTerm}
/>
);
@ -128,8 +123,8 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
override getPreRowsNode({
i18n,
}: Readonly<{ i18n: LocalizerType }>): ReactChild | null {
if (this.searchHelper) {
return this.searchHelper.getPreRowsNode({ i18n });
if (this.#searchHelper) {
return this.#searchHelper.getPreRowsNode({ i18n });
}
return (
@ -143,16 +138,16 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
getRowCount(): number {
return (
this.searchHelper?.getRowCount() ?? this.archivedConversations.length
this.#searchHelper?.getRowCount() ?? this.#archivedConversations.length
);
}
getRow(rowIndex: number): undefined | Row {
if (this.searchHelper) {
return this.searchHelper.getRow(rowIndex);
if (this.#searchHelper) {
return this.#searchHelper.getRow(rowIndex);
}
const conversation = this.archivedConversations[rowIndex];
const conversation = this.#archivedConversations[rowIndex];
return conversation
? {
type: RowType.Conversation,
@ -164,14 +159,14 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
override getRowIndexToScrollTo(
selectedConversationId: undefined | string
): undefined | number {
if (this.searchHelper) {
return this.searchHelper.getRowIndexToScrollTo(selectedConversationId);
if (this.#searchHelper) {
return this.#searchHelper.getRowIndexToScrollTo(selectedConversationId);
}
if (!selectedConversationId) {
return undefined;
}
const result = this.archivedConversations.findIndex(
const result = this.#archivedConversations.findIndex(
conversation => conversation.id === selectedConversationId
);
return result === -1 ? undefined : result;
@ -180,7 +175,8 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
getConversationAndMessageAtIndex(
conversationIndex: number
): undefined | { conversationId: string } {
const { archivedConversations, searchHelper } = this;
const searchHelper = this.#searchHelper;
const archivedConversations = this.#archivedConversations;
if (searchHelper) {
return searchHelper.getConversationAndMessageAtIndex(conversationIndex);
@ -196,8 +192,8 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
selectedConversationId: undefined | string,
targetedMessageId: unknown
): undefined | { conversationId: string } {
if (this.searchHelper) {
return this.searchHelper.getConversationAndMessageInDirection(
if (this.#searchHelper) {
return this.#searchHelper.getConversationAndMessageInDirection(
toFind,
selectedConversationId,
targetedMessageId
@ -205,7 +201,7 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
}
return getConversationInDirection(
this.archivedConversations,
this.#archivedConversations,
toFind,
selectedConversationId
);
@ -213,13 +209,13 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
shouldRecomputeRowHeights(old: Readonly<LeftPaneArchivePropsType>): boolean {
const hasSearchingChanged =
'conversationResults' in old !== Boolean(this.searchHelper);
'conversationResults' in old !== Boolean(this.#searchHelper);
if (hasSearchingChanged) {
return true;
}
if ('conversationResults' in old && this.searchHelper) {
return this.searchHelper.shouldRecomputeRowHeights(old);
if ('conversationResults' in old && this.#searchHelper) {
return this.#searchHelper.shouldRecomputeRowHeights(old);
}
return false;
@ -251,7 +247,9 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
!commandAndCtrl &&
shiftKey &&
(key === 'f' || key === 'F') &&
this.archivedConversations.some(({ id }) => id === selectedConversationId)
this.#archivedConversations.some(
({ id }) => id === selectedConversationId
)
) {
searchInConversation(selectedConversationId);

View file

@ -42,31 +42,19 @@ export type LeftPaneChooseGroupMembersPropsType = {
};
export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneChooseGroupMembersPropsType> {
private readonly candidateContacts: ReadonlyArray<ConversationType>;
private readonly isPhoneNumberChecked: boolean;
private readonly isUsernameChecked: boolean;
private readonly isShowingMaximumGroupSizeModal: boolean;
private readonly isShowingRecommendedGroupSizeModal: boolean;
private readonly groupSizeRecommendedLimit: number;
private readonly groupSizeHardLimit: number;
private readonly searchTerm: string;
private readonly phoneNumber: ParsedE164Type | undefined;
private readonly username: string | undefined;
private readonly selectedContacts: Array<ConversationType>;
private readonly selectedConversationIdsSet: Set<string>;
private readonly uuidFetchState: UUIDFetchStateType;
readonly #candidateContacts: ReadonlyArray<ConversationType>;
readonly #isPhoneNumberChecked: boolean;
readonly #isUsernameChecked: boolean;
readonly #isShowingMaximumGroupSizeModal: boolean;
readonly #isShowingRecommendedGroupSizeModal: boolean;
readonly #groupSizeRecommendedLimit: number;
readonly #groupSizeHardLimit: number;
readonly #searchTerm: string;
readonly #phoneNumber: ParsedE164Type | undefined;
readonly #username: string | undefined;
readonly #selectedContacts: Array<ConversationType>;
readonly #selectedConversationIdsSet: Set<string>;
readonly #uuidFetchState: UUIDFetchStateType;
constructor({
candidateContacts,
@ -84,27 +72,27 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
}: Readonly<LeftPaneChooseGroupMembersPropsType>) {
super();
this.uuidFetchState = uuidFetchState;
this.groupSizeRecommendedLimit = groupSizeRecommendedLimit - 1;
this.groupSizeHardLimit = groupSizeHardLimit - 1;
this.#uuidFetchState = uuidFetchState;
this.#groupSizeRecommendedLimit = groupSizeRecommendedLimit - 1;
this.#groupSizeHardLimit = groupSizeHardLimit - 1;
this.candidateContacts = candidateContacts;
this.isShowingMaximumGroupSizeModal = isShowingMaximumGroupSizeModal;
this.isShowingRecommendedGroupSizeModal =
this.#candidateContacts = candidateContacts;
this.#isShowingMaximumGroupSizeModal = isShowingMaximumGroupSizeModal;
this.#isShowingRecommendedGroupSizeModal =
isShowingRecommendedGroupSizeModal;
this.searchTerm = searchTerm;
this.#searchTerm = searchTerm;
const isUsernameVisible =
username !== undefined &&
username !== ourUsername &&
this.candidateContacts.every(contact => contact.username !== username);
this.#candidateContacts.every(contact => contact.username !== username);
if (isUsernameVisible) {
this.username = username;
this.#username = username;
}
this.isUsernameChecked = selectedContacts.some(
contact => contact.username === this.username
this.#isUsernameChecked = selectedContacts.some(
contact => contact.username === this.#username
);
const phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode);
@ -114,22 +102,22 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
phoneNumber
) {
const { e164 } = phoneNumber;
this.isPhoneNumberChecked =
this.#isPhoneNumberChecked =
phoneNumber.isValid &&
selectedContacts.some(contact => contact.e164 === e164);
const isVisible =
e164 !== ourE164 &&
this.candidateContacts.every(contact => contact.e164 !== e164);
this.#candidateContacts.every(contact => contact.e164 !== e164);
if (isVisible) {
this.phoneNumber = phoneNumber;
this.#phoneNumber = phoneNumber;
}
} else {
this.isPhoneNumberChecked = false;
this.#isPhoneNumberChecked = false;
}
this.selectedContacts = selectedContacts;
this.#selectedContacts = selectedContacts;
this.selectedConversationIdsSet = new Set(
this.#selectedConversationIdsSet = new Set(
selectedContacts.map(contact => contact.id)
);
}
@ -183,7 +171,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
onChange={onChangeComposeSearchTerm}
placeholder={i18n('icu:contactSearchPlaceholder')}
ref={focusRef}
value={this.searchTerm}
value={this.#searchTerm}
/>
);
}
@ -200,20 +188,20 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
removeSelectedContact: (conversationId: string) => unknown;
}>): ReactChild {
let modalNode: undefined | ReactChild;
if (this.isShowingMaximumGroupSizeModal) {
if (this.#isShowingMaximumGroupSizeModal) {
modalNode = (
<AddGroupMemberErrorDialog
i18n={i18n}
maximumNumberOfContacts={this.groupSizeHardLimit}
maximumNumberOfContacts={this.#groupSizeHardLimit}
mode={AddGroupMemberErrorDialogMode.MaximumGroupSize}
onClose={closeMaximumGroupSizeModal}
/>
);
} else if (this.isShowingRecommendedGroupSizeModal) {
} else if (this.#isShowingRecommendedGroupSizeModal) {
modalNode = (
<AddGroupMemberErrorDialog
i18n={i18n}
recommendedMaximumNumberOfContacts={this.groupSizeRecommendedLimit}
recommendedMaximumNumberOfContacts={this.#groupSizeRecommendedLimit}
mode={AddGroupMemberErrorDialogMode.RecommendedMaximumGroupSize}
onClose={closeRecommendedGroupSizeModal}
/>
@ -222,9 +210,9 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
return (
<>
{Boolean(this.selectedContacts.length) && (
{Boolean(this.#selectedContacts.length) && (
<ContactPills>
{this.selectedContacts.map(contact => (
{this.#selectedContacts.map(contact => (
<ContactPill
key={contact.id}
acceptedMessageRequest={contact.acceptedMessageRequest}
@ -264,10 +252,10 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
}>): ReactChild {
return (
<Button
disabled={this.hasExceededMaximumNumberOfContacts()}
disabled={this.#hasExceededMaximumNumberOfContacts()}
onClick={startSettingGroupMetadata}
>
{this.selectedContacts.length
{this.#selectedContacts.length
? i18n('icu:chooseGroupMembers__next')
: i18n('icu:chooseGroupMembers__skip')}
</Button>
@ -278,18 +266,18 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
let rowCount = 0;
// Header + Phone Number
if (this.phoneNumber) {
if (this.#phoneNumber) {
rowCount += 2;
}
// Header + Username
if (this.username) {
if (this.#username) {
rowCount += 2;
}
// Header + Contacts
if (this.candidateContacts.length) {
rowCount += 1 + this.candidateContacts.length;
if (this.#candidateContacts.length) {
rowCount += 1 + this.#candidateContacts.length;
}
// Footer
@ -301,7 +289,11 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
}
getRow(actualRowIndex: number): undefined | Row {
if (!this.candidateContacts.length && !this.phoneNumber && !this.username) {
if (
!this.#candidateContacts.length &&
!this.#phoneNumber &&
!this.#username
) {
return undefined;
}
@ -314,7 +306,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
let virtualRowIndex = actualRowIndex;
if (this.candidateContacts.length) {
if (this.#candidateContacts.length) {
if (virtualRowIndex === 0) {
return {
type: RowType.Header,
@ -322,12 +314,12 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
};
}
if (virtualRowIndex <= this.candidateContacts.length) {
const contact = this.candidateContacts[virtualRowIndex - 1];
if (virtualRowIndex <= this.#candidateContacts.length) {
const contact = this.#candidateContacts[virtualRowIndex - 1];
const isChecked = this.selectedConversationIdsSet.has(contact.id);
const isChecked = this.#selectedConversationIdsSet.has(contact.id);
const disabledReason =
!isChecked && this.hasSelectedMaximumNumberOfContacts()
!isChecked && this.#hasSelectedMaximumNumberOfContacts()
? ContactCheckboxDisabledReason.MaximumContactsSelected
: undefined;
@ -339,10 +331,10 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
};
}
virtualRowIndex -= 1 + this.candidateContacts.length;
virtualRowIndex -= 1 + this.#candidateContacts.length;
}
if (this.phoneNumber) {
if (this.#phoneNumber) {
if (virtualRowIndex === 0) {
return {
type: RowType.Header,
@ -352,18 +344,18 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
if (virtualRowIndex === 1) {
return {
type: RowType.PhoneNumberCheckbox,
isChecked: this.isPhoneNumberChecked,
isChecked: this.#isPhoneNumberChecked,
isFetching: isFetchingByE164(
this.uuidFetchState,
this.phoneNumber.e164
this.#uuidFetchState,
this.#phoneNumber.e164
),
phoneNumber: this.phoneNumber,
phoneNumber: this.#phoneNumber,
};
}
virtualRowIndex -= 2;
}
if (this.username) {
if (this.#username) {
if (virtualRowIndex === 0) {
return {
type: RowType.Header,
@ -373,9 +365,12 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
if (virtualRowIndex === 1) {
return {
type: RowType.UsernameCheckbox,
isChecked: this.isUsernameChecked,
isFetching: isFetchingByUsername(this.uuidFetchState, this.username),
username: this.username,
isChecked: this.#isUsernameChecked,
isFetching: isFetchingByUsername(
this.#uuidFetchState,
this.#username
),
username: this.#username,
};
}
virtualRowIndex -= 2;
@ -402,13 +397,13 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
return false;
}
private hasSelectedMaximumNumberOfContacts(): boolean {
return this.selectedContacts.length >= this.groupSizeHardLimit;
#hasSelectedMaximumNumberOfContacts(): boolean {
return this.#selectedContacts.length >= this.#groupSizeHardLimit;
}
private hasExceededMaximumNumberOfContacts(): boolean {
#hasExceededMaximumNumberOfContacts(): boolean {
// It should be impossible to reach this state. This is here as a failsafe.
return this.selectedContacts.length > this.groupSizeHardLimit;
return this.#selectedContacts.length > this.#groupSizeHardLimit;
}
}

View file

@ -35,21 +35,14 @@ enum TopButtons {
}
export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsType> {
private readonly composeContacts: ReadonlyArray<ContactListItemConversationType>;
private readonly composeGroups: ReadonlyArray<GroupListItemConversationType>;
private readonly uuidFetchState: UUIDFetchStateType;
private readonly searchTerm: string;
private readonly phoneNumber: ParsedE164Type | undefined;
private readonly isPhoneNumberVisible: boolean;
private readonly username: string | undefined;
private readonly isUsernameVisible: boolean;
readonly #composeContacts: ReadonlyArray<ContactListItemConversationType>;
readonly #composeGroups: ReadonlyArray<GroupListItemConversationType>;
readonly #uuidFetchState: UUIDFetchStateType;
readonly #searchTerm: string;
readonly #phoneNumber: ParsedE164Type | undefined;
readonly #isPhoneNumberVisible: boolean;
readonly #username: string | undefined;
readonly #isUsernameVisible: boolean;
constructor({
composeContacts,
@ -61,24 +54,24 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
}: Readonly<LeftPaneComposePropsType>) {
super();
this.composeContacts = composeContacts;
this.composeGroups = composeGroups;
this.searchTerm = searchTerm;
this.uuidFetchState = uuidFetchState;
this.#composeContacts = composeContacts;
this.#composeGroups = composeGroups;
this.#searchTerm = searchTerm;
this.#uuidFetchState = uuidFetchState;
this.username = username;
this.isUsernameVisible =
this.#username = username;
this.#isUsernameVisible =
Boolean(username) &&
this.composeContacts.every(contact => contact.username !== username);
this.#composeContacts.every(contact => contact.username !== username);
const phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode);
if (!username && phoneNumber) {
this.phoneNumber = phoneNumber;
this.isPhoneNumberVisible = this.composeContacts.every(
this.#phoneNumber = phoneNumber;
this.#isPhoneNumberVisible = this.#composeContacts.every(
contact => contact.e164 !== phoneNumber.e164
);
} else {
this.isPhoneNumberVisible = false;
this.#isPhoneNumberVisible = false;
}
}
@ -125,7 +118,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
onChange={onChangeComposeSearchTerm}
placeholder={i18n('icu:contactSearchPlaceholder')}
ref={focusRef}
value={this.searchTerm}
value={this.#searchTerm}
/>
);
}
@ -143,20 +136,20 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
}
getRowCount(): number {
let result = this.composeContacts.length + this.composeGroups.length;
if (this.hasTopButtons()) {
let result = this.#composeContacts.length + this.#composeGroups.length;
if (this.#hasTopButtons()) {
result += 3;
}
if (this.hasContactsHeader()) {
if (this.#hasContactsHeader()) {
result += 1;
}
if (this.hasGroupsHeader()) {
if (this.#hasGroupsHeader()) {
result += 1;
}
if (this.isUsernameVisible) {
if (this.#isUsernameVisible) {
result += 2;
}
if (this.isPhoneNumberVisible) {
if (this.#isPhoneNumberVisible) {
result += 2;
}
@ -165,7 +158,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
getRow(actualRowIndex: number): undefined | Row {
let virtualRowIndex = actualRowIndex;
if (this.hasTopButtons()) {
if (this.#hasTopButtons()) {
if (virtualRowIndex === 0) {
return { type: RowType.CreateNewGroup };
}
@ -179,7 +172,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
virtualRowIndex -= 3;
}
if (this.hasContactsHeader()) {
if (this.#hasContactsHeader()) {
if (virtualRowIndex === 0) {
return {
type: RowType.Header,
@ -189,7 +182,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
virtualRowIndex -= 1;
const contact = this.composeContacts[virtualRowIndex];
const contact = this.#composeContacts[virtualRowIndex];
if (contact) {
return {
type: RowType.Contact,
@ -198,10 +191,10 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
};
}
virtualRowIndex -= this.composeContacts.length;
virtualRowIndex -= this.#composeContacts.length;
}
if (this.hasGroupsHeader()) {
if (this.#hasGroupsHeader()) {
if (virtualRowIndex === 0) {
return {
type: RowType.Header,
@ -211,7 +204,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
virtualRowIndex -= 1;
const group = this.composeGroups[virtualRowIndex];
const group = this.#composeGroups[virtualRowIndex];
if (group) {
return {
type: RowType.SelectSingleGroup,
@ -219,10 +212,10 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
};
}
virtualRowIndex -= this.composeGroups.length;
virtualRowIndex -= this.#composeGroups.length;
}
if (this.username && this.isUsernameVisible) {
if (this.#username && this.#isUsernameVisible) {
if (virtualRowIndex === 0) {
return {
type: RowType.Header,
@ -235,16 +228,16 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
if (virtualRowIndex === 0) {
return {
type: RowType.UsernameSearchResult,
username: this.username,
username: this.#username,
isFetchingUsername: isFetchingByUsername(
this.uuidFetchState,
this.username
this.#uuidFetchState,
this.#username
),
};
}
}
if (this.phoneNumber && this.isPhoneNumberVisible) {
if (this.#phoneNumber && this.#isPhoneNumberVisible) {
if (virtualRowIndex === 0) {
return {
type: RowType.Header,
@ -257,10 +250,10 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
if (virtualRowIndex === 0) {
return {
type: RowType.StartNewConversation,
phoneNumber: this.phoneNumber,
phoneNumber: this.#phoneNumber,
isFetching: isFetchingByE164(
this.uuidFetchState,
this.phoneNumber.e164
this.#uuidFetchState,
this.#phoneNumber.e164
),
};
}
@ -287,8 +280,8 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
exProps: Readonly<LeftPaneComposePropsType>
): boolean {
const prev = new LeftPaneComposeHelper(exProps);
const currHeaderIndices = this.getHeaderIndices();
const prevHeaderIndices = prev.getHeaderIndices();
const currHeaderIndices = this.#getHeaderIndices();
const prevHeaderIndices = prev.#getHeaderIndices();
return (
currHeaderIndices.top !== prevHeaderIndices.top ||
@ -299,26 +292,26 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
);
}
private getTopButtons(): TopButtons {
if (this.searchTerm) {
#getTopButtons(): TopButtons {
if (this.#searchTerm) {
return TopButtons.None;
}
return TopButtons.Visible;
}
private hasTopButtons(): boolean {
return this.getTopButtons() !== TopButtons.None;
#hasTopButtons(): boolean {
return this.#getTopButtons() !== TopButtons.None;
}
private hasContactsHeader(): boolean {
return Boolean(this.composeContacts.length);
#hasContactsHeader(): boolean {
return Boolean(this.#composeContacts.length);
}
private hasGroupsHeader(): boolean {
return Boolean(this.composeGroups.length);
#hasGroupsHeader(): boolean {
return Boolean(this.#composeGroups.length);
}
private getHeaderIndices(): {
#getHeaderIndices(): {
top?: number;
contact?: number;
group?: number;
@ -333,22 +326,22 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
let rowCount = 0;
if (this.hasTopButtons()) {
if (this.#hasTopButtons()) {
top = 0;
rowCount += 3;
}
if (this.hasContactsHeader()) {
if (this.#hasContactsHeader()) {
contact = rowCount;
rowCount += this.composeContacts.length;
rowCount += this.#composeContacts.length;
}
if (this.hasGroupsHeader()) {
if (this.#hasGroupsHeader()) {
group = rowCount;
rowCount += this.composeContacts.length;
rowCount += this.#composeContacts.length;
}
if (this.phoneNumber) {
if (this.#phoneNumber) {
phoneNumber = rowCount;
}
if (this.username) {
if (this.#username) {
username = rowCount;
}

View file

@ -36,17 +36,12 @@ type DoLookupActionsType = Readonly<{
LookupConversationWithoutServiceIdActionsType;
export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFindByPhoneNumberPropsType> {
private readonly searchTerm: string;
private readonly phoneNumber: ParsedE164Type | undefined;
private readonly regionCode: string | undefined;
private readonly uuidFetchState: UUIDFetchStateType;
private readonly countries: ReadonlyArray<CountryDataType>;
private readonly selectedRegion: string;
readonly #searchTerm: string;
readonly #phoneNumber: ParsedE164Type | undefined;
readonly #regionCode: string | undefined;
readonly #uuidFetchState: UUIDFetchStateType;
readonly #countries: ReadonlyArray<CountryDataType>;
readonly #selectedRegion: string;
constructor({
searchTerm,
@ -57,14 +52,14 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
}: Readonly<LeftPaneFindByPhoneNumberPropsType>) {
super();
this.searchTerm = searchTerm;
this.uuidFetchState = uuidFetchState;
this.regionCode = regionCode;
this.countries = countries;
this.selectedRegion = selectedRegion;
this.#searchTerm = searchTerm;
this.#uuidFetchState = uuidFetchState;
this.#regionCode = regionCode;
this.#countries = countries;
this.#selectedRegion = selectedRegion;
this.phoneNumber = parseAndFormatPhoneNumber(
this.searchTerm,
this.#phoneNumber = parseAndFormatPhoneNumber(
this.#searchTerm,
selectedRegion || regionCode
);
}
@ -83,7 +78,7 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
<button
aria-label={backButtonLabel}
className="module-left-pane__header__contents__back-button"
disabled={this.isFetching()}
disabled={this.#isFetching()}
onClick={this.getBackAction({ startComposing })}
title={backButtonLabel}
type="button"
@ -100,7 +95,7 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
}: {
startComposing: () => void;
}): undefined | (() => void) {
return this.isFetching() ? undefined : startComposing;
return this.#isFetching() ? undefined : startComposing;
}
override getSearchInput({
@ -122,25 +117,25 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
return (
<div className="LeftPaneFindByPhoneNumberHelper__container">
<CountryCodeSelect
countries={this.countries}
countries={this.#countries}
i18n={i18n}
defaultRegion={this.regionCode ?? ''}
value={this.selectedRegion}
defaultRegion={this.#regionCode ?? ''}
value={this.#selectedRegion}
onChange={onChangeComposeSelectedRegion}
/>
<SearchInput
hasSearchIcon={false}
disabled={this.isFetching()}
disabled={this.#isFetching()}
i18n={i18n}
moduleClassName="LeftPaneFindByPhoneNumberHelper__search-input"
onChange={onChangeComposeSearchTerm}
placeholder={placeholder}
ref={focusRef}
value={this.searchTerm}
value={this.#searchTerm}
onKeyDown={ev => {
if (ev.key === 'Enter') {
drop(this.doLookup(lookupActions));
drop(this.#doLookup(lookupActions));
}
}}
/>
@ -157,10 +152,10 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
DoLookupActionsType): ReactChild {
return (
<Button
disabled={this.isLookupDisabled()}
onClick={() => drop(this.doLookup(lookupActions))}
disabled={this.#isLookupDisabled()}
onClick={() => drop(this.#doLookup(lookupActions))}
>
{this.isFetching() ? (
{this.#isFetching() ? (
<span aria-label={i18n('icu:loading')} role="status">
<Spinner size="20px" svgSize="small" direction="on-avatar" />
</span>
@ -198,14 +193,14 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
return false;
}
private async doLookup({
async #doLookup({
lookupConversationWithoutServiceId,
showUserNotFoundModal,
setIsFetchingUUID,
showInbox,
showConversation,
}: DoLookupActionsType): Promise<void> {
if (!this.phoneNumber || this.isLookupDisabled()) {
if (!this.#phoneNumber || this.#isLookupDisabled()) {
return;
}
@ -213,8 +208,8 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
showUserNotFoundModal,
setIsFetchingUUID,
type: 'e164',
e164: this.phoneNumber.e164,
phoneNumber: this.searchTerm,
e164: this.#phoneNumber.e164,
phoneNumber: this.#searchTerm,
});
if (conversationId != null) {
@ -223,20 +218,20 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
}
}
private isFetching(): boolean {
if (this.phoneNumber != null) {
return isFetchingByE164(this.uuidFetchState, this.phoneNumber.e164);
#isFetching(): boolean {
if (this.#phoneNumber != null) {
return isFetchingByE164(this.#uuidFetchState, this.#phoneNumber.e164);
}
return false;
}
private isLookupDisabled(): boolean {
if (this.isFetching()) {
#isLookupDisabled(): boolean {
if (this.#isFetching()) {
return true;
}
return !this.phoneNumber?.isValid;
return !this.#phoneNumber?.isValid;
}
}

View file

@ -30,11 +30,9 @@ type DoLookupActionsType = Readonly<{
LookupConversationWithoutServiceIdActionsType;
export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByUsernamePropsType> {
private readonly searchTerm: string;
private readonly username: string | undefined;
private readonly uuidFetchState: UUIDFetchStateType;
readonly #searchTerm: string;
readonly #username: string | undefined;
readonly #uuidFetchState: UUIDFetchStateType;
constructor({
searchTerm,
@ -43,10 +41,10 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
}: Readonly<LeftPaneFindByUsernamePropsType>) {
super();
this.searchTerm = searchTerm;
this.uuidFetchState = uuidFetchState;
this.#searchTerm = searchTerm;
this.#uuidFetchState = uuidFetchState;
this.username = username;
this.#username = username;
}
override getHeaderContents({
@ -63,7 +61,7 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
<button
aria-label={backButtonLabel}
className="module-left-pane__header__contents__back-button"
disabled={this.isFetching()}
disabled={this.#isFetching()}
onClick={this.getBackAction({ startComposing })}
title={backButtonLabel}
type="button"
@ -80,7 +78,7 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
}: {
startComposing: () => void;
}): undefined | (() => void) {
return this.isFetching() ? undefined : startComposing;
return this.#isFetching() ? undefined : startComposing;
}
override getSearchInput({
@ -103,17 +101,17 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
return (
<SearchInput
hasSearchIcon={false}
disabled={this.isFetching()}
disabled={this.#isFetching()}
i18n={i18n}
moduleClassName="LeftPaneFindByUsernameHelper__search-input"
onChange={onChangeComposeSearchTerm}
placeholder={placeholder}
ref={focusRef}
value={this.searchTerm}
value={this.#searchTerm}
description={description}
onKeyDown={ev => {
if (ev.key === 'Enter') {
drop(this.doLookup(lookupActions));
drop(this.#doLookup(lookupActions));
}
}}
/>
@ -129,10 +127,10 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
DoLookupActionsType): ReactChild {
return (
<Button
disabled={this.isLookupDisabled()}
onClick={() => drop(this.doLookup(lookupActions))}
disabled={this.#isLookupDisabled()}
onClick={() => drop(this.#doLookup(lookupActions))}
>
{this.isFetching() ? (
{this.#isFetching() ? (
<span aria-label={i18n('icu:loading')} role="status">
<Spinner size="20px" svgSize="small" direction="on-avatar" />
</span>
@ -170,14 +168,14 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
return false;
}
private async doLookup({
async #doLookup({
lookupConversationWithoutServiceId,
showUserNotFoundModal,
setIsFetchingUUID,
showInbox,
showConversation,
}: DoLookupActionsType): Promise<void> {
if (!this.username || this.isLookupDisabled()) {
if (!this.#username || this.#isLookupDisabled()) {
return;
}
@ -185,7 +183,7 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
showUserNotFoundModal,
setIsFetchingUUID,
type: 'username',
username: this.username,
username: this.#username,
});
if (conversationId != null) {
@ -194,20 +192,20 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
}
}
private isFetching(): boolean {
if (this.username != null) {
return isFetchingByUsername(this.uuidFetchState, this.username);
#isFetching(): boolean {
if (this.#username != null) {
return isFetchingByUsername(this.#uuidFetchState, this.#username);
}
return false;
}
private isLookupDisabled(): boolean {
if (this.isFetching()) {
#isLookupDisabled(): boolean {
if (this.#isFetching()) {
return true;
}
return this.username == null;
return this.#username == null;
}
}

View file

@ -34,25 +34,16 @@ export type LeftPaneInboxPropsType = {
};
export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType> {
private readonly conversations: ReadonlyArray<ConversationListItemPropsType>;
private readonly archivedConversations: ReadonlyArray<ConversationListItemPropsType>;
private readonly pinnedConversations: ReadonlyArray<ConversationListItemPropsType>;
private readonly isAboutToSearch: boolean;
private readonly isSearchingGlobally: boolean;
private readonly startSearchCounter: number;
private readonly searchDisabled: boolean;
private readonly searchTerm: string;
private readonly searchConversation: undefined | ConversationType;
private readonly filterByUnread: boolean;
readonly #conversations: ReadonlyArray<ConversationListItemPropsType>;
readonly #archivedConversations: ReadonlyArray<ConversationListItemPropsType>;
readonly #pinnedConversations: ReadonlyArray<ConversationListItemPropsType>;
readonly #isAboutToSearch: boolean;
readonly #isSearchingGlobally: boolean;
readonly #startSearchCounter: number;
readonly #searchDisabled: boolean;
readonly #searchTerm: string;
readonly #searchConversation: undefined | ConversationType;
readonly #filterByUnread: boolean;
constructor({
conversations,
@ -68,25 +59,25 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
}: Readonly<LeftPaneInboxPropsType>) {
super();
this.conversations = conversations;
this.archivedConversations = archivedConversations;
this.pinnedConversations = pinnedConversations;
this.isAboutToSearch = isAboutToSearch;
this.isSearchingGlobally = isSearchingGlobally;
this.startSearchCounter = startSearchCounter;
this.searchDisabled = searchDisabled;
this.searchTerm = searchTerm;
this.searchConversation = searchConversation;
this.filterByUnread = filterByUnread;
this.#conversations = conversations;
this.#archivedConversations = archivedConversations;
this.#pinnedConversations = pinnedConversations;
this.#isAboutToSearch = isAboutToSearch;
this.#isSearchingGlobally = isSearchingGlobally;
this.#startSearchCounter = startSearchCounter;
this.#searchDisabled = searchDisabled;
this.#searchTerm = searchTerm;
this.#searchConversation = searchConversation;
this.#filterByUnread = filterByUnread;
}
getRowCount(): number {
const headerCount = this.hasPinnedAndNonpinned() ? 2 : 0;
const buttonCount = this.archivedConversations.length ? 1 : 0;
const headerCount = this.#hasPinnedAndNonpinned() ? 2 : 0;
const buttonCount = this.#archivedConversations.length ? 1 : 0;
return (
headerCount +
this.pinnedConversations.length +
this.conversations.length +
this.#pinnedConversations.length +
this.#conversations.length +
buttonCount
);
}
@ -116,17 +107,17 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
clearSearchQuery={clearSearchQuery}
endConversationSearch={endConversationSearch}
endSearch={endSearch}
disabled={this.searchDisabled}
disabled={this.#searchDisabled}
i18n={i18n}
isSearchingGlobally={this.isSearchingGlobally}
searchConversation={this.searchConversation}
searchTerm={this.searchTerm}
isSearchingGlobally={this.#isSearchingGlobally}
searchConversation={this.#searchConversation}
searchTerm={this.#searchTerm}
showConversation={showConversation}
startSearchCounter={this.startSearchCounter}
startSearchCounter={this.#startSearchCounter}
updateSearchTerm={updateSearchTerm}
onFilterClick={updateFilterByUnread}
filterButtonEnabled={!this.searchConversation}
filterPressed={this.filterByUnread}
filterButtonEnabled={!this.#searchConversation}
filterPressed={this.#filterByUnread}
/>
);
}
@ -149,11 +140,13 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
}
getRow(rowIndex: number): undefined | Row {
const { conversations, archivedConversations, pinnedConversations } = this;
const pinnedConversations = this.#pinnedConversations;
const archivedConversations = this.#archivedConversations;
const conversations = this.#conversations;
const archivedConversationsCount = archivedConversations.length;
if (this.hasPinnedAndNonpinned()) {
if (this.#hasPinnedAndNonpinned()) {
switch (rowIndex) {
case 0:
return {
@ -226,9 +219,9 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
const isConversationSelected = (
conversation: Readonly<ConversationListItemPropsType>
) => conversation.id === selectedConversationId;
const hasHeaders = this.hasPinnedAndNonpinned();
const hasHeaders = this.#hasPinnedAndNonpinned();
const pinnedConversationIndex = this.pinnedConversations.findIndex(
const pinnedConversationIndex = this.#pinnedConversations.findIndex(
isConversationSelected
);
if (pinnedConversationIndex !== -1) {
@ -236,11 +229,11 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
return pinnedConversationIndex + headerOffset;
}
const conversationIndex = this.conversations.findIndex(
const conversationIndex = this.#conversations.findIndex(
isConversationSelected
);
if (conversationIndex !== -1) {
const pinnedOffset = this.pinnedConversations.length;
const pinnedOffset = this.#pinnedConversations.length;
const headerOffset = hasHeaders ? 2 : 0;
return conversationIndex + pinnedOffset + headerOffset;
}
@ -250,20 +243,21 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
override requiresFullWidth(): boolean {
const hasNoConversations =
!this.conversations.length &&
!this.pinnedConversations.length &&
!this.archivedConversations.length;
return hasNoConversations || this.isAboutToSearch;
!this.#conversations.length &&
!this.#pinnedConversations.length &&
!this.#archivedConversations.length;
return hasNoConversations || this.#isAboutToSearch;
}
shouldRecomputeRowHeights(old: Readonly<LeftPaneInboxPropsType>): boolean {
return old.pinnedConversations.length !== this.pinnedConversations.length;
return old.pinnedConversations.length !== this.#pinnedConversations.length;
}
getConversationAndMessageAtIndex(
conversationIndex: number
): undefined | { conversationId: string } {
const { conversations, pinnedConversations } = this;
const pinnedConversations = this.#pinnedConversations;
const conversations = this.#conversations;
const conversation =
pinnedConversations[conversationIndex] ||
conversations[conversationIndex - pinnedConversations.length] ||
@ -278,7 +272,7 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
_targetedMessageId: unknown
): undefined | { conversationId: string } {
return getConversationInDirection(
[...this.pinnedConversations, ...this.conversations],
[...this.#pinnedConversations, ...this.#conversations],
toFind,
selectedConversationId
);
@ -295,9 +289,9 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
handleKeydownForSearch(event, options);
}
private hasPinnedAndNonpinned(): boolean {
#hasPinnedAndNonpinned(): boolean {
return Boolean(
this.pinnedConversations.length && this.conversations.length
this.#pinnedConversations.length && this.#conversations.length
);
}
}

View file

@ -50,36 +50,24 @@ export type LeftPaneSearchPropsType = {
searchConversation: undefined | ConversationType;
};
const searchResultKeys: Array<
'conversationResults' | 'contactResults' | 'messageResults'
> = ['conversationResults', 'contactResults', 'messageResults'];
export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType> {
private readonly conversationResults: MaybeLoadedSearchResultsType<ConversationListItemPropsType>;
readonly #conversationResults: MaybeLoadedSearchResultsType<ConversationListItemPropsType>;
readonly #contactResults: MaybeLoadedSearchResultsType<ConversationListItemPropsType>;
readonly #isSearchingGlobally: boolean;
private readonly contactResults: MaybeLoadedSearchResultsType<ConversationListItemPropsType>;
private readonly isSearchingGlobally: boolean;
private readonly messageResults: MaybeLoadedSearchResultsType<{
readonly #messageResults: MaybeLoadedSearchResultsType<{
id: string;
conversationId: string;
type: string;
}>;
private readonly searchConversationName?: string;
private readonly primarySendsSms: boolean;
private readonly searchTerm: string;
private readonly startSearchCounter: number;
private readonly searchDisabled: boolean;
private readonly searchConversation: undefined | ConversationType;
private readonly filterByUnread: boolean;
readonly #searchConversationName?: string;
readonly #primarySendsSms: boolean;
readonly #searchTerm: string;
readonly #startSearchCounter: number;
readonly #searchDisabled: boolean;
readonly #searchConversation: undefined | ConversationType;
readonly #filterByUnread: boolean;
constructor({
contactResults,
@ -96,18 +84,17 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
}: Readonly<LeftPaneSearchPropsType>) {
super();
this.contactResults = contactResults;
this.conversationResults = conversationResults;
this.isSearchingGlobally = isSearchingGlobally;
this.messageResults = messageResults;
this.primarySendsSms = primarySendsSms;
this.searchConversation = searchConversation;
this.searchConversationName = searchConversationName;
this.searchDisabled = searchDisabled;
this.searchTerm = searchTerm;
this.startSearchCounter = startSearchCounter;
this.filterByUnread = filterByUnread;
this.onEnterKeyDown = this.onEnterKeyDown.bind(this);
this.#contactResults = contactResults;
this.#conversationResults = conversationResults;
this.#isSearchingGlobally = isSearchingGlobally;
this.#messageResults = messageResults;
this.#primarySendsSms = primarySendsSms;
this.#searchConversation = searchConversation;
this.#searchConversationName = searchConversationName;
this.#searchDisabled = searchDisabled;
this.#searchTerm = searchTerm;
this.#startSearchCounter = startSearchCounter;
this.#filterByUnread = filterByUnread;
}
override getSearchInput({
@ -135,17 +122,17 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
clearSearchQuery={clearSearchQuery}
endConversationSearch={endConversationSearch}
endSearch={endSearch}
disabled={this.searchDisabled}
disabled={this.#searchDisabled}
i18n={i18n}
isSearchingGlobally={this.isSearchingGlobally}
onEnterKeyDown={this.onEnterKeyDown}
searchConversation={this.searchConversation}
searchTerm={this.searchTerm}
isSearchingGlobally={this.#isSearchingGlobally}
onEnterKeyDown={this.#onEnterKeyDown}
searchConversation={this.#searchConversation}
searchTerm={this.#searchTerm}
showConversation={showConversation}
startSearchCounter={this.startSearchCounter}
startSearchCounter={this.#startSearchCounter}
updateSearchTerm={updateSearchTerm}
filterButtonEnabled={!this.searchConversation}
filterPressed={this.filterByUnread}
filterButtonEnabled={!this.#searchConversation}
filterPressed={this.#filterByUnread}
onFilterClick={updateFilterByUnread}
/>
);
@ -156,7 +143,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
}: Readonly<{
i18n: LocalizerType;
}>): ReactChild | null {
const mightHaveSearchResults = this.allResults().some(
const mightHaveSearchResults = this.#allResults().some(
searchResult => searchResult.isLoading || searchResult.results.length
);
@ -164,7 +151,9 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
return null;
}
const { searchConversationName, primarySendsSms, searchTerm } = this;
const searchTerm = this.#searchTerm;
const primarySendsSms = this.#primarySendsSms;
const searchConversationName = this.#searchConversationName;
let noResults: ReactChild;
if (searchConversationName) {
@ -182,11 +171,11 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
);
} else {
let noResultsMessage: string;
if (this.filterByUnread && this.searchTerm.length > 0) {
if (this.#filterByUnread && this.#searchTerm.length > 0) {
noResultsMessage = i18n('icu:noSearchResultsWithUnreadFilter', {
searchTerm,
});
} else if (this.filterByUnread) {
} else if (this.#filterByUnread) {
noResultsMessage = i18n('icu:noSearchResultsOnlyUnreadFilter');
} else {
noResultsMessage = i18n('icu:noSearchResults', {
@ -195,7 +184,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
}
noResults = (
<>
{this.filterByUnread && (
{this.#filterByUnread && (
<div
className="module-conversation-list__item--header module-left-pane__no-search-results__unread-header"
aria-label={i18n('icu:conversationsUnreadHeader')}
@ -218,7 +207,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
// We need this for Ctrl-T shortcut cycling through parts of app
tabIndex={-1}
className={
this.filterByUnread
this.#filterByUnread
? 'module-left-pane__no-search-results--withHeader'
: 'module-left-pane__no-search-results'
}
@ -230,19 +219,19 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
}
getRowCount(): number {
if (this.isLoading()) {
if (this.#isLoading()) {
// 1 for the header.
return 1 + SEARCH_RESULTS_FAKE_ROW_COUNT;
}
let count = this.allResults().reduce(
let count = this.#allResults().reduce(
(result: number, searchResults) =>
result + getRowCountForLoadedSearchResults(searchResults),
0
);
// The clear unread filter button adds an extra row
if (this.filterByUnread) {
if (this.#filterByUnread) {
count += 1;
}
@ -257,9 +246,11 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
}
getRow(rowIndex: number): undefined | Row {
const { conversationResults, contactResults, messageResults } = this;
const messageResults = this.#messageResults;
const contactResults = this.#contactResults;
const conversationResults = this.#conversationResults;
if (this.isLoading()) {
if (this.#isLoading()) {
if (rowIndex === 0) {
return { type: RowType.SearchResultsLoadingFakeHeader };
}
@ -273,7 +264,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
getRowCountForLoadedSearchResults(conversationResults);
const contactRowCount = getRowCountForLoadedSearchResults(contactResults);
const messageRowCount = getRowCountForLoadedSearchResults(messageResults);
const clearFilterButtonRowCount = this.filterByUnread ? 1 : 0;
const clearFilterButtonRowCount = this.#filterByUnread ? 1 : 0;
let rowOffset = 0;
@ -283,7 +274,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
return {
type: RowType.Header,
getHeaderText: i18n =>
this.filterByUnread
this.#filterByUnread
? i18n('icu:conversationsUnreadHeader')
: i18n('icu:conversationsHeader'),
};
@ -350,7 +341,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
if (rowIndex < rowOffset) {
return {
type: RowType.ClearFilterButton,
isOnNoResultsPage: this.allResults().every(
isOnNoResultsPage: this.#allResults().every(
searchResult =>
searchResult.isLoading || searchResult.results.length === 0
),
@ -361,24 +352,30 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
}
override isScrollable(): boolean {
return !this.isLoading();
return !this.#isLoading();
}
shouldRecomputeRowHeights(old: Readonly<LeftPaneSearchPropsType>): boolean {
const oldSearchPaneHelper = new LeftPaneSearchHelper(old);
const oldIsLoading = oldSearchPaneHelper.isLoading();
const newIsLoading = this.isLoading();
const oldIsLoading = oldSearchPaneHelper.#isLoading();
const newIsLoading = this.#isLoading();
if (oldIsLoading && newIsLoading) {
return false;
}
if (oldIsLoading !== newIsLoading) {
return true;
}
return searchResultKeys.some(
key =>
getRowCountForLoadedSearchResults(old[key]) !==
getRowCountForLoadedSearchResults(this[key])
);
const searchResultsByKey = [
{ current: this.#conversationResults, prev: old.conversationResults },
{ current: this.#contactResults, prev: old.contactResults },
{ current: this.#messageResults, prev: old.messageResults },
];
return searchResultsByKey.some(item => {
return (
getRowCountForLoadedSearchResults(item.prev) !==
getRowCountForLoadedSearchResults(item.current)
);
});
}
getConversationAndMessageAtIndex(
@ -388,7 +385,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
return undefined;
}
let pointer = conversationIndex;
for (const list of this.allResults()) {
for (const list of this.#allResults()) {
if (list.isLoading) {
continue;
}
@ -426,25 +423,29 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
handleKeydownForSearch(event, options);
}
private allResults() {
return [this.conversationResults, this.contactResults, this.messageResults];
#allResults() {
return [
this.#conversationResults,
this.#contactResults,
this.#messageResults,
];
}
private isLoading(): boolean {
return this.allResults().some(results => results.isLoading);
#isLoading(): boolean {
return this.#allResults().some(results => results.isLoading);
}
private onEnterKeyDown(
#onEnterKeyDown = (
clearSearchQuery: () => unknown,
showConversation: ShowConversationType
): void {
): void => {
const conversation = this.getConversationAndMessageAtIndex(0);
if (!conversation) {
return;
}
showConversation(conversation);
clearSearchQuery();
}
};
}
function getRowCountForLoadedSearchResults(

View file

@ -38,21 +38,14 @@ export type LeftPaneSetGroupMetadataPropsType = {
};
export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGroupMetadataPropsType> {
private readonly groupAvatar: undefined | Uint8Array;
private readonly groupName: string;
private readonly groupExpireTimer: DurationInSeconds;
private readonly hasError: boolean;
private readonly isCreating: boolean;
private readonly isEditingAvatar: boolean;
private readonly selectedContacts: ReadonlyArray<ContactListItemConversationType>;
private readonly userAvatarData: ReadonlyArray<AvatarDataType>;
readonly #groupAvatar: undefined | Uint8Array;
readonly #groupName: string;
readonly #groupExpireTimer: DurationInSeconds;
readonly #hasError: boolean;
readonly #isCreating: boolean;
readonly #isEditingAvatar: boolean;
readonly #selectedContacts: ReadonlyArray<ContactListItemConversationType>;
readonly #userAvatarData: ReadonlyArray<AvatarDataType>;
constructor({
groupAvatar,
@ -66,14 +59,14 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
}: Readonly<LeftPaneSetGroupMetadataPropsType>) {
super();
this.groupAvatar = groupAvatar;
this.groupName = groupName;
this.groupExpireTimer = groupExpireTimer;
this.hasError = hasError;
this.isCreating = isCreating;
this.isEditingAvatar = isEditingAvatar;
this.selectedContacts = selectedContacts;
this.userAvatarData = userAvatarData;
this.#groupAvatar = groupAvatar;
this.#groupName = groupName;
this.#groupExpireTimer = groupExpireTimer;
this.#hasError = hasError;
this.#isCreating = isCreating;
this.#isEditingAvatar = isEditingAvatar;
this.#selectedContacts = selectedContacts;
this.#userAvatarData = userAvatarData;
}
override getHeaderContents({
@ -90,7 +83,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
<button
aria-label={backButtonLabel}
className="module-left-pane__header__contents__back-button"
disabled={this.isCreating}
disabled={this.#isCreating}
onClick={this.getBackAction({ showChooseGroupMembers })}
title={backButtonLabel}
type="button"
@ -107,7 +100,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
}: {
showChooseGroupMembers: () => void;
}): undefined | (() => void) {
return this.isCreating ? undefined : showChooseGroupMembers;
return this.#isCreating ? undefined : showChooseGroupMembers;
}
override getPreRowsNode({
@ -134,7 +127,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
toggleComposeEditingAvatar: () => unknown;
}>): ReactChild {
const [avatarColor] = AvatarColors;
const disabled = this.isCreating;
const disabled = this.#isCreating;
return (
<form
@ -143,14 +136,14 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
event.preventDefault();
event.stopPropagation();
if (!this.canCreateGroup()) {
if (!this.#canCreateGroup()) {
return;
}
createGroup();
}}
>
{this.isEditingAvatar && (
{this.#isEditingAvatar && (
<Modal
modalName="LeftPaneSetGroupMetadataHelper.AvatarEditor"
hasXButton
@ -162,7 +155,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
>
<AvatarEditor
avatarColor={avatarColor}
avatarValue={this.groupAvatar}
avatarValue={this.#groupAvatar}
deleteAvatarFromDisk={composeDeleteAvatarFromDisk}
i18n={i18n}
isGroup
@ -171,7 +164,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
setComposeGroupAvatar(newAvatar);
toggleComposeEditingAvatar();
}}
userAvatarData={this.userAvatarData}
userAvatarData={this.#userAvatarData}
replaceAvatar={composeReplaceAvatar}
saveAvatarToDisk={composeSaveAvatarToDisk}
/>
@ -179,7 +172,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
)}
<AvatarPreview
avatarColor={avatarColor}
avatarValue={this.groupAvatar}
avatarValue={this.#groupAvatar}
i18n={i18n}
isEditable
isGroup
@ -196,7 +189,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
i18n={i18n}
onChangeValue={setComposeGroupName}
ref={focusRef}
value={this.groupName}
value={this.#groupName}
/>
</div>
@ -206,12 +199,12 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
</div>
<DisappearingTimerSelect
i18n={i18n}
value={this.groupExpireTimer}
value={this.#groupExpireTimer}
onChange={setComposeGroupExpireTimer}
/>
</section>
{this.hasError && (
{this.#hasError && (
<Alert
body={i18n('icu:setGroupMetadata__error-message')}
i18n={i18n}
@ -231,12 +224,12 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
}>): ReactChild {
return (
<Button
disabled={!this.canCreateGroup()}
disabled={!this.#canCreateGroup()}
onClick={() => {
createGroup();
}}
>
{this.isCreating ? (
{this.#isCreating ? (
<span aria-label={i18n('icu:loading')} role="status">
<Spinner size="20px" svgSize="small" direction="on-avatar" />
</span>
@ -248,14 +241,14 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
}
getRowCount(): number {
if (!this.selectedContacts.length) {
if (!this.#selectedContacts.length) {
return 0;
}
return this.selectedContacts.length + 2;
return this.#selectedContacts.length + 2;
}
getRow(rowIndex: number): undefined | Row {
if (!this.selectedContacts.length) {
if (!this.#selectedContacts.length) {
return undefined;
}
@ -267,11 +260,11 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
}
// This puts a blank row for the footer.
if (rowIndex === this.selectedContacts.length + 1) {
if (rowIndex === this.#selectedContacts.length + 1) {
return { type: RowType.Blank };
}
const contact = this.selectedContacts[rowIndex - 1];
const contact = this.#selectedContacts[rowIndex - 1];
return contact
? {
type: RowType.Contact,
@ -299,8 +292,8 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
return false;
}
private canCreateGroup(): boolean {
return !this.isCreating && Boolean(this.groupName.trim());
#canCreateGroup(): boolean {
return !this.#isCreating && Boolean(this.#groupName.trim());
}
}