diff --git a/app/javascript/mastodon/blurhash.ts b/app/javascript/mastodon/blurhash.ts
index cafe7b12dc..a1d1a0f4e1 100644
--- a/app/javascript/mastodon/blurhash.ts
+++ b/app/javascript/mastodon/blurhash.ts
@@ -96,13 +96,19 @@ export const decode83 = (str: string) => {
return value;
};
-export const intToRGB = (int: number) => ({
+export interface RGB {
+ r: number;
+ g: number;
+ b: number;
+}
+
+export const intToRGB = (int: number): RGB => ({
r: Math.max(0, int >> 16),
g: Math.max(0, (int >> 8) & 255),
b: Math.max(0, int & 255),
});
-export const getAverageFromBlurhash = (blurhash: string) => {
+export const getAverageFromBlurhash = (blurhash: string | null) => {
if (!blurhash) {
return null;
}
diff --git a/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx b/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx
deleted file mode 100644
index 50f91a9275..0000000000
--- a/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import PropTypes from 'prop-types';
-import { PureComponent } from 'react';
-
-import { FormattedMessage } from 'react-intl';
-
-import { connect } from 'react-redux';
-
-import CancelPresentationIcon from '@/material-icons/400-24px/cancel_presentation.svg?react';
-import { removePictureInPicture } from 'mastodon/actions/picture_in_picture';
-import { Icon } from 'mastodon/components/icon';
-
-class PictureInPicturePlaceholder extends PureComponent {
-
- static propTypes = {
- dispatch: PropTypes.func.isRequired,
- aspectRatio: PropTypes.string,
- };
-
- handleClick = () => {
- const { dispatch } = this.props;
- dispatch(removePictureInPicture());
- };
-
- render () {
- const { aspectRatio } = this.props;
-
- return (
-
-
-
-
- );
- }
-
-}
-
-export default connect()(PictureInPicturePlaceholder);
diff --git a/app/javascript/mastodon/components/picture_in_picture_placeholder.tsx b/app/javascript/mastodon/components/picture_in_picture_placeholder.tsx
new file mode 100644
index 0000000000..829ab5febd
--- /dev/null
+++ b/app/javascript/mastodon/components/picture_in_picture_placeholder.tsx
@@ -0,0 +1,46 @@
+import { useCallback } from 'react';
+
+import { FormattedMessage } from 'react-intl';
+
+import PipExitIcon from '@/material-icons/400-24px/pip_exit.svg?react';
+import { removePictureInPicture } from 'mastodon/actions/picture_in_picture';
+import { Icon } from 'mastodon/components/icon';
+import { useAppDispatch } from 'mastodon/store';
+
+export const PictureInPicturePlaceholder: React.FC<{ aspectRatio: string }> = ({
+ aspectRatio,
+}) => {
+ const dispatch = useAppDispatch();
+
+ const handleClick = useCallback(() => {
+ dispatch(removePictureInPicture());
+ }, [dispatch]);
+
+ const handleKeyDown = useCallback(
+ (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ e.stopPropagation();
+ handleClick();
+ }
+ },
+ [handleClick],
+ );
+
+ return (
+
+
+
+
+ );
+};
diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx
index c45942dde4..21d596a58c 100644
--- a/app/javascript/mastodon/components/status.jsx
+++ b/app/javascript/mastodon/components/status.jsx
@@ -17,7 +17,7 @@ import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
import { ContentWarning } from 'mastodon/components/content_warning';
import { FilterWarning } from 'mastodon/components/filter_warning';
import { Icon } from 'mastodon/components/icon';
-import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
+import { PictureInPicturePlaceholder } from 'mastodon/components/picture_in_picture_placeholder';
import { withOptionalRouter, WithOptionalRouterPropTypes } from 'mastodon/utils/react_router';
import Card from '../features/status/components/card';
@@ -484,9 +484,6 @@ class Status extends ImmutablePureComponent {
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
- width={this.props.cachedMediaWidth}
- height={110}
- cacheWidth={this.props.cacheMediaWidth}
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
sensitive={status.get('sensitive')}
blurhash={attachment.get('blurhash')}
diff --git a/app/javascript/mastodon/containers/media_container.jsx b/app/javascript/mastodon/containers/media_container.jsx
index 9c07341faa..e826dbfa96 100644
--- a/app/javascript/mastodon/containers/media_container.jsx
+++ b/app/javascript/mastodon/containers/media_container.jsx
@@ -8,7 +8,7 @@ import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
import MediaGallery from 'mastodon/components/media_gallery';
import ModalRoot from 'mastodon/components/modal_root';
import { Poll } from 'mastodon/components/poll';
-import Audio from 'mastodon/features/audio';
+import { Audio } from 'mastodon/features/audio';
import Card from 'mastodon/features/status/components/card';
import MediaModal from 'mastodon/features/ui/components/media_modal';
import { Video } from 'mastodon/features/video';
diff --git a/app/javascript/mastodon/features/alt_text_modal/index.tsx b/app/javascript/mastodon/features/alt_text_modal/index.tsx
index e2d05a99ca..08e4a8917c 100644
--- a/app/javascript/mastodon/features/alt_text_modal/index.tsx
+++ b/app/javascript/mastodon/features/alt_text_modal/index.tsx
@@ -27,7 +27,7 @@ import { Button } from 'mastodon/components/button';
import { GIFV } from 'mastodon/components/gifv';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { Skeleton } from 'mastodon/components/skeleton';
-import Audio from 'mastodon/features/audio';
+import { Audio } from 'mastodon/features/audio';
import { CharacterCounter } from 'mastodon/features/compose/components/character_counter';
import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
import { Video, getPointerPosition } from 'mastodon/features/video';
@@ -212,11 +212,11 @@ const Preview: React.FC<{
return (