Add useSizeObserver and replace most react-measure
This commit is contained in:
parent
7267391de4
commit
6c70cd450b
20 changed files with 539 additions and 421 deletions
|
@ -3,8 +3,6 @@
|
|||
|
||||
import { pick } from 'lodash';
|
||||
import React, { useCallback } from 'react';
|
||||
import type { MeasuredComponentProps } from 'react-measure';
|
||||
import Measure from 'react-measure';
|
||||
import type { ListRowProps } from 'react-virtualized';
|
||||
|
||||
import type { ConversationType } from '../state/ducks/conversations';
|
||||
|
@ -23,6 +21,7 @@ import { useRestoreFocus } from '../hooks/useRestoreFocus';
|
|||
import { ListView } from './ListView';
|
||||
import { ListTile } from './ListTile';
|
||||
import type { ShowToastAction } from '../state/ducks/toast';
|
||||
import { SizeObserver } from '../hooks/useSizeObserver';
|
||||
|
||||
type OwnProps = {
|
||||
i18n: LocalizerType;
|
||||
|
@ -180,33 +179,26 @@ export function AddUserToAnotherGroupModal({
|
|||
ref={inputRef}
|
||||
value={searchTerm}
|
||||
/>
|
||||
|
||||
<Measure bounds>
|
||||
{({ contentRect, measureRef }: MeasuredComponentProps) => {
|
||||
// Though `width` and `height` are required properties, we want to be
|
||||
// careful in case the caller sends bogus data. Notably, react-measure's
|
||||
// types seem to be inaccurate.
|
||||
const { width = 100, height = 100 } = contentRect.bounds || {};
|
||||
if (!width || !height) {
|
||||
return null;
|
||||
}
|
||||
|
||||
<SizeObserver>
|
||||
{(ref, size) => {
|
||||
return (
|
||||
<div
|
||||
className="AddUserToAnotherGroupModal__list-wrapper"
|
||||
ref={measureRef}
|
||||
ref={ref}
|
||||
>
|
||||
<ListView
|
||||
width={width}
|
||||
height={height}
|
||||
rowCount={filteredConversations.length}
|
||||
calculateRowHeight={handleCalculateRowHeight}
|
||||
rowRenderer={renderGroupListItem}
|
||||
/>
|
||||
{size != null && (
|
||||
<ListView
|
||||
width={size.width}
|
||||
height={size.height}
|
||||
rowCount={filteredConversations.length}
|
||||
calculateRowHeight={handleCalculateRowHeight}
|
||||
rowRenderer={renderGroupListItem}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Measure>
|
||||
</SizeObserver>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useState, useCallback, useRef } from 'react';
|
||||
import type { ContentRect } from 'react-measure';
|
||||
import Measure from 'react-measure';
|
||||
import { useComputePeaks } from '../hooks/useComputePeaks';
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import { WaveformScrubber } from './conversation/WaveformScrubber';
|
||||
import { PlaybackButton } from './PlaybackButton';
|
||||
import { RecordingComposer } from './RecordingComposer';
|
||||
import * as log from '../logging/log';
|
||||
import type { Size } from '../hooks/useSizeObserver';
|
||||
import { SizeObserver } from '../hooks/useSizeObserver';
|
||||
|
||||
type Props = {
|
||||
i18n: LocalizerType;
|
||||
|
@ -46,8 +46,8 @@ export function CompositionRecordingDraft({
|
|||
const timeout = useRef<undefined | NodeJS.Timeout>(undefined);
|
||||
|
||||
const handleResize = useCallback(
|
||||
({ bounds }: ContentRect) => {
|
||||
if (!bounds || bounds.width === state.width) {
|
||||
(size: Size) => {
|
||||
if (size.width === state.width) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ export function CompositionRecordingDraft({
|
|||
clearTimeout(timeout.current);
|
||||
}
|
||||
|
||||
const newWidth = bounds.width;
|
||||
const newWidth = size.width;
|
||||
|
||||
// if mounting, set width immediately
|
||||
// otherwise debounce
|
||||
|
@ -106,13 +106,13 @@ export function CompositionRecordingDraft({
|
|||
}
|
||||
onClick={handlePlaybackClick}
|
||||
/>
|
||||
<Measure bounds onResize={handleResize}>
|
||||
{({ measureRef }) => (
|
||||
<div ref={measureRef} className="CompositionRecordingDraft__sizer">
|
||||
<SizeObserver onSizeChange={handleResize}>
|
||||
{ref => (
|
||||
<div ref={ref} className="CompositionRecordingDraft__sizer">
|
||||
{scrubber}
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
</SizeObserver>
|
||||
</RecordingComposer>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -489,14 +489,11 @@ export function ConversationList({
|
|||
]
|
||||
);
|
||||
|
||||
// Though `width` and `height` are required properties, we want to be careful in case
|
||||
// the caller sends bogus data. Notably, react-measure's types seem to be inaccurate.
|
||||
const { width = 0, height = 0 } = dimensions || {};
|
||||
if (!width || !height) {
|
||||
if (dimensions == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const widthBreakpoint = getConversationListWidthBreakpoint(width);
|
||||
const widthBreakpoint = getConversationListWidthBreakpoint(dimensions.width);
|
||||
|
||||
return (
|
||||
<ListView
|
||||
|
@ -504,8 +501,8 @@ export function ConversationList({
|
|||
'module-conversation-list',
|
||||
`module-conversation-list--width-${widthBreakpoint}`
|
||||
)}
|
||||
width={width}
|
||||
height={height}
|
||||
width={dimensions.width}
|
||||
height={dimensions.height}
|
||||
rowCount={rowCount}
|
||||
calculateRowHeight={calculateRowHeight}
|
||||
rowRenderer={renderRow}
|
||||
|
|
|
@ -9,8 +9,6 @@ import React, {
|
|||
useState,
|
||||
Fragment,
|
||||
} from 'react';
|
||||
import type { MeasuredComponentProps } from 'react-measure';
|
||||
import Measure from 'react-measure';
|
||||
import { AttachmentList } from './conversation/AttachmentList';
|
||||
import type { AttachmentType } from '../types/Attachment';
|
||||
import { Button } from './Button';
|
||||
|
@ -42,6 +40,7 @@ import type { HydratedBodyRangesType } from '../types/BodyRange';
|
|||
import { BodyRange } from '../types/BodyRange';
|
||||
import { UserText } from './UserText';
|
||||
import { Modal } from './Modal';
|
||||
import { SizeObserver } from '../hooks/useSizeObserver';
|
||||
|
||||
export type DataPropsType = {
|
||||
candidateConversations: ReadonlyArray<ConversationType>;
|
||||
|
@ -334,14 +333,14 @@ export function ForwardMessagesModal({
|
|||
value={searchTerm}
|
||||
/>
|
||||
{candidateConversations.length ? (
|
||||
<Measure bounds>
|
||||
{({ contentRect, measureRef }: MeasuredComponentProps) => (
|
||||
<SizeObserver>
|
||||
{(ref, size) => (
|
||||
<div
|
||||
className="module-ForwardMessageModal__list-wrapper"
|
||||
ref={measureRef}
|
||||
ref={ref}
|
||||
>
|
||||
<ConversationList
|
||||
dimensions={contentRect.bounds}
|
||||
dimensions={size ?? undefined}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
getRow={getRow}
|
||||
i18n={i18n}
|
||||
|
@ -379,7 +378,7 @@ export function ForwardMessagesModal({
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
</SizeObserver>
|
||||
) : (
|
||||
<div className="module-ForwardMessageModal__no-candidate-contacts">
|
||||
{i18n('icu:noContactsFound')}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useCallback, useState, useMemo, useEffect } from 'react';
|
||||
import Measure from 'react-measure';
|
||||
import { takeWhile, clamp, chunk, maxBy, flatten, noop } from 'lodash';
|
||||
import type { VideoFrameSource } from '@signalapp/ringrtc';
|
||||
import { GroupCallRemoteParticipant } from './GroupCallRemoteParticipant';
|
||||
|
@ -25,6 +24,7 @@ import { filter, join } from '../util/iterables';
|
|||
import * as setUtil from '../util/setUtil';
|
||||
import * as log from '../logging/log';
|
||||
import { MAX_FRAME_HEIGHT, MAX_FRAME_WIDTH } from '../calling/constants';
|
||||
import { SizeObserver } from '../hooks/useSizeObserver';
|
||||
|
||||
const MIN_RENDERED_HEIGHT = 180;
|
||||
const PARTICIPANT_MARGIN = 10;
|
||||
|
@ -398,40 +398,27 @@ export function GroupCallRemoteParticipants({
|
|||
]);
|
||||
|
||||
return (
|
||||
<Measure
|
||||
bounds
|
||||
onResize={({ bounds }) => {
|
||||
if (!bounds) {
|
||||
log.error('We should be measuring the bounds');
|
||||
return;
|
||||
}
|
||||
setContainerDimensions(bounds);
|
||||
<SizeObserver
|
||||
onSizeChange={size => {
|
||||
setContainerDimensions(size);
|
||||
}}
|
||||
>
|
||||
{containerMeasure => (
|
||||
<div
|
||||
className="module-ongoing-call__participants"
|
||||
ref={containerMeasure.measureRef}
|
||||
>
|
||||
<Measure
|
||||
bounds
|
||||
onResize={({ bounds }) => {
|
||||
if (!bounds) {
|
||||
log.error('We should be measuring the bounds');
|
||||
return;
|
||||
}
|
||||
setGridDimensions(bounds);
|
||||
{containerRef => (
|
||||
<div className="module-ongoing-call__participants" ref={containerRef}>
|
||||
<SizeObserver
|
||||
onSizeChange={size => {
|
||||
setGridDimensions(size);
|
||||
}}
|
||||
>
|
||||
{gridMeasure => (
|
||||
{gridRef => (
|
||||
<div
|
||||
className="module-ongoing-call__participants__grid"
|
||||
ref={gridMeasure.measureRef}
|
||||
ref={gridRef}
|
||||
>
|
||||
{flatten(rowElements)}
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
</SizeObserver>
|
||||
|
||||
<GroupCallOverflowArea
|
||||
getFrameBuffer={getFrameBuffer}
|
||||
|
@ -444,7 +431,7 @@ export function GroupCallRemoteParticipants({
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
</SizeObserver>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useEffect, useCallback, useMemo, useState } from 'react';
|
||||
import type { MeasuredComponentProps } from 'react-measure';
|
||||
import Measure from 'react-measure';
|
||||
import classNames from 'classnames';
|
||||
import { clamp, isNumber, noop } from 'lodash';
|
||||
|
||||
|
@ -51,6 +49,7 @@ import type {
|
|||
ReplaceAvatarActionType,
|
||||
SaveAvatarToDiskActionType,
|
||||
} from '../types/Avatar';
|
||||
import { SizeObserver } from '../hooks/useSizeObserver';
|
||||
|
||||
export enum LeftPaneMode {
|
||||
Inbox,
|
||||
|
@ -652,9 +651,9 @@ export function LeftPane({
|
|||
))}
|
||||
</div>
|
||||
{preRowsNode && <React.Fragment key={0}>{preRowsNode}</React.Fragment>}
|
||||
<Measure bounds>
|
||||
{({ contentRect, measureRef }: MeasuredComponentProps) => (
|
||||
<div className="module-left-pane__list--measure" ref={measureRef}>
|
||||
<SizeObserver>
|
||||
{(ref, size) => (
|
||||
<div className="module-left-pane__list--measure" ref={ref}>
|
||||
<div className="module-left-pane__list--wrapper">
|
||||
<div
|
||||
aria-live="polite"
|
||||
|
@ -667,7 +666,7 @@ export function LeftPane({
|
|||
<ConversationList
|
||||
dimensions={{
|
||||
width,
|
||||
height: contentRect.bounds?.height || 0,
|
||||
height: size?.height || 0,
|
||||
}}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
getRow={getRow}
|
||||
|
@ -717,7 +716,7 @@ export function LeftPane({
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
</SizeObserver>
|
||||
{footerContents && (
|
||||
<div className="module-left-pane__footer">{footerContents}</div>
|
||||
)}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import Measure from 'react-measure';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
@ -47,6 +46,7 @@ import type { HydratedBodyRangesType } from '../types/BodyRange';
|
|||
import { MessageBody } from './conversation/MessageBody';
|
||||
import { RenderLocation } from './conversation/MessageTextRenderer';
|
||||
import { arrow } from '../util/keyboard';
|
||||
import { SizeObserver } from '../hooks/useSizeObserver';
|
||||
|
||||
export type MediaEditorResultType = Readonly<{
|
||||
data: Uint8Array;
|
||||
|
@ -911,19 +911,14 @@ export function MediaEditor({
|
|||
return createPortal(
|
||||
<div className="MediaEditor">
|
||||
<div className="MediaEditor__container">
|
||||
<Measure
|
||||
bounds
|
||||
onResize={({ bounds }) => {
|
||||
if (!bounds) {
|
||||
log.error('We should be measuring the bounds');
|
||||
return;
|
||||
}
|
||||
setContainerWidth(bounds.width);
|
||||
setContainerHeight(bounds.height);
|
||||
<SizeObserver
|
||||
onSizeChange={size => {
|
||||
setContainerWidth(size.width);
|
||||
setContainerHeight(size.height);
|
||||
}}
|
||||
>
|
||||
{({ measureRef }) => (
|
||||
<div className="MediaEditor__media" ref={measureRef}>
|
||||
{ref => (
|
||||
<div className="MediaEditor__media" ref={ref}>
|
||||
{image && (
|
||||
<div>
|
||||
<canvas
|
||||
|
@ -937,7 +932,7 @@ export function MediaEditor({
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
</SizeObserver>
|
||||
</div>
|
||||
<div className="MediaEditor__toolbar">
|
||||
{tooling ? (
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
import type { ReactElement, ReactNode } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import type { ContentRect, MeasuredComponentProps } from 'react-measure';
|
||||
import Measure from 'react-measure';
|
||||
import classNames from 'classnames';
|
||||
import { noop } from 'lodash';
|
||||
import { animated } from '@react-spring/web';
|
||||
|
@ -16,8 +14,12 @@ import { assertDev } from '../util/assert';
|
|||
import { getClassNamesFor } from '../util/getClassNamesFor';
|
||||
import { useAnimated } from '../hooks/useAnimated';
|
||||
import { useHasWrapped } from '../hooks/useHasWrapped';
|
||||
import { useRefMerger } from '../hooks/useRefMerger';
|
||||
import * as log from '../logging/log';
|
||||
import {
|
||||
isOverflowing,
|
||||
isScrolled,
|
||||
useScrollObserver,
|
||||
} from '../hooks/useSizeObserver';
|
||||
|
||||
type PropsType = {
|
||||
children: ReactNode;
|
||||
|
@ -169,24 +171,19 @@ export function ModalPage({
|
|||
}: ModalPageProps): JSX.Element {
|
||||
const modalRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const refMerger = useRefMerger();
|
||||
const bodyRef = useRef<HTMLDivElement>(null);
|
||||
const bodyInnerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const bodyRef = useRef<HTMLDivElement | null>(null);
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const [hasOverflow, setHasOverflow] = useState(false);
|
||||
|
||||
const hasHeader = Boolean(hasXButton || title || onBackButtonClick);
|
||||
const getClassName = getClassNamesFor(BASE_CLASS_NAME, moduleClassName);
|
||||
|
||||
function handleResize({ scroll }: ContentRect) {
|
||||
const modalNode = modalRef?.current;
|
||||
if (!modalNode) {
|
||||
return;
|
||||
}
|
||||
if (scroll) {
|
||||
setHasOverflow(scroll.height > modalNode.clientHeight);
|
||||
}
|
||||
}
|
||||
useScrollObserver(bodyRef, bodyInnerRef, scroll => {
|
||||
setScrolled(isScrolled(scroll));
|
||||
setHasOverflow(isOverflowing(scroll));
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -249,26 +246,16 @@ export function ModalPage({
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
<Measure scroll onResize={handleResize}>
|
||||
{({ measureRef }: MeasuredComponentProps) => (
|
||||
<div
|
||||
className={classNames(
|
||||
getClassName('__body'),
|
||||
scrolled ? getClassName('__body--scrolled') : null,
|
||||
hasOverflow || scrolled
|
||||
? getClassName('__body--overflow')
|
||||
: null
|
||||
)}
|
||||
onScroll={() => {
|
||||
const scrollTop = bodyRef.current?.scrollTop || 0;
|
||||
setScrolled(scrollTop > 2);
|
||||
}}
|
||||
ref={refMerger(measureRef, bodyRef)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
getClassName('__body'),
|
||||
scrolled ? getClassName('__body--scrolled') : null,
|
||||
hasOverflow || scrolled ? getClassName('__body--overflow') : null
|
||||
)}
|
||||
</Measure>
|
||||
ref={bodyRef}
|
||||
>
|
||||
<div ref={bodyInnerRef}>{children}</div>
|
||||
</div>
|
||||
{modalFooter && <Modal.ButtonFooter>{modalFooter}</Modal.ButtonFooter>}
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { MeasuredComponentProps } from 'react-measure';
|
||||
import type { ReactNode } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import Measure from 'react-measure';
|
||||
import { noop } from 'lodash';
|
||||
|
||||
import type { ConversationType } from '../state/ducks/conversations';
|
||||
|
@ -41,6 +39,7 @@ import { useConfirmDiscard } from '../hooks/useConfirmDiscard';
|
|||
import { getGroupMemberships } from '../util/getGroupMemberships';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { UserText } from './UserText';
|
||||
import { SizeObserver } from '../hooks/useSizeObserver';
|
||||
|
||||
export type PropsType = {
|
||||
candidateConversations: Array<ConversationType>;
|
||||
|
@ -1193,14 +1192,11 @@ export function EditDistributionListModal({
|
|||
</ContactPills>
|
||||
) : undefined}
|
||||
{candidateConversations.length ? (
|
||||
<Measure bounds>
|
||||
{({ contentRect, measureRef }: MeasuredComponentProps) => (
|
||||
<div
|
||||
className="StoriesSettingsModal__conversation-list"
|
||||
ref={measureRef}
|
||||
>
|
||||
<SizeObserver>
|
||||
{(ref, size) => (
|
||||
<div className="StoriesSettingsModal__conversation-list" ref={ref}>
|
||||
<ConversationList
|
||||
dimensions={contentRect.bounds}
|
||||
dimensions={size ?? undefined}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
getRow={getRow}
|
||||
i18n={i18n}
|
||||
|
@ -1228,7 +1224,7 @@ export function EditDistributionListModal({
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
</SizeObserver>
|
||||
) : (
|
||||
<div className="module-ForwardMessageModal__no-candidate-contacts">
|
||||
{i18n('icu:noContactsFound')}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import Measure from 'react-measure';
|
||||
import React, { forwardRef, useEffect, useRef, useState } from 'react';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import classNames from 'classnames';
|
||||
|
@ -22,6 +21,7 @@ import {
|
|||
} from '../util/getStoryBackground';
|
||||
import { SECOND } from '../util/durations';
|
||||
import { useRefMerger } from '../hooks/useRefMerger';
|
||||
import { useSizeObserver } from '../hooks/useSizeObserver';
|
||||
|
||||
const renderNewLines: RenderTextCallbackType = ({
|
||||
text: textWithNewLines,
|
||||
|
@ -169,152 +169,142 @@ export const TextAttachment = forwardRef<HTMLTextAreaElement, PropsType>(
|
|||
background: getBackgroundColor(textAttachment),
|
||||
};
|
||||
|
||||
return (
|
||||
<Measure bounds>
|
||||
{({ contentRect, measureRef }) => {
|
||||
const scaleFactor = (contentRect.bounds?.height || 1) / 1280;
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const size = useSizeObserver(ref);
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<div
|
||||
className="TextAttachment"
|
||||
onClick={() => {
|
||||
if (linkPreviewOffsetTop) {
|
||||
setLinkPreviewOffsetTop(undefined);
|
||||
}
|
||||
onClick?.();
|
||||
}}
|
||||
onKeyUp={ev => {
|
||||
if (ev.key === 'Escape' && linkPreviewOffsetTop) {
|
||||
setLinkPreviewOffsetTop(undefined);
|
||||
}
|
||||
}}
|
||||
ref={measureRef}
|
||||
style={isThumbnail ? storyBackgroundColor : undefined}
|
||||
>
|
||||
{/*
|
||||
const scaleFactor = (size?.height || 1) / 1280;
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<div
|
||||
className="TextAttachment"
|
||||
onClick={() => {
|
||||
if (linkPreviewOffsetTop) {
|
||||
setLinkPreviewOffsetTop(undefined);
|
||||
}
|
||||
onClick?.();
|
||||
}}
|
||||
onKeyUp={ev => {
|
||||
if (ev.key === 'Escape' && linkPreviewOffsetTop) {
|
||||
setLinkPreviewOffsetTop(undefined);
|
||||
}
|
||||
}}
|
||||
ref={ref}
|
||||
style={isThumbnail ? storyBackgroundColor : undefined}
|
||||
>
|
||||
{/*
|
||||
The tooltip must be outside of the scaled area, as it should not scale with
|
||||
the story, but it must be positioned using the scaled offset
|
||||
*/}
|
||||
{textAttachment.preview &&
|
||||
textAttachment.preview.url &&
|
||||
linkPreviewOffsetTop &&
|
||||
!isThumbnail && (
|
||||
<a
|
||||
className="TextAttachment__preview__tooltip"
|
||||
href={textAttachment.preview.url}
|
||||
rel="noreferrer"
|
||||
style={{
|
||||
top: linkPreviewOffsetTop * scaleFactor - 89, // minus height of tooltip and some spacing
|
||||
}}
|
||||
target="_blank"
|
||||
>
|
||||
<div>
|
||||
<div className="TextAttachment__preview__tooltip__title">
|
||||
{i18n('icu:TextAttachment__preview__link')}
|
||||
</div>
|
||||
<div className="TextAttachment__preview__tooltip__url">
|
||||
{textAttachment.preview.url}
|
||||
</div>
|
||||
</div>
|
||||
<div className="TextAttachment__preview__tooltip__arrow" />
|
||||
</a>
|
||||
)}
|
||||
<div
|
||||
className="TextAttachment__story"
|
||||
style={{
|
||||
...(isThumbnail ? {} : storyBackgroundColor),
|
||||
transform: `scale(${scaleFactor})`,
|
||||
}}
|
||||
>
|
||||
{(textContent || onChange) && (
|
||||
<div
|
||||
className={classNames('TextAttachment__text', {
|
||||
'TextAttachment__text--with-bg': Boolean(
|
||||
textAttachment.textBackgroundColor
|
||||
),
|
||||
})}
|
||||
style={{
|
||||
backgroundColor: textAttachment.textBackgroundColor
|
||||
? getHexFromNumber(textAttachment.textBackgroundColor)
|
||||
: 'transparent',
|
||||
}}
|
||||
>
|
||||
{onChange ? (
|
||||
<TextareaAutosize
|
||||
dir="auto"
|
||||
className="TextAttachment__text__container TextAttachment__text__textarea"
|
||||
disabled={!isEditingText}
|
||||
onChange={ev => onChange(ev.currentTarget.value)}
|
||||
placeholder={i18n('icu:TextAttachment__placeholder')}
|
||||
ref={refMerger(forwardedTextEditorRef, textEditorRef)}
|
||||
style={getTextStyles(
|
||||
textContent,
|
||||
textAttachment.textForegroundColor,
|
||||
textAttachment.textStyle,
|
||||
i18n
|
||||
)}
|
||||
value={textContent}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="TextAttachment__text__container"
|
||||
style={getTextStyles(
|
||||
textContent,
|
||||
textAttachment.textForegroundColor,
|
||||
textAttachment.textStyle,
|
||||
i18n
|
||||
)}
|
||||
>
|
||||
<Emojify
|
||||
text={textContent}
|
||||
renderNonEmoji={renderNewLines}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{textAttachment.preview && textAttachment.preview.url && (
|
||||
<div
|
||||
className={classNames('TextAttachment__preview-container', {
|
||||
'TextAttachment__preview-container--large': Boolean(
|
||||
textAttachment.preview.title
|
||||
),
|
||||
})}
|
||||
ref={linkPreview}
|
||||
onBlur={() => setIsHoveringOverTooltip(false)}
|
||||
onFocus={showTooltip}
|
||||
onMouseOut={() => setIsHoveringOverTooltip(false)}
|
||||
onMouseOver={showTooltip}
|
||||
>
|
||||
{onRemoveLinkPreview && (
|
||||
<div className="TextAttachment__preview__remove">
|
||||
<button
|
||||
aria-label={i18n(
|
||||
'icu:Keyboard--remove-draft-link-preview'
|
||||
)}
|
||||
type="button"
|
||||
onClick={onRemoveLinkPreview}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<StoryLinkPreview
|
||||
{...textAttachment.preview}
|
||||
domain={getDomain(String(textAttachment.preview.url))}
|
||||
forceCompactMode={
|
||||
getTextSize(textContent) !== TextSize.Large
|
||||
}
|
||||
i18n={i18n}
|
||||
title={textAttachment.preview.title || undefined}
|
||||
url={textAttachment.preview.url}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{textAttachment.preview &&
|
||||
textAttachment.preview.url &&
|
||||
linkPreviewOffsetTop &&
|
||||
!isThumbnail && (
|
||||
<a
|
||||
className="TextAttachment__preview__tooltip"
|
||||
href={textAttachment.preview.url}
|
||||
rel="noreferrer"
|
||||
style={{
|
||||
top: linkPreviewOffsetTop * scaleFactor - 89, // minus height of tooltip and some spacing
|
||||
}}
|
||||
target="_blank"
|
||||
>
|
||||
<div>
|
||||
<div className="TextAttachment__preview__tooltip__title">
|
||||
{i18n('icu:TextAttachment__preview__link')}
|
||||
</div>
|
||||
<div className="TextAttachment__preview__tooltip__url">
|
||||
{textAttachment.preview.url}
|
||||
</div>
|
||||
</div>
|
||||
<div className="TextAttachment__preview__tooltip__arrow" />
|
||||
</a>
|
||||
)}
|
||||
<div
|
||||
className="TextAttachment__story"
|
||||
style={{
|
||||
...(isThumbnail ? {} : storyBackgroundColor),
|
||||
transform: `scale(${scaleFactor})`,
|
||||
}}
|
||||
>
|
||||
{(textContent || onChange) && (
|
||||
<div
|
||||
className={classNames('TextAttachment__text', {
|
||||
'TextAttachment__text--with-bg': Boolean(
|
||||
textAttachment.textBackgroundColor
|
||||
),
|
||||
})}
|
||||
style={{
|
||||
backgroundColor: textAttachment.textBackgroundColor
|
||||
? getHexFromNumber(textAttachment.textBackgroundColor)
|
||||
: 'transparent',
|
||||
}}
|
||||
>
|
||||
{onChange ? (
|
||||
<TextareaAutosize
|
||||
dir="auto"
|
||||
className="TextAttachment__text__container TextAttachment__text__textarea"
|
||||
disabled={!isEditingText}
|
||||
onChange={ev => onChange(ev.currentTarget.value)}
|
||||
placeholder={i18n('icu:TextAttachment__placeholder')}
|
||||
ref={refMerger(forwardedTextEditorRef, textEditorRef)}
|
||||
style={getTextStyles(
|
||||
textContent,
|
||||
textAttachment.textForegroundColor,
|
||||
textAttachment.textStyle,
|
||||
i18n
|
||||
)}
|
||||
value={textContent}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="TextAttachment__text__container"
|
||||
style={getTextStyles(
|
||||
textContent,
|
||||
textAttachment.textForegroundColor,
|
||||
textAttachment.textStyle,
|
||||
i18n
|
||||
)}
|
||||
>
|
||||
<Emojify text={textContent} renderNonEmoji={renderNewLines} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Measure>
|
||||
)}
|
||||
{textAttachment.preview && textAttachment.preview.url && (
|
||||
<div
|
||||
className={classNames('TextAttachment__preview-container', {
|
||||
'TextAttachment__preview-container--large': Boolean(
|
||||
textAttachment.preview.title
|
||||
),
|
||||
})}
|
||||
ref={linkPreview}
|
||||
onBlur={() => setIsHoveringOverTooltip(false)}
|
||||
onFocus={showTooltip}
|
||||
onMouseOut={() => setIsHoveringOverTooltip(false)}
|
||||
onMouseOver={showTooltip}
|
||||
>
|
||||
{onRemoveLinkPreview && (
|
||||
<div className="TextAttachment__preview__remove">
|
||||
<button
|
||||
aria-label={i18n('icu:Keyboard--remove-draft-link-preview')}
|
||||
type="button"
|
||||
onClick={onRemoveLinkPreview}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<StoryLinkPreview
|
||||
{...textAttachment.preview}
|
||||
domain={getDomain(String(textAttachment.preview.url))}
|
||||
forceCompactMode={getTextSize(textContent) !== TextSize.Large}
|
||||
i18n={i18n}
|
||||
title={textAttachment.preview.title || undefined}
|
||||
url={textAttachment.preview.url}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
import type { ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
import Measure from 'react-measure';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
ContextMenu,
|
||||
|
@ -40,6 +39,7 @@ import {
|
|||
import { PanelType } from '../../types/Panels';
|
||||
import { UserText } from '../UserText';
|
||||
import { Alert } from '../Alert';
|
||||
import { SizeObserver } from '../../hooks/useSizeObserver';
|
||||
|
||||
export enum OutgoingCallButtonStyle {
|
||||
None,
|
||||
|
@ -783,16 +783,12 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
|||
{this.renderDeleteMessagesConfirmationDialog()}
|
||||
{this.renderLeaveGroupConfirmationDialog()}
|
||||
{this.renderCannotLeaveGroupBecauseYouAreLastAdminAlert()}
|
||||
<Measure
|
||||
bounds
|
||||
onResize={({ bounds }) => {
|
||||
if (!bounds || !bounds.width) {
|
||||
return;
|
||||
}
|
||||
this.setState({ isNarrow: bounds.width < 500 });
|
||||
<SizeObserver
|
||||
onSizeChange={size => {
|
||||
this.setState({ isNarrow: size.width < 500 });
|
||||
}}
|
||||
>
|
||||
{({ measureRef }) => (
|
||||
{measureRef => (
|
||||
<div
|
||||
className={classNames('module-ConversationHeader', {
|
||||
'module-ConversationHeader--narrow': isNarrow,
|
||||
|
@ -821,7 +817,7 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
|||
{this.renderMenu(triggerId)}
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
</SizeObserver>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
import type { ReactChild } from 'react';
|
||||
import React, { forwardRef, useCallback, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import type { ContentRect } from 'react-measure';
|
||||
import Measure from 'react-measure';
|
||||
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import type { DirectionType, MessageStatusType } from './Message';
|
||||
|
@ -17,6 +15,8 @@ import { PanelType } from '../../types/Panels';
|
|||
import { Spinner } from '../Spinner';
|
||||
import { ConfirmationDialog } from '../ConfirmationDialog';
|
||||
import { refMerger } from '../../util/refMerger';
|
||||
import type { Size } from '../../hooks/useSizeObserver';
|
||||
import { SizeObserver } from '../../hooks/useSizeObserver';
|
||||
|
||||
type PropsType = {
|
||||
deletedForEveryone?: boolean;
|
||||
|
@ -254,21 +254,21 @@ export const MessageMetadata = forwardRef<HTMLDivElement, Readonly<PropsType>>(
|
|||
);
|
||||
|
||||
const onResize = useCallback(
|
||||
({ bounds }: ContentRect) => {
|
||||
onWidthMeasured?.(bounds?.width || 0);
|
||||
(size: Size) => {
|
||||
onWidthMeasured?.(size.width);
|
||||
},
|
||||
[onWidthMeasured]
|
||||
);
|
||||
|
||||
if (onWidthMeasured) {
|
||||
return (
|
||||
<Measure bounds onResize={onResize}>
|
||||
{({ measureRef }) => (
|
||||
<SizeObserver onSizeChange={onResize}>
|
||||
{measureRef => (
|
||||
<div className={className} ref={refMerger(measureRef, ref)}>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
</SizeObserver>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import { first, get, isNumber, last, throttle } from 'lodash';
|
|||
import classNames from 'classnames';
|
||||
import type { ReactChild, ReactNode, RefObject } from 'react';
|
||||
import React from 'react';
|
||||
import Measure from 'react-measure';
|
||||
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
import { ScrollDownButton, ScrollDownButtonVariant } from './ScrollDownButton';
|
||||
|
@ -43,6 +42,7 @@ import {
|
|||
} from '../../util/scrollUtil';
|
||||
import { LastSeenIndicator } from './LastSeenIndicator';
|
||||
import { MINUTE } from '../../util/durations';
|
||||
import { SizeObserver } from '../../hooks/useSizeObserver';
|
||||
|
||||
const AT_BOTTOM_THRESHOLD = 15;
|
||||
const AT_BOTTOM_DETECTOR_STYLE = { height: AT_BOTTOM_THRESHOLD };
|
||||
|
@ -204,7 +204,6 @@ export class Timeline extends React.Component<
|
|||
private readonly atBottomDetectorRef = React.createRef<HTMLDivElement>();
|
||||
private readonly lastSeenIndicatorRef = React.createRef<HTMLDivElement>();
|
||||
private intersectionObserver?: IntersectionObserver;
|
||||
private intersectionObserverCallbackFrame?: number;
|
||||
|
||||
// This is a best guess. It will likely be overridden when the timeline is measured.
|
||||
private maxVisibleRows = Math.ceil(window.innerHeight / MIN_ROW_HEIGHT);
|
||||
|
@ -340,10 +339,6 @@ export class Timeline extends React.Component<
|
|||
// this another way, but this approach works.)
|
||||
this.intersectionObserver?.disconnect();
|
||||
|
||||
if (this.intersectionObserverCallbackFrame !== undefined) {
|
||||
window.cancelAnimationFrame(this.intersectionObserverCallbackFrame);
|
||||
}
|
||||
|
||||
const intersectionRatios = new Map<Element, number>();
|
||||
|
||||
const intersectionObserverCallback: IntersectionObserverCallback =
|
||||
|
@ -445,19 +440,12 @@ export class Timeline extends React.Component<
|
|||
'observer.disconnect() should prevent callbacks from firing'
|
||||
);
|
||||
|
||||
// `react-measure` schedules the callbacks on the next tick and so
|
||||
// should we because we want other parts of this component to respond
|
||||
// to resize events before we recalculate what is visible.
|
||||
this.intersectionObserverCallbackFrame = window.requestAnimationFrame(
|
||||
() => {
|
||||
// Observer was updated from under us
|
||||
if (this.intersectionObserver !== observer) {
|
||||
return;
|
||||
}
|
||||
// Observer was updated from under us
|
||||
if (this.intersectionObserver !== observer) {
|
||||
return;
|
||||
}
|
||||
|
||||
intersectionObserverCallback(entries, observer);
|
||||
}
|
||||
);
|
||||
intersectionObserverCallback(entries, observer);
|
||||
},
|
||||
{
|
||||
root: containerEl,
|
||||
|
@ -1002,17 +990,12 @@ export class Timeline extends React.Component<
|
|||
}
|
||||
|
||||
headerElements = (
|
||||
<Measure
|
||||
bounds
|
||||
onResize={({ bounds }) => {
|
||||
if (!bounds) {
|
||||
assertDev(false, 'We should be measuring the bounds');
|
||||
return;
|
||||
}
|
||||
this.setState({ lastMeasuredWarningHeight: bounds.height });
|
||||
<SizeObserver
|
||||
onSizeChange={size => {
|
||||
this.setState({ lastMeasuredWarningHeight: size.height });
|
||||
}}
|
||||
>
|
||||
{({ measureRef }) => (
|
||||
{measureRef => (
|
||||
<TimelineWarnings ref={measureRef}>
|
||||
{renderMiniPlayer({ shouldFlow: true })}
|
||||
{text && (
|
||||
|
@ -1025,7 +1008,7 @@ export class Timeline extends React.Component<
|
|||
)}
|
||||
</TimelineWarnings>
|
||||
)}
|
||||
</Measure>
|
||||
</SizeObserver>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1061,18 +1044,15 @@ export class Timeline extends React.Component<
|
|||
|
||||
return (
|
||||
<>
|
||||
<Measure
|
||||
bounds
|
||||
onResize={({ bounds }) => {
|
||||
<SizeObserver
|
||||
onSizeChange={size => {
|
||||
const { isNearBottom } = this.props;
|
||||
|
||||
strictAssert(bounds, 'We should be measuring the bounds');
|
||||
|
||||
this.setState({
|
||||
widthBreakpoint: getWidthBreakpoint(bounds.width),
|
||||
widthBreakpoint: getWidthBreakpoint(size.width),
|
||||
});
|
||||
|
||||
this.maxVisibleRows = Math.ceil(bounds.height / MIN_ROW_HEIGHT);
|
||||
this.maxVisibleRows = Math.ceil(size.height / MIN_ROW_HEIGHT);
|
||||
|
||||
const containerEl = this.containerRef.current;
|
||||
if (containerEl && isNearBottom) {
|
||||
|
@ -1080,7 +1060,7 @@ export class Timeline extends React.Component<
|
|||
}
|
||||
}}
|
||||
>
|
||||
{({ measureRef }) => (
|
||||
{ref => (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-timeline',
|
||||
|
@ -1091,7 +1071,7 @@ export class Timeline extends React.Component<
|
|||
tabIndex={-1}
|
||||
onBlur={this.handleBlur}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
ref={measureRef}
|
||||
ref={ref}
|
||||
>
|
||||
{headerElements}
|
||||
|
||||
|
@ -1152,7 +1132,7 @@ export class Timeline extends React.Component<
|
|||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
</SizeObserver>
|
||||
|
||||
{Boolean(invitedContactsForNewlyCreatedGroup.length) && (
|
||||
<NewlyCreatedGroupInvitedContactsDialog
|
||||
|
|
|
@ -9,8 +9,6 @@ import React, {
|
|||
useCallback,
|
||||
} from 'react';
|
||||
import { omit } from 'lodash';
|
||||
import type { MeasuredComponentProps } from 'react-measure';
|
||||
import Measure from 'react-measure';
|
||||
import type { ListRowProps } from 'react-virtualized';
|
||||
|
||||
import type { LocalizerType, ThemeType } from '../../../../types/Util';
|
||||
|
@ -47,6 +45,7 @@ import { SearchInput } from '../../../SearchInput';
|
|||
import { ListView } from '../../../ListView';
|
||||
import { UsernameCheckbox } from '../../../conversationList/UsernameCheckbox';
|
||||
import { PhoneNumberCheckbox } from '../../../conversationList/PhoneNumberCheckbox';
|
||||
import { SizeObserver } from '../../../../hooks/useSizeObserver';
|
||||
|
||||
export type StatePropsType = {
|
||||
regionCode: string | undefined;
|
||||
|
@ -432,16 +431,8 @@ export function ChooseGroupMembersModal({
|
|||
</ContactPills>
|
||||
)}
|
||||
{rowCount ? (
|
||||
<Measure bounds>
|
||||
{({ contentRect, measureRef }: MeasuredComponentProps) => {
|
||||
// Though `width` and `height` are required properties, we want to be
|
||||
// careful in case the caller sends bogus data. Notably, react-measure's
|
||||
// types seem to be inaccurate.
|
||||
const { width = 100, height = 100 } = contentRect.bounds || {};
|
||||
if (!width || !height) {
|
||||
return null;
|
||||
}
|
||||
|
||||
<SizeObserver>
|
||||
{(ref, size) => {
|
||||
// We disable this ESLint rule because we're capturing a bubbled keydown
|
||||
// event. See [this note in the jsx-a11y docs][0].
|
||||
//
|
||||
|
@ -450,38 +441,40 @@ export function ChooseGroupMembersModal({
|
|||
return (
|
||||
<div
|
||||
className="module-AddGroupMembersModal__list-wrapper"
|
||||
ref={measureRef}
|
||||
ref={ref}
|
||||
onKeyDown={event => {
|
||||
if (event.key === 'Enter') {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListView
|
||||
width={width}
|
||||
height={height}
|
||||
rowCount={rowCount}
|
||||
calculateRowHeight={index => {
|
||||
const row = getRow(index);
|
||||
if (!row) {
|
||||
assertDev(false, `Expected a row at index ${index}`);
|
||||
return 52;
|
||||
}
|
||||
|
||||
switch (row.type) {
|
||||
case RowType.Header:
|
||||
return 40;
|
||||
default:
|
||||
{size != null && (
|
||||
<ListView
|
||||
width={size.width}
|
||||
height={size.height}
|
||||
rowCount={rowCount}
|
||||
calculateRowHeight={index => {
|
||||
const row = getRow(index);
|
||||
if (!row) {
|
||||
assertDev(false, `Expected a row at index ${index}`);
|
||||
return 52;
|
||||
}
|
||||
}}
|
||||
rowRenderer={renderItem}
|
||||
/>
|
||||
}
|
||||
|
||||
switch (row.type) {
|
||||
case RowType.Header:
|
||||
return 40;
|
||||
default:
|
||||
return 52;
|
||||
}
|
||||
}}
|
||||
rowRenderer={renderItem}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
/* eslint-enable jsx-a11y/no-static-element-interactions */
|
||||
}}
|
||||
</Measure>
|
||||
</SizeObserver>
|
||||
) : (
|
||||
<div className="module-AddGroupMembersModal__no-candidate-contacts">
|
||||
{i18n('icu:noContactsFound')}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue