Rewrite PIP state in Typescript (#27645)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
		
							parent
							
								
									b016f03637
								
							
						
					
					
						commit
						9fbe8d3a0c
					
				@ -1,46 +0,0 @@
 | 
				
			|||||||
// @ts-check
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const PICTURE_IN_PICTURE_DEPLOY = 'PICTURE_IN_PICTURE_DEPLOY';
 | 
					 | 
				
			||||||
export const PICTURE_IN_PICTURE_REMOVE = 'PICTURE_IN_PICTURE_REMOVE';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @typedef MediaProps
 | 
					 | 
				
			||||||
 * @property {string} src
 | 
					 | 
				
			||||||
 * @property {boolean} muted
 | 
					 | 
				
			||||||
 * @property {number} volume
 | 
					 | 
				
			||||||
 * @property {number} currentTime
 | 
					 | 
				
			||||||
 * @property {string} poster
 | 
					 | 
				
			||||||
 * @property {string} backgroundColor
 | 
					 | 
				
			||||||
 * @property {string} foregroundColor
 | 
					 | 
				
			||||||
 * @property {string} accentColor
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @param {string} statusId
 | 
					 | 
				
			||||||
 * @param {string} accountId
 | 
					 | 
				
			||||||
 * @param {string} playerType
 | 
					 | 
				
			||||||
 * @param {MediaProps} props
 | 
					 | 
				
			||||||
 * @returns {object}
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const deployPictureInPicture = (statusId, accountId, playerType, props) => {
 | 
					 | 
				
			||||||
  // @ts-expect-error
 | 
					 | 
				
			||||||
  return (dispatch, getState) => {
 | 
					 | 
				
			||||||
    // Do not open a player for a toot that does not exist
 | 
					 | 
				
			||||||
    if (getState().hasIn(['statuses', statusId])) {
 | 
					 | 
				
			||||||
      dispatch({
 | 
					 | 
				
			||||||
        type: PICTURE_IN_PICTURE_DEPLOY,
 | 
					 | 
				
			||||||
        statusId,
 | 
					 | 
				
			||||||
        accountId,
 | 
					 | 
				
			||||||
        playerType,
 | 
					 | 
				
			||||||
        props,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * @return {object}
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const removePictureInPicture = () => ({
 | 
					 | 
				
			||||||
  type: PICTURE_IN_PICTURE_REMOVE,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
							
								
								
									
										31
									
								
								app/javascript/mastodon/actions/picture_in_picture.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								app/javascript/mastodon/actions/picture_in_picture.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					import { createAction } from '@reduxjs/toolkit';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { PIPMediaProps } from 'mastodon/reducers/picture_in_picture';
 | 
				
			||||||
 | 
					import { createAppAsyncThunk } from 'mastodon/store/typed_functions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface DeployParams {
 | 
				
			||||||
 | 
					  statusId: string;
 | 
				
			||||||
 | 
					  accountId: string;
 | 
				
			||||||
 | 
					  playerType: 'audio' | 'video';
 | 
				
			||||||
 | 
					  props: PIPMediaProps;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const removePictureInPicture = createAction('pip/remove');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const deployPictureInPictureAction =
 | 
				
			||||||
 | 
					  createAction<DeployParams>('pip/deploy');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const deployPictureInPicture = createAppAsyncThunk(
 | 
				
			||||||
 | 
					  'pip/deploy',
 | 
				
			||||||
 | 
					  (args: DeployParams, { dispatch, getState }) => {
 | 
				
			||||||
 | 
					    const { statusId } = args;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Do not open a player for a toot that does not exist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // @ts-expect-error state.statuses is not yet typed
 | 
				
			||||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unsafe-call
 | 
				
			||||||
 | 
					    if (getState().hasIn(['statuses', statusId])) {
 | 
				
			||||||
 | 
					      dispatch(deployPictureInPictureAction(args));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
@ -262,7 +262,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  deployPictureInPicture (status, type, mediaProps) {
 | 
					  deployPictureInPicture (status, type, mediaProps) {
 | 
				
			||||||
    dispatch(deployPictureInPicture(status.get('id'), status.getIn(['account', 'id']), type, mediaProps));
 | 
					    dispatch(deployPictureInPicture({statusId: status.get('id'), accountId: status.getIn(['account', 'id']), playerType: type, props: mediaProps}));
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onInteractionModal (type, status) {
 | 
					  onInteractionModal (type, status) {
 | 
				
			||||||
 | 
				
			|||||||
@ -210,4 +210,4 @@ class Footer extends ImmutablePureComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default  withRouter(connect(makeMapStateToProps)(injectIntl(Footer)));
 | 
					export default  connect(makeMapStateToProps)(withRouter(injectIntl(Footer)));
 | 
				
			||||||
 | 
				
			|||||||
@ -1,51 +0,0 @@
 | 
				
			|||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { defineMessages, injectIntl } from 'react-intl';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Link } from 'react-router-dom';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
					 | 
				
			||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
					 | 
				
			||||||
import { connect } from 'react-redux';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
 | 
					 | 
				
			||||||
import { Avatar } from 'mastodon/components/avatar';
 | 
					 | 
				
			||||||
import { DisplayName } from 'mastodon/components/display_name';
 | 
					 | 
				
			||||||
import { IconButton } from 'mastodon/components/icon_button';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const messages = defineMessages({
 | 
					 | 
				
			||||||
  close: { id: 'lightbox.close', defaultMessage: 'Close' },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mapStateToProps = (state, { accountId }) => ({
 | 
					 | 
				
			||||||
  account: state.getIn(['accounts', accountId]),
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Header extends ImmutablePureComponent {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static propTypes = {
 | 
					 | 
				
			||||||
    accountId: PropTypes.string.isRequired,
 | 
					 | 
				
			||||||
    statusId: PropTypes.string.isRequired,
 | 
					 | 
				
			||||||
    account: ImmutablePropTypes.record.isRequired,
 | 
					 | 
				
			||||||
    onClose: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
    intl: PropTypes.object.isRequired,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  render () {
 | 
					 | 
				
			||||||
    const { account, statusId, onClose, intl } = this.props;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      <div className='picture-in-picture__header'>
 | 
					 | 
				
			||||||
        <Link to={`/@${account.get('acct')}/${statusId}`} className='picture-in-picture__header__account'>
 | 
					 | 
				
			||||||
          <Avatar account={account} size={36} />
 | 
					 | 
				
			||||||
          <DisplayName account={account} />
 | 
					 | 
				
			||||||
        </Link>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <IconButton icon='times' iconComponent={CloseIcon} onClick={onClose} title={intl.formatMessage(messages.close)} />
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default connect(mapStateToProps)(injectIntl(Header));
 | 
					 | 
				
			||||||
@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					import { defineMessages, useIntl } from 'react-intl';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Link } from 'react-router-dom';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import CloseIcon from '@/material-icons/400-24px/close.svg?react';
 | 
				
			||||||
 | 
					import { Avatar } from 'mastodon/components/avatar';
 | 
				
			||||||
 | 
					import { DisplayName } from 'mastodon/components/display_name';
 | 
				
			||||||
 | 
					import { IconButton } from 'mastodon/components/icon_button';
 | 
				
			||||||
 | 
					import { useAppSelector } from 'mastodon/store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const messages = defineMessages({
 | 
				
			||||||
 | 
					  close: { id: 'lightbox.close', defaultMessage: 'Close' },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Props {
 | 
				
			||||||
 | 
					  accountId: string;
 | 
				
			||||||
 | 
					  statusId: string;
 | 
				
			||||||
 | 
					  onClose: () => void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Header: React.FC<Props> = ({ accountId, statusId, onClose }) => {
 | 
				
			||||||
 | 
					  const account = useAppSelector((state) => state.accounts.get(accountId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const intl = useIntl();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!account) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className='picture-in-picture__header'>
 | 
				
			||||||
 | 
					      <Link
 | 
				
			||||||
 | 
					        to={`/@${account.get('acct')}/${statusId}`}
 | 
				
			||||||
 | 
					        className='picture-in-picture__header__account'
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <Avatar account={account} size={36} />
 | 
				
			||||||
 | 
					        <DisplayName account={account} />
 | 
				
			||||||
 | 
					      </Link>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <IconButton
 | 
				
			||||||
 | 
					        icon='times'
 | 
				
			||||||
 | 
					        iconComponent={CloseIcon}
 | 
				
			||||||
 | 
					        onClick={onClose}
 | 
				
			||||||
 | 
					        title={intl.formatMessage(messages.close)}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,89 +0,0 @@
 | 
				
			|||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
import { Component } from 'react';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { connect } from 'react-redux';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { removePictureInPicture } from 'mastodon/actions/picture_in_picture';
 | 
					 | 
				
			||||||
import Audio from 'mastodon/features/audio';
 | 
					 | 
				
			||||||
import Video from 'mastodon/features/video';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import Footer from './components/footer';
 | 
					 | 
				
			||||||
import Header from './components/header';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mapStateToProps = state => ({
 | 
					 | 
				
			||||||
  ...state.get('picture_in_picture'),
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class PictureInPicture extends Component {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static propTypes = {
 | 
					 | 
				
			||||||
    statusId: PropTypes.string,
 | 
					 | 
				
			||||||
    accountId: PropTypes.string,
 | 
					 | 
				
			||||||
    type: PropTypes.string,
 | 
					 | 
				
			||||||
    src: PropTypes.string,
 | 
					 | 
				
			||||||
    muted: PropTypes.bool,
 | 
					 | 
				
			||||||
    volume: PropTypes.number,
 | 
					 | 
				
			||||||
    currentTime: PropTypes.number,
 | 
					 | 
				
			||||||
    poster: PropTypes.string,
 | 
					 | 
				
			||||||
    backgroundColor: PropTypes.string,
 | 
					 | 
				
			||||||
    foregroundColor: PropTypes.string,
 | 
					 | 
				
			||||||
    accentColor: PropTypes.string,
 | 
					 | 
				
			||||||
    dispatch: PropTypes.func.isRequired,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  handleClose = () => {
 | 
					 | 
				
			||||||
    const { dispatch } = this.props;
 | 
					 | 
				
			||||||
    dispatch(removePictureInPicture());
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  render () {
 | 
					 | 
				
			||||||
    const { type, src, currentTime, accountId, statusId } = this.props;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!currentTime) {
 | 
					 | 
				
			||||||
      return null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let player;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (type === 'video') {
 | 
					 | 
				
			||||||
      player = (
 | 
					 | 
				
			||||||
        <Video
 | 
					 | 
				
			||||||
          src={src}
 | 
					 | 
				
			||||||
          currentTime={this.props.currentTime}
 | 
					 | 
				
			||||||
          volume={this.props.volume}
 | 
					 | 
				
			||||||
          muted={this.props.muted}
 | 
					 | 
				
			||||||
          autoPlay
 | 
					 | 
				
			||||||
          inline
 | 
					 | 
				
			||||||
          alwaysVisible
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    } else if (type === 'audio') {
 | 
					 | 
				
			||||||
      player = (
 | 
					 | 
				
			||||||
        <Audio
 | 
					 | 
				
			||||||
          src={src}
 | 
					 | 
				
			||||||
          currentTime={this.props.currentTime}
 | 
					 | 
				
			||||||
          volume={this.props.volume}
 | 
					 | 
				
			||||||
          muted={this.props.muted}
 | 
					 | 
				
			||||||
          poster={this.props.poster}
 | 
					 | 
				
			||||||
          backgroundColor={this.props.backgroundColor}
 | 
					 | 
				
			||||||
          foregroundColor={this.props.foregroundColor}
 | 
					 | 
				
			||||||
          accentColor={this.props.accentColor}
 | 
					 | 
				
			||||||
          autoPlay
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      <div className='picture-in-picture'>
 | 
					 | 
				
			||||||
        <Header accountId={accountId} statusId={statusId} onClose={this.handleClose} />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        {player}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <Footer statusId={statusId} />
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default connect(mapStateToProps)(PictureInPicture);
 | 
					 | 
				
			||||||
@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					import { useCallback } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { removePictureInPicture } from 'mastodon/actions/picture_in_picture';
 | 
				
			||||||
 | 
					import Audio from 'mastodon/features/audio';
 | 
				
			||||||
 | 
					import Video from 'mastodon/features/video';
 | 
				
			||||||
 | 
					import { useAppDispatch, useAppSelector } from 'mastodon/store/typed_functions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Footer from './components/footer';
 | 
				
			||||||
 | 
					import { Header } from './components/header';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const PictureInPicture: React.FC = () => {
 | 
				
			||||||
 | 
					  const dispatch = useAppDispatch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleClose = useCallback(() => {
 | 
				
			||||||
 | 
					    dispatch(removePictureInPicture());
 | 
				
			||||||
 | 
					  }, [dispatch]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const pipState = useAppSelector((s) => s.picture_in_picture);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (pipState.type === null) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    type,
 | 
				
			||||||
 | 
					    src,
 | 
				
			||||||
 | 
					    currentTime,
 | 
				
			||||||
 | 
					    accountId,
 | 
				
			||||||
 | 
					    statusId,
 | 
				
			||||||
 | 
					    volume,
 | 
				
			||||||
 | 
					    muted,
 | 
				
			||||||
 | 
					    poster,
 | 
				
			||||||
 | 
					    backgroundColor,
 | 
				
			||||||
 | 
					    foregroundColor,
 | 
				
			||||||
 | 
					    accentColor,
 | 
				
			||||||
 | 
					  } = pipState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let player;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  switch (type) {
 | 
				
			||||||
 | 
					    case 'video':
 | 
				
			||||||
 | 
					      player = (
 | 
				
			||||||
 | 
					        <Video
 | 
				
			||||||
 | 
					          src={src}
 | 
				
			||||||
 | 
					          currentTime={currentTime}
 | 
				
			||||||
 | 
					          volume={volume}
 | 
				
			||||||
 | 
					          muted={muted}
 | 
				
			||||||
 | 
					          autoPlay
 | 
				
			||||||
 | 
					          inline
 | 
				
			||||||
 | 
					          alwaysVisible
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case 'audio':
 | 
				
			||||||
 | 
					      player = (
 | 
				
			||||||
 | 
					        <Audio
 | 
				
			||||||
 | 
					          src={src}
 | 
				
			||||||
 | 
					          currentTime={currentTime}
 | 
				
			||||||
 | 
					          volume={volume}
 | 
				
			||||||
 | 
					          muted={muted}
 | 
				
			||||||
 | 
					          poster={poster}
 | 
				
			||||||
 | 
					          backgroundColor={backgroundColor}
 | 
				
			||||||
 | 
					          foregroundColor={foregroundColor}
 | 
				
			||||||
 | 
					          accentColor={accentColor}
 | 
				
			||||||
 | 
					          autoPlay
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className='picture-in-picture'>
 | 
				
			||||||
 | 
					      <Header accountId={accountId} statusId={statusId} onClose={handleClose} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {player}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <Footer statusId={statusId} />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -14,7 +14,7 @@ import { HotKeys } from 'react-hotkeys';
 | 
				
			|||||||
import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
 | 
					import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
 | 
				
			||||||
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
 | 
					import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
 | 
				
			||||||
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
 | 
					import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
 | 
				
			||||||
import PictureInPicture from 'mastodon/features/picture_in_picture';
 | 
					import { PictureInPicture } from 'mastodon/features/picture_in_picture';
 | 
				
			||||||
import { layoutFromWindow } from 'mastodon/is_mobile';
 | 
					import { layoutFromWindow } from 'mastodon/is_mobile';
 | 
				
			||||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
 | 
					import { WithRouterPropTypes } from 'mastodon/utils/react_router';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -28,7 +28,7 @@ import { modalReducer } from './modal';
 | 
				
			|||||||
import { notificationPolicyReducer } from './notification_policy';
 | 
					import { notificationPolicyReducer } from './notification_policy';
 | 
				
			||||||
import { notificationRequestsReducer } from './notification_requests';
 | 
					import { notificationRequestsReducer } from './notification_requests';
 | 
				
			||||||
import notifications from './notifications';
 | 
					import notifications from './notifications';
 | 
				
			||||||
import picture_in_picture from './picture_in_picture';
 | 
					import { pictureInPictureReducer } from './picture_in_picture';
 | 
				
			||||||
import polls from './polls';
 | 
					import polls from './polls';
 | 
				
			||||||
import push_notifications from './push_notifications';
 | 
					import push_notifications from './push_notifications';
 | 
				
			||||||
import { relationshipsReducer } from './relationships';
 | 
					import { relationshipsReducer } from './relationships';
 | 
				
			||||||
@ -78,7 +78,7 @@ const reducers = {
 | 
				
			|||||||
  polls,
 | 
					  polls,
 | 
				
			||||||
  trends,
 | 
					  trends,
 | 
				
			||||||
  markers: markersReducer,
 | 
					  markers: markersReducer,
 | 
				
			||||||
  picture_in_picture,
 | 
					  picture_in_picture: pictureInPictureReducer,
 | 
				
			||||||
  history,
 | 
					  history,
 | 
				
			||||||
  tags,
 | 
					  tags,
 | 
				
			||||||
  followed_tags,
 | 
					  followed_tags,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,26 +0,0 @@
 | 
				
			|||||||
import { PICTURE_IN_PICTURE_DEPLOY, PICTURE_IN_PICTURE_REMOVE } from 'mastodon/actions/picture_in_picture';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { TIMELINE_DELETE } from '../actions/timelines';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const initialState = {
 | 
					 | 
				
			||||||
  statusId: null,
 | 
					 | 
				
			||||||
  accountId: null,
 | 
					 | 
				
			||||||
  type: null,
 | 
					 | 
				
			||||||
  src: null,
 | 
					 | 
				
			||||||
  muted: false,
 | 
					 | 
				
			||||||
  volume: 0,
 | 
					 | 
				
			||||||
  currentTime: 0,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default function pictureInPicture(state = initialState, action) {
 | 
					 | 
				
			||||||
  switch(action.type) {
 | 
					 | 
				
			||||||
  case PICTURE_IN_PICTURE_DEPLOY:
 | 
					 | 
				
			||||||
    return { statusId: action.statusId, accountId: action.accountId, type: action.playerType, ...action.props };
 | 
					 | 
				
			||||||
  case PICTURE_IN_PICTURE_REMOVE:
 | 
					 | 
				
			||||||
    return { ...initialState };
 | 
					 | 
				
			||||||
  case TIMELINE_DELETE:
 | 
					 | 
				
			||||||
    return (state.statusId === action.id) ? { ...initialState } : state;
 | 
					 | 
				
			||||||
  default:
 | 
					 | 
				
			||||||
    return state;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										56
									
								
								app/javascript/mastodon/reducers/picture_in_picture.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								app/javascript/mastodon/reducers/picture_in_picture.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					import type { Reducer } from '@reduxjs/toolkit';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  deployPictureInPictureAction,
 | 
				
			||||||
 | 
					  removePictureInPicture,
 | 
				
			||||||
 | 
					} from 'mastodon/actions/picture_in_picture';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { TIMELINE_DELETE } from '../actions/timelines';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface PIPMediaProps {
 | 
				
			||||||
 | 
					  src: string;
 | 
				
			||||||
 | 
					  muted: boolean;
 | 
				
			||||||
 | 
					  volume: number;
 | 
				
			||||||
 | 
					  currentTime: number;
 | 
				
			||||||
 | 
					  poster: string;
 | 
				
			||||||
 | 
					  backgroundColor: string;
 | 
				
			||||||
 | 
					  foregroundColor: string;
 | 
				
			||||||
 | 
					  accentColor: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface PIPStateWithValue extends Partial<PIPMediaProps> {
 | 
				
			||||||
 | 
					  statusId: string;
 | 
				
			||||||
 | 
					  accountId: string;
 | 
				
			||||||
 | 
					  type: 'audio' | 'video';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface PIPStateEmpty extends Partial<PIPMediaProps> {
 | 
				
			||||||
 | 
					  type: null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PIPState = PIPStateWithValue | PIPStateEmpty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const initialState = {
 | 
				
			||||||
 | 
					  type: null,
 | 
				
			||||||
 | 
					  muted: false,
 | 
				
			||||||
 | 
					  volume: 0,
 | 
				
			||||||
 | 
					  currentTime: 0,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const pictureInPictureReducer: Reducer<PIPState> = (
 | 
				
			||||||
 | 
					  state = initialState,
 | 
				
			||||||
 | 
					  action,
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					  if (deployPictureInPictureAction.match(action))
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      statusId: action.payload.statusId,
 | 
				
			||||||
 | 
					      accountId: action.payload.accountId,
 | 
				
			||||||
 | 
					      type: action.payload.playerType,
 | 
				
			||||||
 | 
					      ...action.payload.props,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  else if (removePictureInPicture.match(action)) return initialState;
 | 
				
			||||||
 | 
					  else if (action.type === TIMELINE_DELETE)
 | 
				
			||||||
 | 
					    if (state.type && state.statusId === action.id) return initialState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return state;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -60,7 +60,7 @@ export const makeGetStatus = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const makeGetPictureInPicture = () => {
 | 
					export const makeGetPictureInPicture = () => {
 | 
				
			||||||
  return createSelector([
 | 
					  return createSelector([
 | 
				
			||||||
    (state, { id }) => state.get('picture_in_picture').statusId === id,
 | 
					    (state, { id }) => state.picture_in_picture.statusId === id,
 | 
				
			||||||
    (state) => state.getIn(['meta', 'layout']) !== 'mobile',
 | 
					    (state) => state.getIn(['meta', 'layout']) !== 'mobile',
 | 
				
			||||||
  ], (inUse, available) => ImmutableMap({
 | 
					  ], (inUse, available) => ImmutableMap({
 | 
				
			||||||
    inUse: inUse && available,
 | 
					    inUse: inUse && available,
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user