This commit is contained in:
@@ -0,0 +1 @@
|
||||
export default {};
|
||||
@@ -0,0 +1,177 @@
|
||||
import { Theme } from '@mui/material';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { createDeepLinkingPageEvent } from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { IDeeplinkingConfig } from '../../base/config/configType';
|
||||
import { getLegalUrls } from '../../base/config/functions.any';
|
||||
import { isSupportedBrowser } from '../../base/environment/environment';
|
||||
import { translate, translateToHTML } from '../../base/i18n/functions';
|
||||
import Platform from '../../base/react/Platform.web';
|
||||
import Button from '../../base/ui/components/web/Button';
|
||||
import { BUTTON_TYPES } from '../../base/ui/constants.any';
|
||||
import {
|
||||
openDesktopApp,
|
||||
openWebApp
|
||||
} from '../actions';
|
||||
import { _TNS } from '../constants';
|
||||
|
||||
const useStyles = makeStyles()((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
background: '#1E1E1E',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex'
|
||||
},
|
||||
contentPane: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
background: theme.palette.ui01,
|
||||
border: `1px solid ${theme.palette.ui03}`,
|
||||
padding: 40,
|
||||
borderRadius: 16,
|
||||
maxWidth: 410,
|
||||
color: theme.palette.text01
|
||||
},
|
||||
logo: {
|
||||
marginBottom: 32
|
||||
},
|
||||
launchingMeetingLabel: {
|
||||
marginBottom: 16,
|
||||
...theme.typography.heading4
|
||||
},
|
||||
roomName: {
|
||||
marginBottom: 32,
|
||||
...theme.typography.heading5
|
||||
},
|
||||
descriptionLabel: {
|
||||
marginBottom: 32,
|
||||
...theme.typography.bodyLongRegular
|
||||
},
|
||||
buttonsContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
'& > *:not(:last-child)': {
|
||||
marginRight: 16
|
||||
}
|
||||
},
|
||||
separator: {
|
||||
marginTop: 40,
|
||||
height: 1,
|
||||
maxWidth: 390,
|
||||
background: theme.palette.ui03
|
||||
},
|
||||
label: {
|
||||
marginTop: 40,
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text02,
|
||||
'& a': {
|
||||
color: theme.palette.link01
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const DeepLinkingDesktopPage: React.FC<WithTranslation> = ({ t }) => {
|
||||
const dispatch = useDispatch();
|
||||
const room = useSelector((state: IReduxState) => decodeURIComponent(state['features/base/conference'].room || ''));
|
||||
const deeplinkingCfg = useSelector((state: IReduxState) =>
|
||||
state['features/base/config']?.deeplinking || {} as IDeeplinkingConfig);
|
||||
|
||||
const generateDownloadURL = useCallback(() => {
|
||||
const downloadCfg = deeplinkingCfg.desktop?.download;
|
||||
|
||||
if (downloadCfg) {
|
||||
return downloadCfg[Platform.OS as keyof typeof downloadCfg];
|
||||
}
|
||||
}, [ deeplinkingCfg ]);
|
||||
|
||||
const legalUrls = useSelector(getLegalUrls);
|
||||
|
||||
const { hideLogo, desktop } = deeplinkingCfg;
|
||||
|
||||
const { classes: styles } = useStyles();
|
||||
const onLaunchWeb = useCallback(() => {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'launchWebButton', { isMobileBrowser: false }));
|
||||
dispatch(openWebApp());
|
||||
}, []);
|
||||
const onTryAgain = useCallback(() => {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'tryAgainButton', { isMobileBrowser: false }));
|
||||
dispatch(openDesktopApp());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'displayed', 'DeepLinkingDesktop', { isMobileBrowser: false }));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className = { styles.container }>
|
||||
<div className = { styles.contentPane }>
|
||||
<div className = 'header'>
|
||||
{
|
||||
!hideLogo
|
||||
&& <img
|
||||
alt = { t('welcomepage.logo.logoDeepLinking') }
|
||||
className = { styles.logo }
|
||||
src = 'images/logo-deep-linking.png' />
|
||||
}
|
||||
</div>
|
||||
<div className = { styles.launchingMeetingLabel }>
|
||||
{
|
||||
t(`${_TNS}.titleNew`)
|
||||
}
|
||||
</div>
|
||||
<div className = { styles.roomName }>{ room }</div>
|
||||
<div className = { styles.descriptionLabel }>
|
||||
{
|
||||
isSupportedBrowser()
|
||||
? translateToHTML(t, `${_TNS}.descriptionNew`, { app: desktop?.appName })
|
||||
: t(`${_TNS}.descriptionWithoutWeb`, { app: desktop?.appName })
|
||||
}
|
||||
</div>
|
||||
<div className = { styles.descriptionLabel }>
|
||||
{
|
||||
t(`${_TNS}.noDesktopApp`)
|
||||
}
|
||||
<a href = { generateDownloadURL() }>
|
||||
{
|
||||
t(`${_TNS}.downloadApp`)
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
<div className = { styles.buttonsContainer }>
|
||||
<Button
|
||||
label = { t(`${_TNS}.tryAgainButton`) }
|
||||
onClick = { onTryAgain } />
|
||||
{ isSupportedBrowser() && (
|
||||
<Button
|
||||
label = { t(`${_TNS}.launchWebButton`) }
|
||||
onClick = { onLaunchWeb }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
)}
|
||||
|
||||
</div>
|
||||
<div className = { styles.separator } />
|
||||
<div className = { styles.label }> {translateToHTML(t, 'deepLinking.termsAndConditions', {
|
||||
termsAndConditionsLink: legalUrls.terms
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default translate(DeepLinkingDesktopPage);
|
||||
@@ -0,0 +1 @@
|
||||
export default {};
|
||||
@@ -0,0 +1,219 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import { Theme } from '@mui/material';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { createDeepLinkingPageEvent } from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { IDeeplinkingConfig, IDeeplinkingMobileConfig } from '../../base/config/configType';
|
||||
import { isSupportedMobileBrowser } from '../../base/environment/environment';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import Platform from '../../base/react/Platform.web';
|
||||
import Button from '../../base/ui/components/web/Button';
|
||||
import DialInSummary from '../../invite/components/dial-in-summary/web/DialInSummary';
|
||||
import { openWebApp } from '../actions';
|
||||
import { _TNS } from '../constants';
|
||||
import { generateDeepLinkingURL } from '../functions';
|
||||
|
||||
|
||||
const PADDINGS = {
|
||||
topBottom: 24,
|
||||
leftRight: 40
|
||||
};
|
||||
|
||||
const useStyles = makeStyles()((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
background: '#1E1E1E',
|
||||
width: '100vw',
|
||||
height: '100dvh',
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'auto',
|
||||
justifyContent: 'center',
|
||||
display: 'flex',
|
||||
'& a': {
|
||||
textDecoration: 'none'
|
||||
}
|
||||
},
|
||||
contentPane: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
padding: `${PADDINGS.topBottom}px ${PADDINGS.leftRight}px`,
|
||||
maxWidth: 410,
|
||||
color: theme.palette.text01
|
||||
},
|
||||
launchingMeetingLabel: {
|
||||
marginTop: 24,
|
||||
textAlign: 'center',
|
||||
marginBottom: 32,
|
||||
...theme.typography.heading5
|
||||
},
|
||||
roomNameLabel: {
|
||||
...theme.typography.bodyLongRegularLarge
|
||||
},
|
||||
joinMeetWrapper: {
|
||||
marginTop: 24,
|
||||
width: '100%'
|
||||
},
|
||||
labelDescription: {
|
||||
textAlign: 'center',
|
||||
marginTop: 16,
|
||||
...theme.typography.bodyShortRegularLarge
|
||||
},
|
||||
linkWrapper: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: 8,
|
||||
width: '100%'
|
||||
},
|
||||
linkLabel: {
|
||||
color: theme.palette.link01,
|
||||
...theme.typography.bodyLongBoldLarge
|
||||
},
|
||||
supportedBrowserContent: {
|
||||
marginTop: 16,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
labelOr: {
|
||||
...theme.typography.bodyShortRegularLarge
|
||||
},
|
||||
separator: {
|
||||
marginTop: '32px',
|
||||
height: 1,
|
||||
width: `calc(100% + ${2 * PADDINGS.leftRight}px)`,
|
||||
background: theme.palette.ui03
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const DeepLinkingMobilePage: React.FC<WithTranslation> = ({ t }) => {
|
||||
const deeplinkingCfg = useSelector((state: IReduxState) =>
|
||||
state['features/base/config']?.deeplinking || {} as IDeeplinkingConfig);
|
||||
const { hideLogo } = deeplinkingCfg;
|
||||
const deepLinkingUrl: string = useSelector(generateDeepLinkingURL);
|
||||
const room = useSelector((state: IReduxState) => decodeURIComponent(state['features/base/conference'].room || ''));
|
||||
const url = useSelector((state: IReduxState) => state['features/base/connection'] || {});
|
||||
const dispatch = useDispatch();
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
const generateDownloadURL = useCallback(() => {
|
||||
const { downloadLink }
|
||||
= (deeplinkingCfg?.[Platform.OS as keyof typeof deeplinkingCfg] || {}) as IDeeplinkingMobileConfig;
|
||||
|
||||
return downloadLink;
|
||||
}, [ deeplinkingCfg ]);
|
||||
|
||||
const onDownloadApp = useCallback(() => {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'downloadAppButton', { isMobileBrowser: true }));
|
||||
}, []);
|
||||
|
||||
const onLaunchWeb = useCallback(() => {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'launchWebButton', { isMobileBrowser: true }));
|
||||
dispatch(openWebApp());
|
||||
}, []);
|
||||
|
||||
const onOpenApp = useCallback(() => {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'openAppButton', { isMobileBrowser: true }));
|
||||
}, []);
|
||||
|
||||
const onOpenLinkProperties = useMemo(() => {
|
||||
const { downloadLink }
|
||||
= (deeplinkingCfg?.[Platform.OS as keyof typeof deeplinkingCfg] || {}) as IDeeplinkingMobileConfig;
|
||||
|
||||
if (downloadLink) {
|
||||
return {
|
||||
// When opening a link to the download page, we want to let the
|
||||
// OS itself handle intercepting and opening the appropriate
|
||||
// app store. This avoids potential issues with browsers, such
|
||||
// as iOS Chrome, not opening the store properly.
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
// When falling back to another URL (Firebase) let the page be
|
||||
// opened in a new window. This helps prevent the user getting
|
||||
// trapped in an app-open-cycle where going back to the mobile
|
||||
// browser re-triggers the app-open behavior.
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer'
|
||||
};
|
||||
}, [ deeplinkingCfg ]);
|
||||
|
||||
useEffect(() => {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'displayed', 'DeepLinkingMobile', { isMobileBrowser: true }));
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<div className = { styles.container }>
|
||||
<div className = { styles.contentPane }>
|
||||
{!hideLogo && (<img
|
||||
alt = { t('welcomepage.logo.logoDeepLinking') }
|
||||
src = 'images/logo-deep-linking-mobile.png' />
|
||||
)}
|
||||
|
||||
<div className = { styles.launchingMeetingLabel }>{ t(`${_TNS}.launchMeetingLabel`) }</div>
|
||||
<div className = ''>{room}</div>
|
||||
<a
|
||||
{ ...onOpenLinkProperties }
|
||||
className = { styles.joinMeetWrapper }
|
||||
href = { deepLinkingUrl }
|
||||
onClick = { onOpenApp }
|
||||
target = '_top'>
|
||||
<Button
|
||||
fullWidth = { true }
|
||||
label = { t(`${_TNS}.joinInAppNew`) } />
|
||||
</a>
|
||||
<div className = { styles.labelDescription }>{ t(`${_TNS}.noMobileApp`) }</div>
|
||||
<a
|
||||
{ ...onOpenLinkProperties }
|
||||
className = { styles.linkWrapper }
|
||||
href = { generateDownloadURL() }
|
||||
onClick = { onDownloadApp }
|
||||
target = '_top'>
|
||||
<div className = { styles.linkLabel }>{ t(`${_TNS}.downloadMobileApp`) }</div>
|
||||
</a>
|
||||
{isSupportedMobileBrowser() ? (
|
||||
<div className = { styles.supportedBrowserContent }>
|
||||
<div className = { styles.labelOr }>{ t(`${_TNS}.or`) }</div>
|
||||
<a
|
||||
className = { styles.linkWrapper }
|
||||
onClick = { onLaunchWeb }
|
||||
target = '_top'>
|
||||
<div className = { styles.linkLabel }>{ t(`${_TNS}.joinInBrowser`) }</div>
|
||||
</a>
|
||||
</div>
|
||||
) : (
|
||||
<div className = { styles.labelDescription }>
|
||||
{t(`${_TNS}.unsupportedBrowser`)}
|
||||
</div>
|
||||
)}
|
||||
<div className = { styles.separator } />
|
||||
<DialInSummary
|
||||
className = 'deep-linking-dial-in'
|
||||
clickableNumbers = { true }
|
||||
hideError = { true }
|
||||
room = { room }
|
||||
url = { url } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default translate(DeepLinkingMobilePage);
|
||||
@@ -0,0 +1 @@
|
||||
export default {};
|
||||
77
react/features/deep-linking/components/NoMobileApp.web.tsx
Normal file
77
react/features/deep-linking/components/NoMobileApp.web.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { createDeepLinkingPageEvent } from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { IDeeplinkingConfig } from '../../base/config/configType';
|
||||
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link NoMobileApp}.
|
||||
*/
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The deeplinking config.
|
||||
*/
|
||||
_deeplinkingCfg: IDeeplinkingConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* React component representing no mobile app page.
|
||||
*
|
||||
* @class NoMobileApp
|
||||
*/
|
||||
class NoMobileApp extends Component<IProps> {
|
||||
/**
|
||||
* Implements the Component's componentDidMount method.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
override componentDidMount() {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'displayed', 'noMobileApp', { isMobileBrowser: true }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the component.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
override render() {
|
||||
const ns = 'no-mobile-app';
|
||||
const { desktop } = this.props._deeplinkingCfg;
|
||||
const { appName } = desktop ?? {};
|
||||
|
||||
return (
|
||||
<div className = { ns }>
|
||||
<h2 className = { `${ns}__title` }>
|
||||
Video chat isn't available on mobile.
|
||||
</h2>
|
||||
<p className = { `${ns}__description` }>
|
||||
Please use { appName } on desktop to
|
||||
join calls.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code NoMobileApp} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
_deeplinkingCfg: state['features/base/config'].deeplinking || {}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(NoMobileApp);
|
||||
Reference in New Issue
Block a user