TelegramPWA/src/components/story/mediaArea/MediaAreaOverlay.tsx
Alexander Zinchuk af3f767dcc Stories: Implement Weather Widget (#5027)
Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
2024-10-20 18:53:16 +02:00

156 lines
4.4 KiB
TypeScript

import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import type { ApiMediaArea, ApiStory } from '../../../api/types';
import { MOBILE_SCREEN_MAX_WIDTH } from '../../../config';
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
import buildClassName from '../../../util/buildClassName';
import buildStyle from '../../../util/buildStyle';
import useWindowSize from '../../../hooks/window/useWindowSize';
import MediaAreaSuggestedReaction from './MediaAreaSuggestedReaction';
import MediaAreaWeather from './MediaAreaWeather';
import styles from './MediaArea.module.scss';
type OwnProps = {
story: ApiStory;
isActive?: boolean;
className?: string;
};
const STORY_ASPECT_RATIO = 9 / 16;
const MediaAreaOverlay = ({
story, isActive, className,
}: OwnProps) => {
const {
openMapModal, focusMessage, closeStoryViewer, openUrl,
} = getActions();
// eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLDivElement>(null);
const windowSize = useWindowSize();
useEffect(() => {
if (!ref.current || !isActive) return;
const element = ref.current;
if (windowSize.width > MOBILE_SCREEN_MAX_WIDTH) {
requestMutation(() => {
element.style.removeProperty('--media-width');
element.style.removeProperty('--media-height');
});
return;
}
const screenAspectRatio = windowSize.width / windowSize.height;
const width = screenAspectRatio < STORY_ASPECT_RATIO
? element.clientHeight * STORY_ASPECT_RATIO : element.clientWidth;
const height = screenAspectRatio < STORY_ASPECT_RATIO
? element.clientHeight : element.clientWidth / STORY_ASPECT_RATIO;
requestMutation(() => {
element.style.setProperty('--media-width', `${width}px`);
element.style.setProperty('--media-height', `${height}px`);
});
}, [isActive, windowSize]);
const handleMediaAreaClick = (mediaArea: ApiMediaArea) => {
switch (mediaArea.type) {
case 'geoPoint':
case 'venue': {
openMapModal({ geoPoint: mediaArea.geo });
break;
}
case 'channelPost': {
focusMessage({
chatId: mediaArea.channelId,
messageId: mediaArea.messageId,
});
closeStoryViewer();
break;
}
case 'url': {
openUrl({ url: mediaArea.url });
break;
}
}
};
const mediaAreas = story.mediaAreas;
return (
<div
className={buildClassName(styles.overlay, !isActive && styles.preview, className)}
ref={ref}
>
{mediaAreas?.map((mediaArea, i) => {
switch (mediaArea.type) {
case 'geoPoint':
case 'venue':
case 'channelPost':
case 'url': {
const isShiny = isActive && (mediaArea.type !== 'channelPost');
return (
<div
className={buildClassName(styles.mediaArea, isShiny && styles.shiny)}
style={prepareStyle(mediaArea)}
onClick={() => handleMediaAreaClick(mediaArea)}
/>
);
}
case 'suggestedReaction':
return (
<MediaAreaSuggestedReaction
// eslint-disable-next-line react/no-array-index-key
key={`${mediaArea.type}-${i}`}
story={story}
mediaArea={mediaArea}
index={i}
isPreview={!isActive}
className={styles.mediaArea}
style={prepareStyle(mediaArea)}
/>
);
case 'weather': {
return (
<MediaAreaWeather
// eslint-disable-next-line react/no-array-index-key
key={`${mediaArea.type}-${i}`}
mediaArea={mediaArea}
className={styles.mediaArea}
style={prepareStyle(mediaArea)}
isPreview={isActive}
/>
);
}
default:
return undefined;
}
})}
</div>
);
};
function prepareStyle(mediaArea: ApiMediaArea) {
const {
x, y, width, height, rotation, radius,
} = mediaArea.coordinates;
return buildStyle(
`left: ${x}%`,
`top: ${y}%`,
`width: ${width}%`,
`height: ${height}%`,
`transform: rotate(${rotation}deg) translate(-50%, -50%)`,
Boolean(radius) && `border-radius: ${radius}%`,
);
}
export default memo(MediaAreaOverlay);