165 lines
4.9 KiB
TypeScript
165 lines
4.9 KiB
TypeScript
import {
|
|
memo, useEffect, useLayoutEffect, useRef, useState,
|
|
} from '../../lib/teact/teact';
|
|
import { addExtraClass, removeExtraClass } from '../../lib/teact/teact-dom';
|
|
|
|
import type { ApiStory } from '../../api/types';
|
|
|
|
import { requestForcedReflow, requestMeasure, requestMutation } from '../../lib/fasterdom/fasterdom';
|
|
import buildClassName from '../../util/buildClassName';
|
|
import calcTextLineHeightAndCount from '../../util/element/calcTextLineHeightAndCount';
|
|
|
|
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
|
import useOldLang from '../../hooks/useOldLang';
|
|
import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
|
|
import useShowTransitionDeprecated from '../../hooks/useShowTransitionDeprecated';
|
|
|
|
import EmbeddedStoryForward from '../common/embedded/EmbeddedStoryForward';
|
|
import MessageText from '../common/MessageText';
|
|
|
|
import styles from './StoryViewer.module.scss';
|
|
|
|
interface OwnProps {
|
|
story: ApiStory;
|
|
isExpanded: boolean;
|
|
className?: string;
|
|
onExpand: NoneToVoidFunction;
|
|
onFold?: NoneToVoidFunction;
|
|
}
|
|
|
|
const EXPAND_ANIMATION_DURATION_MS = 400;
|
|
const LINES_TO_SHOW = 3;
|
|
|
|
function StoryCaption({
|
|
story, isExpanded, className, onExpand, onFold,
|
|
}: OwnProps) {
|
|
const lang = useOldLang();
|
|
const ref = useRef<HTMLDivElement>();
|
|
const contentRef = useRef<HTMLDivElement>();
|
|
const textRef = useRef<HTMLDivElement>();
|
|
const showMoreButtonRef = useRef<HTMLDivElement>();
|
|
const renderingStory = useCurrentOrPrev(story, true);
|
|
|
|
const caption = renderingStory?.content.text;
|
|
|
|
const [hasOverflow, setHasOverflow] = useState(false);
|
|
const prevIsExpanded = usePrevDuringAnimation(isExpanded || undefined, EXPAND_ANIMATION_DURATION_MS);
|
|
const isInExpandedState = isExpanded || prevIsExpanded;
|
|
|
|
useEffect(() => {
|
|
requestMutation(() => {
|
|
if (!contentRef.current) {
|
|
return;
|
|
}
|
|
|
|
if (isExpanded) {
|
|
addExtraClass(contentRef.current, styles.animate);
|
|
} else {
|
|
removeExtraClass(contentRef.current, styles.animate);
|
|
}
|
|
});
|
|
}, [isExpanded]);
|
|
|
|
const canExpand = hasOverflow && !isInExpandedState;
|
|
const { shouldRender: shouldRenderShowMore, transitionClassNames } = useShowTransitionDeprecated(
|
|
canExpand, undefined, true, 'slow', true,
|
|
);
|
|
|
|
// Setup gradient to clip caption before button
|
|
useLayoutEffect(() => {
|
|
requestMeasure(() => {
|
|
const container = contentRef.current;
|
|
const button = showMoreButtonRef.current;
|
|
if (!container || !button) {
|
|
return;
|
|
}
|
|
|
|
const { offsetWidth } = button;
|
|
|
|
requestMutation(() => {
|
|
container.style.setProperty('--expand-button-width', `${offsetWidth}px`);
|
|
});
|
|
});
|
|
}, [shouldRenderShowMore, lang]);
|
|
|
|
useLayoutEffect(() => {
|
|
requestForcedReflow(() => {
|
|
if (!contentRef.current || !textRef.current) {
|
|
return undefined;
|
|
}
|
|
|
|
const container = contentRef.current;
|
|
const textContainer = textRef.current;
|
|
|
|
const textOffsetTop = textContainer.offsetTop;
|
|
const { lineHeight, totalLines } = calcTextLineHeightAndCount(textContainer);
|
|
const isOverflowing = totalLines > LINES_TO_SHOW;
|
|
const overflowShift = textOffsetTop + lineHeight * LINES_TO_SHOW;
|
|
|
|
return () => {
|
|
if (isOverflowing) {
|
|
addExtraClass(container, styles.hasOverflow);
|
|
setHasOverflow(true);
|
|
}
|
|
|
|
container.style.setProperty('--_overflow-shift', `${overflowShift}px`);
|
|
};
|
|
});
|
|
}, [caption]);
|
|
|
|
useEffect(() => {
|
|
if (!isExpanded) {
|
|
ref.current?.scrollTo({ top: 0 });
|
|
}
|
|
}, [isExpanded]);
|
|
|
|
const fullClassName = buildClassName(
|
|
styles.captionContent,
|
|
isInExpandedState && styles.expanded,
|
|
shouldRenderShowMore && styles.withShowMore,
|
|
);
|
|
|
|
return (
|
|
<div className={buildClassName(styles.caption, className)}>
|
|
<div
|
|
ref={contentRef}
|
|
className={fullClassName}
|
|
role={canExpand ? 'button' : undefined}
|
|
onClick={canExpand ? onExpand : onFold}
|
|
>
|
|
<div
|
|
ref={ref}
|
|
className={buildClassName(styles.captionInner, 'allow-selection', 'custom-scroll')}
|
|
>
|
|
{renderingStory?.forwardInfo && (
|
|
<EmbeddedStoryForward
|
|
forwardInfo={renderingStory.forwardInfo}
|
|
className={styles.forwardInfo}
|
|
/>
|
|
)}
|
|
{renderingStory && caption && (
|
|
<div ref={textRef} className={styles.captionText}>
|
|
<MessageText
|
|
messageOrStory={renderingStory}
|
|
withTranslucentThumbs
|
|
forcePlayback
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
{shouldRenderShowMore && (
|
|
<div
|
|
ref={showMoreButtonRef}
|
|
className={buildClassName(styles.captionShowMore, transitionClassNames)}
|
|
onClick={onExpand}
|
|
>
|
|
{lang('Story.CaptionShowMore')}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default memo(StoryCaption);
|