feat(deeplinking) Refactor deeplinking (#12950)
- redesign deeplinking mobile page, desktop page and dial in number page - now dial in number page is an entry point in app.bundle.
This commit is contained in:
parent
9b7a5ffdd1
commit
35ee92869f
2
Makefile
2
Makefile
|
@ -48,8 +48,6 @@ deploy-appbundle:
|
||||||
$(BUILD_DIR)/do_external_connect.min.js.map \
|
$(BUILD_DIR)/do_external_connect.min.js.map \
|
||||||
$(BUILD_DIR)/external_api.min.js \
|
$(BUILD_DIR)/external_api.min.js \
|
||||||
$(BUILD_DIR)/external_api.min.js.map \
|
$(BUILD_DIR)/external_api.min.js.map \
|
||||||
$(BUILD_DIR)/dial_in_info_bundle.min.js \
|
|
||||||
$(BUILD_DIR)/dial_in_info_bundle.min.js.map \
|
|
||||||
$(BUILD_DIR)/alwaysontop.min.js \
|
$(BUILD_DIR)/alwaysontop.min.js \
|
||||||
$(BUILD_DIR)/alwaysontop.min.js.map \
|
$(BUILD_DIR)/alwaysontop.min.js.map \
|
||||||
$(OUTPUT_DIR)/analytics-ga.js \
|
$(OUTPUT_DIR)/analytics-ga.js \
|
||||||
|
|
|
@ -1146,6 +1146,13 @@ var config = {
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
|
|
||||||
|
// // The terms, privacy and help centre URL's.
|
||||||
|
// legalUrls: {
|
||||||
|
// helpCentre: 'https://web-cdn.jitsi.net/faq/meet-faq.html',
|
||||||
|
// privacy: 'https://jitsi.org/meet/privacy',
|
||||||
|
// terms: 'https://jitsi.org/meet/terms'
|
||||||
|
// },
|
||||||
|
|
||||||
// A property to disable the right click context menu for localVideo
|
// A property to disable the right click context menu for localVideo
|
||||||
// the menu has option to flip the locally seen video for local presentations
|
// the menu has option to flip the locally seen video for local presentations
|
||||||
// disableLocalVideoFlip: false,
|
// disableLocalVideoFlip: false,
|
||||||
|
|
|
@ -201,11 +201,6 @@ $deepLinkingDialInConferenceIdPadding: inherit;
|
||||||
$deepLinkingDialInConferenceIdBackgroundColor: inherit;
|
$deepLinkingDialInConferenceIdBackgroundColor: inherit;
|
||||||
$deepLinkingDialInConferenceIdBorderRadius: inherit;
|
$deepLinkingDialInConferenceIdBorderRadius: inherit;
|
||||||
|
|
||||||
$deepLinkingDialInConferenceNameFontSize: inherit;
|
|
||||||
$deepLinkingDialInConferenceNameLineHeight: inherit;
|
|
||||||
$deepLinkingDialInConferenceNameMarginBottom: none;
|
|
||||||
$deepLinkingDialInConferenceNameFontWeight: inherit;
|
|
||||||
|
|
||||||
$deepLinkingDialInConferenceDescriptionFontSize: 0.8em;
|
$deepLinkingDialInConferenceDescriptionFontSize: 0.8em;
|
||||||
$deepLinkingDialInConferenceDescriptionLineHeight: inherit;
|
$deepLinkingDialInConferenceDescriptionLineHeight: inherit;
|
||||||
$deepLinkingDialInConferenceDescriptionMarginBottom: none;
|
$deepLinkingDialInConferenceDescriptionMarginBottom: none;
|
||||||
|
|
|
@ -67,6 +67,13 @@
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.dial-in-conference-id {
|
||||||
|
text-align: center;
|
||||||
|
min-width: 200px;
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
.dial-in-conference-id {
|
.dial-in-conference-id {
|
||||||
margin: $deepLinkingDialInConferenceIdMargin;
|
margin: $deepLinkingDialInConferenceIdMargin;
|
||||||
padding: $deepLinkingDialInConferenceIdPadding;
|
padding: $deepLinkingDialInConferenceIdPadding;
|
||||||
|
@ -74,24 +81,12 @@
|
||||||
border-radius: $deepLinkingDialInConferenceIdBorderRadius;
|
border-radius: $deepLinkingDialInConferenceIdBorderRadius;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dial-in-conference-name {
|
|
||||||
font-size: $deepLinkingDialInConferenceNameFontSize;
|
|
||||||
line-height: $deepLinkingDialInConferenceNameLineHeight;
|
|
||||||
margin-bottom: $deepLinkingDialInConferenceNameMarginBottom;
|
|
||||||
font-weight: $deepLinkingDialInConferenceNameFontWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dial-in-conference-description {
|
.dial-in-conference-description {
|
||||||
font-size: $deepLinkingDialInConferenceDescriptionFontSize;
|
font-size: $deepLinkingDialInConferenceDescriptionFontSize;
|
||||||
line-height: $deepLinkingDialInConferenceDescriptionLineHeight;
|
line-height: $deepLinkingDialInConferenceDescriptionLineHeight;
|
||||||
margin-bottom: $deepLinkingDialInConferenceDescriptionMarginBottom;
|
margin-bottom: $deepLinkingDialInConferenceDescriptionMarginBottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dial-in-conference-pin {
|
|
||||||
font-size: $deepLinkingDialInConferencePinFontSize;
|
|
||||||
line-height: $deepLinkingDialInConferencePinLineHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toll-free-list {
|
.toll-free-list {
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.dial-in-numbers-list {
|
.dial-in-numbers-list {
|
||||||
|
max-width: 334px;
|
||||||
|
width: 100%;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
|
@ -59,10 +61,6 @@
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr {
|
|
||||||
border-bottom: 1px solid #d1dbe8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flag-cell {
|
.flag-cell {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
|
@ -91,6 +89,7 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
li.toll-free:empty:before {
|
li.toll-free:empty:before {
|
||||||
|
@ -119,11 +118,6 @@
|
||||||
margin-top: 40px;
|
margin-top: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dial-in-conference-name,
|
|
||||||
.dial-in-conference-pin {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dial-in-conference-description {
|
.dial-in-conference-description {
|
||||||
margin: 12px;
|
margin: 12px;
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 6.3 KiB |
|
@ -184,13 +184,21 @@
|
||||||
"deepLinking": {
|
"deepLinking": {
|
||||||
"appNotInstalled": "You need the {{app}} mobile app to join this meeting on your phone.",
|
"appNotInstalled": "You need the {{app}} mobile app to join this meeting on your phone.",
|
||||||
"description": "Nothing happened? We tried launching your meeting in the {{app}} desktop app. Try again or launch it in the {{app}} web app.",
|
"description": "Nothing happened? We tried launching your meeting in the {{app}} desktop app. Try again or launch it in the {{app}} web app.",
|
||||||
|
"descriptionNew": "Nothing happened? We tried launching your meeting in the {{app}} desktop app. <br /><br /> You can try again or launch it on web.",
|
||||||
"descriptionWithoutWeb": "Nothing happened? We tried launching your meeting in the {{app}} desktop app.",
|
"descriptionWithoutWeb": "Nothing happened? We tried launching your meeting in the {{app}} desktop app.",
|
||||||
"downloadApp": "Download the app",
|
"downloadApp": "Download the app",
|
||||||
|
"downloadMobileApp": "Download from App Store",
|
||||||
"ifDoNotHaveApp": "If you don't have the app yet:",
|
"ifDoNotHaveApp": "If you don't have the app yet:",
|
||||||
"ifHaveApp": "If you already have the app:",
|
"ifHaveApp": "If you already have the app:",
|
||||||
"joinInApp": "Join this meeting using the app",
|
"joinInApp": "Join this meeting using the app",
|
||||||
|
"joinInAppNew": "Join in app",
|
||||||
|
"joinInBrowser": "Join in browser",
|
||||||
|
"launchMeetingLabel": "How do you want to join this meeting?",
|
||||||
"launchWebButton": "Launch in web",
|
"launchWebButton": "Launch in web",
|
||||||
|
"noMobileApp": "You don’t have the app?",
|
||||||
|
"termsAndConditions": "By continuing you agree to our <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>terms & conditions.</a>",
|
||||||
"title": "Launching your meeting in {{app}}...",
|
"title": "Launching your meeting in {{app}}...",
|
||||||
|
"titleNew": "Launching your meeting ...",
|
||||||
"tryAgainButton": "Try again in desktop",
|
"tryAgainButton": "Try again in desktop",
|
||||||
"unsupportedBrowser": "It looks like you're using a browser we don't support."
|
"unsupportedBrowser": "It looks like you're using a browser we don't support."
|
||||||
},
|
},
|
||||||
|
|
|
@ -388,6 +388,11 @@ export interface IConfig {
|
||||||
lastNLimits?: {
|
lastNLimits?: {
|
||||||
[key: number]: number;
|
[key: number]: number;
|
||||||
};
|
};
|
||||||
|
legalUrls?: {
|
||||||
|
helpCentre: string;
|
||||||
|
privacy: string;
|
||||||
|
terms: string;
|
||||||
|
};
|
||||||
liveStreaming?: {
|
liveStreaming?: {
|
||||||
dataPrivacyLink?: string;
|
dataPrivacyLink?: string;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
|
|
@ -70,3 +70,18 @@ export const THIRD_PARTY_PREJOIN_BUTTONS = [ 'microphone', 'camera', 'select-bac
|
||||||
export const FEATURE_FLAGS = {
|
export const FEATURE_FLAGS = {
|
||||||
SSRC_REWRITING: 'ssrcRewritingEnabled'
|
SSRC_REWRITING: 'ssrcRewritingEnabled'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL at which the terms (of service/use) are available to the user.
|
||||||
|
*/
|
||||||
|
export const DEFAULT_TERMS_URL = 'https://jitsi.org/meet/terms';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL at which the privacy policy is available to the user.
|
||||||
|
*/
|
||||||
|
export const DEFAULT_PRIVACY_URL = 'https://jitsi.org/meet/privacy';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL at which the help centre is available to the user.
|
||||||
|
*/
|
||||||
|
export const DEFAULT_HELP_CENTRE_URL = 'https://web-cdn.jitsi.net/faq/meet-faq.html';
|
||||||
|
|
|
@ -11,7 +11,13 @@ import { parseURLParams } from '../util/parseURLParams';
|
||||||
|
|
||||||
import { IConfig } from './configType';
|
import { IConfig } from './configType';
|
||||||
import CONFIG_WHITELIST from './configWhitelist';
|
import CONFIG_WHITELIST from './configWhitelist';
|
||||||
import { FEATURE_FLAGS, _CONFIG_STORE_PREFIX } from './constants';
|
import {
|
||||||
|
DEFAULT_HELP_CENTRE_URL,
|
||||||
|
DEFAULT_PRIVACY_URL,
|
||||||
|
DEFAULT_TERMS_URL,
|
||||||
|
FEATURE_FLAGS,
|
||||||
|
_CONFIG_STORE_PREFIX
|
||||||
|
} from './constants';
|
||||||
import INTERFACE_CONFIG_WHITELIST from './interfaceConfigWhitelist';
|
import INTERFACE_CONFIG_WHITELIST from './interfaceConfigWhitelist';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
|
||||||
|
@ -326,3 +332,24 @@ export function getDialOutUrl(state: IReduxState) {
|
||||||
export function getSecurityUiConfig(state: IReduxState) {
|
export function getSecurityUiConfig(state: IReduxState) {
|
||||||
return state['features/base/config']?.securityUi || {};
|
return state['features/base/config']?.securityUi || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the terms, privacy and help centre URL's.
|
||||||
|
*
|
||||||
|
* @param {IReduxState} state - The state of the application.
|
||||||
|
* @returns {{
|
||||||
|
* privacy: string,
|
||||||
|
* helpCentre: string,
|
||||||
|
* terms: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function getLegalUrls(state: IReduxState) {
|
||||||
|
const helpCentreURL = state['features/base/config']?.helpCentreURL;
|
||||||
|
const configLegalUrls = state['features/base/config']?.legalUrls;
|
||||||
|
|
||||||
|
return {
|
||||||
|
privacy: configLegalUrls?.privacy || DEFAULT_PRIVACY_URL,
|
||||||
|
helpCentre: helpCentreURL || configLegalUrls?.helpCentre || DEFAULT_HELP_CENTRE_URL,
|
||||||
|
terms: configLegalUrls?.terms || DEFAULT_TERMS_URL
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import type { Dispatch } from 'redux';
|
import type { Dispatch } from 'redux';
|
||||||
|
|
||||||
import { appNavigate } from '../app/actions';
|
import { appNavigate } from '../app/actions';
|
|
@ -1,192 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import type { Dispatch } from 'redux';
|
|
||||||
|
|
||||||
import { createDeepLinkingPageEvent, sendAnalytics } from '../../analytics';
|
|
||||||
import { IDeeplinkingConfig } from '../../base/config/configType';
|
|
||||||
import { isSupportedBrowser } from '../../base/environment';
|
|
||||||
import { translate } from '../../base/i18n';
|
|
||||||
import { connect } from '../../base/redux';
|
|
||||||
import Button from '../../base/ui/components/web/Button';
|
|
||||||
import { BUTTON_TYPES } from '../../base/ui/constants.web';
|
|
||||||
import {
|
|
||||||
openDesktopApp,
|
|
||||||
openWebApp
|
|
||||||
} from '../actions';
|
|
||||||
import { _TNS } from '../constants';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of the React {@code Component} props of
|
|
||||||
* {@link DeepLinkingDesktopPage}.
|
|
||||||
*/
|
|
||||||
type Props = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The deeplinking config.
|
|
||||||
*/
|
|
||||||
_deeplinkingCfg: IDeeplinkingConfig,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to dispatch actions from the buttons.
|
|
||||||
*/
|
|
||||||
dispatch: Dispatch<any>,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to obtain translations.
|
|
||||||
*/
|
|
||||||
t: Function
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* React component representing the deep linking page.
|
|
||||||
*
|
|
||||||
* @class DeepLinkingDesktopPage
|
|
||||||
*/
|
|
||||||
class DeepLinkingDesktopPage<P : Props> extends Component<P> {
|
|
||||||
/**
|
|
||||||
* Initializes a new {@code DeepLinkingDesktopPage} instance.
|
|
||||||
*
|
|
||||||
* @param {Object} props - The read-only React {@code Component} props with
|
|
||||||
* which the new instance is to be initialized.
|
|
||||||
*/
|
|
||||||
constructor(props: P) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
// Bind event handlers so they are only bound once per instance.
|
|
||||||
this._onLaunchWeb = this._onLaunchWeb.bind(this);
|
|
||||||
this._onTryAgain = this._onTryAgain.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements the Component's componentDidMount method.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
componentDidMount() {
|
|
||||||
sendAnalytics(
|
|
||||||
createDeepLinkingPageEvent(
|
|
||||||
'displayed', 'DeepLinkingDesktop', { isMobileBrowser: false }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the component.
|
|
||||||
*
|
|
||||||
* @returns {ReactElement}
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
const { t, _deeplinkingCfg: { desktop = {}, hideLogo, showImage } } = this.props;
|
|
||||||
const { appName } = desktop;
|
|
||||||
const rightColumnStyle
|
|
||||||
= showImage ? null : { width: '100%' };
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
// Enabling light theme because of the color of the buttons.
|
|
||||||
<AtlasKitThemeProvider mode = 'light'>
|
|
||||||
<div className = 'deep-linking-desktop'>
|
|
||||||
<div className = 'header'>
|
|
||||||
{
|
|
||||||
hideLogo
|
|
||||||
? null
|
|
||||||
: <img
|
|
||||||
alt = { t('welcomepage.logo.logoDeepLinking') }
|
|
||||||
className = 'logo'
|
|
||||||
src = 'images/logo-deep-linking.png' />
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div className = 'content'>
|
|
||||||
{
|
|
||||||
showImage
|
|
||||||
? <div className = 'leftColumn'>
|
|
||||||
<div className = 'leftColumnContent'>
|
|
||||||
<div className = 'image' />
|
|
||||||
</div>
|
|
||||||
</div> : null
|
|
||||||
}
|
|
||||||
<div
|
|
||||||
className = 'rightColumn'
|
|
||||||
style = { rightColumnStyle }>
|
|
||||||
<div className = 'rightColumnContent'>
|
|
||||||
<h1 className = 'title'>
|
|
||||||
{
|
|
||||||
t(`${_TNS}.title`,
|
|
||||||
{ app: appName })
|
|
||||||
}
|
|
||||||
</h1>
|
|
||||||
<p className = 'description'>
|
|
||||||
{
|
|
||||||
t(
|
|
||||||
`${_TNS}.${isSupportedBrowser()
|
|
||||||
? 'description'
|
|
||||||
: 'descriptionWithoutWeb'}`,
|
|
||||||
{ app: appName }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
<div className = 'buttons'>
|
|
||||||
<Button
|
|
||||||
label = { t(`${_TNS}.tryAgainButton`) }
|
|
||||||
onClick = { this._onTryAgain }
|
|
||||||
type = { BUTTON_TYPES.SECONDARY } />
|
|
||||||
{
|
|
||||||
isSupportedBrowser()
|
|
||||||
&& <Button
|
|
||||||
label = { t(`${_TNS}.launchWebButton`) }
|
|
||||||
onClick = { this._onLaunchWeb }
|
|
||||||
type = { BUTTON_TYPES.SECONDARY } />
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AtlasKitThemeProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onTryAgain: () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles try again button clicks.
|
|
||||||
*
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_onTryAgain() {
|
|
||||||
sendAnalytics(
|
|
||||||
createDeepLinkingPageEvent(
|
|
||||||
'clicked', 'tryAgainButton', { isMobileBrowser: false }));
|
|
||||||
this.props.dispatch(openDesktopApp());
|
|
||||||
}
|
|
||||||
|
|
||||||
_onLaunchWeb: () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles launch web button clicks.
|
|
||||||
*
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_onLaunchWeb() {
|
|
||||||
sendAnalytics(
|
|
||||||
createDeepLinkingPageEvent(
|
|
||||||
'clicked', 'launchWebButton', { isMobileBrowser: false }));
|
|
||||||
this.props.dispatch(openWebApp());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps (parts of) the Redux state to the associated props for the
|
|
||||||
* {@code DeepLinkingDesktopPage} component.
|
|
||||||
*
|
|
||||||
* @param {Object} state - The Redux state.
|
|
||||||
* @private
|
|
||||||
* @returns {Props}
|
|
||||||
*/
|
|
||||||
function _mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
_deeplinkingCfg: state['features/base/config'].deeplinking || {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default translate(connect(_mapStateToProps)(DeepLinkingDesktopPage));
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
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 { withPixelLineHeight } from '../../base/styles/functions.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,
|
||||||
|
...withPixelLineHeight(theme.typography.heading4)
|
||||||
|
},
|
||||||
|
roomName: {
|
||||||
|
marginBottom: 32,
|
||||||
|
...withPixelLineHeight(theme.typography.heading5)
|
||||||
|
},
|
||||||
|
descriptionLabel: {
|
||||||
|
marginBottom: 32,
|
||||||
|
...withPixelLineHeight(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,
|
||||||
|
...withPixelLineHeight(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 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.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);
|
|
@ -1,299 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import type { Dispatch } from 'redux';
|
|
||||||
|
|
||||||
import { createDeepLinkingPageEvent, sendAnalytics } from '../../analytics';
|
|
||||||
import { IDeeplinkingConfig, IDeeplinkingMobileConfig } from '../../base/config/configType';
|
|
||||||
import { isSupportedMobileBrowser } from '../../base/environment';
|
|
||||||
import { translate } from '../../base/i18n';
|
|
||||||
import { Platform } from '../../base/react';
|
|
||||||
import { connect } from '../../base/redux';
|
|
||||||
import { DialInSummary } from '../../invite';
|
|
||||||
import { openWebApp } from '../actions';
|
|
||||||
import { _TNS } from '../constants';
|
|
||||||
import { generateDeepLinkingURL } from '../functions';
|
|
||||||
import { renderPromotionalFooter } from '../renderPromotionalFooter';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The namespace of the CSS styles of DeepLinkingMobilePage.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
const _SNS = 'deep-linking-mobile';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of the React {@code Component} props of
|
|
||||||
* {@link DeepLinkingMobilePage}.
|
|
||||||
*/
|
|
||||||
type Props = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The deeplinking config.
|
|
||||||
*/
|
|
||||||
_deeplinkingCfg: IDeeplinkingConfig,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Application mobile deeplinking config.
|
|
||||||
*/
|
|
||||||
_mobileConfig: IDeeplinkingMobileConfig,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The deeplinking url.
|
|
||||||
*/
|
|
||||||
_deepLinkingUrl: string,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the conference attempting to being joined.
|
|
||||||
*/
|
|
||||||
_room: string,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The page current url.
|
|
||||||
*/
|
|
||||||
_url: URL,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to dispatch actions from the buttons.
|
|
||||||
*/
|
|
||||||
dispatch: Dispatch<any>,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The function to translate human-readable text.
|
|
||||||
*/
|
|
||||||
t: Function
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* React component representing mobile browser page.
|
|
||||||
*
|
|
||||||
* @class DeepLinkingMobilePage
|
|
||||||
*/
|
|
||||||
class DeepLinkingMobilePage extends Component<Props> {
|
|
||||||
/**
|
|
||||||
* Initializes a new {@code DeepLinkingMobilePage} instance.
|
|
||||||
*
|
|
||||||
* @param {Object} props - The read-only React {@code Component} props with
|
|
||||||
* which the new instance is to be initialized.
|
|
||||||
*/
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
// Bind event handlers so they are only bound once per instance.
|
|
||||||
this._onDownloadApp = this._onDownloadApp.bind(this);
|
|
||||||
this._onLaunchWeb = this._onLaunchWeb.bind(this);
|
|
||||||
this._onOpenApp = this._onOpenApp.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements the Component's componentDidMount method.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
componentDidMount() {
|
|
||||||
sendAnalytics(
|
|
||||||
createDeepLinkingPageEvent(
|
|
||||||
'displayed', 'DeepLinkingMobile', { isMobileBrowser: true }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements React's {@link Component#render()}.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
* @returns {ReactElement}
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
_deeplinkingCfg: { hideLogo },
|
|
||||||
_mobileConfig: { downloadLink, appName },
|
|
||||||
_room,
|
|
||||||
t,
|
|
||||||
_url,
|
|
||||||
_deepLinkingUrl
|
|
||||||
} = this.props;
|
|
||||||
const downloadButtonClassName
|
|
||||||
= `${_SNS}__button ${_SNS}__button_primary`;
|
|
||||||
|
|
||||||
|
|
||||||
const onOpenLinkProperties = downloadLink
|
|
||||||
? {
|
|
||||||
// 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.
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
// 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'
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className = { _SNS }>
|
|
||||||
<div className = 'header'>
|
|
||||||
{
|
|
||||||
hideLogo
|
|
||||||
? null
|
|
||||||
: <img
|
|
||||||
alt = { t('welcomepage.logo.logoDeepLinking') }
|
|
||||||
className = 'logo'
|
|
||||||
src = 'images/logo-deep-linking.png' />
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div className = { `${_SNS}__body` }>
|
|
||||||
<p className = { `${_SNS}__text` }>
|
|
||||||
{ t(`${_TNS}.appNotInstalled`, { app: appName }) }
|
|
||||||
</p>
|
|
||||||
<p className = { `${_SNS}__text` }>
|
|
||||||
{ t(`${_TNS}.ifHaveApp`) }
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
{ ...onOpenLinkProperties }
|
|
||||||
className = { `${_SNS}__href` }
|
|
||||||
href = { _deepLinkingUrl }
|
|
||||||
onClick = { this._onOpenApp }
|
|
||||||
target = '_top'>
|
|
||||||
<button className = { `${_SNS}__button ${_SNS}__button_primary` }>
|
|
||||||
{ t(`${_TNS}.joinInApp`) }
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
<p className = { `${_SNS}__text` }>
|
|
||||||
{ t(`${_TNS}.ifDoNotHaveApp`) }
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
{ ...onOpenLinkProperties }
|
|
||||||
href = { this._generateDownloadURL() }
|
|
||||||
onClick = { this._onDownloadApp }
|
|
||||||
target = '_top'>
|
|
||||||
<button className = { downloadButtonClassName }>
|
|
||||||
{ t(`${_TNS}.downloadApp`) }
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
{
|
|
||||||
isSupportedMobileBrowser()
|
|
||||||
? (
|
|
||||||
<a
|
|
||||||
onClick = { this._onLaunchWeb }
|
|
||||||
target = '_top'>
|
|
||||||
<button className = { downloadButtonClassName }>
|
|
||||||
{ t(`${_TNS}.launchWebButton`) }
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<b>
|
|
||||||
{ t(`${_TNS}.unsupportedBrowser`) }
|
|
||||||
</b>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{ renderPromotionalFooter() }
|
|
||||||
<DialInSummary
|
|
||||||
className = 'deep-linking-dial-in'
|
|
||||||
clickableNumbers = { true }
|
|
||||||
room = { _room }
|
|
||||||
url = { _url } />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the URL for downloading the app.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {string} - The URL for downloading the app.
|
|
||||||
*/
|
|
||||||
_generateDownloadURL() {
|
|
||||||
const { _mobileConfig: { downloadLink, dynamicLink, appScheme } } = this.props;
|
|
||||||
|
|
||||||
if (downloadLink && typeof dynamicLink === 'undefined') {
|
|
||||||
return downloadLink;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
apn,
|
|
||||||
appCode,
|
|
||||||
customDomain,
|
|
||||||
ibi,
|
|
||||||
isi
|
|
||||||
} = dynamicLink || {};
|
|
||||||
|
|
||||||
const domain = customDomain ?? `https://${appCode}.app.goo.gl`;
|
|
||||||
|
|
||||||
return `${domain}/?link=${
|
|
||||||
encodeURIComponent(window.location.href)}&apn=${
|
|
||||||
apn}&ibi=${
|
|
||||||
ibi}&isi=${
|
|
||||||
isi}&ius=${
|
|
||||||
appScheme}&efr=1`;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onDownloadApp: () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles download app button clicks.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_onDownloadApp() {
|
|
||||||
sendAnalytics(
|
|
||||||
createDeepLinkingPageEvent(
|
|
||||||
'clicked', 'downloadAppButton', { isMobileBrowser: true }));
|
|
||||||
}
|
|
||||||
|
|
||||||
_onLaunchWeb: () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles launch web button clicks.
|
|
||||||
*
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_onLaunchWeb() {
|
|
||||||
sendAnalytics(
|
|
||||||
createDeepLinkingPageEvent(
|
|
||||||
'clicked', 'launchWebButton', { isMobileBrowser: true }));
|
|
||||||
this.props.dispatch(openWebApp());
|
|
||||||
}
|
|
||||||
|
|
||||||
_onOpenApp: () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles open app button clicks.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_onOpenApp() {
|
|
||||||
sendAnalytics(
|
|
||||||
createDeepLinkingPageEvent(
|
|
||||||
'clicked', 'openAppButton', { isMobileBrowser: true }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps (parts of) the Redux state to the associated props for the
|
|
||||||
* {@code DeepLinkingMobilePage} component.
|
|
||||||
*
|
|
||||||
* @param {Object} state - The Redux state.
|
|
||||||
* @private
|
|
||||||
* @returns {Props}
|
|
||||||
*/
|
|
||||||
function _mapStateToProps(state) {
|
|
||||||
const { locationURL = {} } = state['features/base/connection'];
|
|
||||||
const { deeplinking } = state['features/base/config'];
|
|
||||||
const mobileConfig = deeplinking?.[Platform.OS] || {};
|
|
||||||
|
|
||||||
return {
|
|
||||||
_deeplinkingCfg: deeplinking || {},
|
|
||||||
_mobileConfig: mobileConfig,
|
|
||||||
_room: decodeURIComponent(state['features/base/conference'].room),
|
|
||||||
_url: locationURL,
|
|
||||||
_deepLinkingUrl: generateDeepLinkingURL(state)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default translate(connect(_mapStateToProps)(DeepLinkingMobilePage));
|
|
|
@ -0,0 +1,241 @@
|
||||||
|
/* 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 { withPixelLineHeight } from '../../base/styles/functions.web';
|
||||||
|
import Button from '../../base/ui/components/web/Button';
|
||||||
|
// @ts-ignore
|
||||||
|
import DialInSummary from '../../invite/components/dial-in-summary/web/DialInSummary';
|
||||||
|
import { openWebApp } from '../actions';
|
||||||
|
// @ts-ignore
|
||||||
|
import { _TNS } from '../constants';
|
||||||
|
// @ts-ignore
|
||||||
|
import { generateDeepLinkingURL } from '../functions';
|
||||||
|
|
||||||
|
|
||||||
|
const PADDINGS = {
|
||||||
|
topBottom: 24,
|
||||||
|
leftRight: 40
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStyles = makeStyles()((theme: Theme) => {
|
||||||
|
return {
|
||||||
|
container: {
|
||||||
|
background: '#1E1E1E',
|
||||||
|
width: '100vw',
|
||||||
|
height: '100vh',
|
||||||
|
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,
|
||||||
|
...withPixelLineHeight(theme.typography.heading5)
|
||||||
|
},
|
||||||
|
roomNameLabel: {
|
||||||
|
...withPixelLineHeight(theme.typography.bodyLongRegularLarge)
|
||||||
|
},
|
||||||
|
joinMeetWrapper: {
|
||||||
|
marginTop: 24,
|
||||||
|
width: '100%'
|
||||||
|
},
|
||||||
|
labelDescription: {
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: 16,
|
||||||
|
...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
|
||||||
|
},
|
||||||
|
linkWrapper: {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 8,
|
||||||
|
width: '100%'
|
||||||
|
},
|
||||||
|
linkLabel: {
|
||||||
|
color: theme.palette.link01,
|
||||||
|
...withPixelLineHeight(theme.typography.bodyLongBoldLarge)
|
||||||
|
},
|
||||||
|
supportedBrowserContent: {
|
||||||
|
marginTop: 16,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
labelOr: {
|
||||||
|
...withPixelLineHeight(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, dynamicLink, appScheme }
|
||||||
|
= (deeplinkingCfg?.[Platform.OS as keyof typeof deeplinkingCfg] || {}) as IDeeplinkingMobileConfig;
|
||||||
|
|
||||||
|
if (downloadLink && typeof dynamicLink === 'undefined') {
|
||||||
|
return downloadLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
apn,
|
||||||
|
appCode,
|
||||||
|
customDomain,
|
||||||
|
ibi,
|
||||||
|
isi
|
||||||
|
} = dynamicLink || {};
|
||||||
|
|
||||||
|
const domain = customDomain ?? `https://${appCode}.app.goo.gl`;
|
||||||
|
|
||||||
|
return `${domain}/?link=${
|
||||||
|
encodeURIComponent(window.location.href)}&apn=${
|
||||||
|
apn}&ibi=${
|
||||||
|
ibi}&isi=${
|
||||||
|
isi}&ius=${
|
||||||
|
appScheme}&efr=1`;
|
||||||
|
}, [ 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 }>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 }
|
||||||
|
room = { room }
|
||||||
|
url = { url } />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default translate(DeepLinkingMobilePage);
|
|
@ -1,9 +0,0 @@
|
||||||
// @flow
|
|
||||||
/**
|
|
||||||
* Method used in order to render a custom promotional footer.
|
|
||||||
*
|
|
||||||
* @returns {HTMLElement}
|
|
||||||
*/
|
|
||||||
export function renderPromotionalFooter() {
|
|
||||||
return null;
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
|
|
||||||
import { translate } from '../../../../base/i18n';
|
|
||||||
import { _formatConferenceIDPin } from '../../../_utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of the React {@code Component} props of {@link ConferenceID}.
|
|
||||||
*/
|
|
||||||
type Props = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The conference ID for dialing in.
|
|
||||||
*/
|
|
||||||
conferenceID: number,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the conference.
|
|
||||||
*/
|
|
||||||
conferenceName: ?string,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked to obtain translated strings.
|
|
||||||
*/
|
|
||||||
t: Function
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a conference ID used as a pin for dialing into a conference.
|
|
||||||
*
|
|
||||||
* @augments Component
|
|
||||||
*/
|
|
||||||
class ConferenceID extends Component<Props> {
|
|
||||||
/**
|
|
||||||
* Implements React's {@link Component#render()}.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
* @returns {ReactElement}
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
const { conferenceID, conferenceName, t } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className = 'dial-in-conference-id'>
|
|
||||||
<div className = 'dial-in-conference-name'>
|
|
||||||
{ conferenceName }
|
|
||||||
</div>
|
|
||||||
<div className = 'dial-in-conference-description'>
|
|
||||||
{ t('info.dialANumber') }
|
|
||||||
</div>
|
|
||||||
<div className = 'dial-in-conference-pin'>
|
|
||||||
{ `${t('info.dialInConferenceID')} ${_formatConferenceIDPin(conferenceID)}` }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default translate(ConferenceID);
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/* eslint-disable lines-around-comment */
|
||||||
|
import { Theme } from '@mui/material';
|
||||||
|
import React from 'react';
|
||||||
|
import { WithTranslation } from 'react-i18next';
|
||||||
|
import { makeStyles } from 'tss-react/mui';
|
||||||
|
|
||||||
|
import { translate } from '../../../../base/i18n/functions';
|
||||||
|
import { withPixelLineHeight } from '../../../../base/styles/functions.web';
|
||||||
|
// @ts-ignore
|
||||||
|
import { _formatConferenceIDPin } from '../../../_utils';
|
||||||
|
|
||||||
|
|
||||||
|
interface IProps extends WithTranslation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The conference id.
|
||||||
|
*/
|
||||||
|
conferenceID?: string | number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The conference name.
|
||||||
|
*/
|
||||||
|
conferenceName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles()((theme: Theme) => {
|
||||||
|
return {
|
||||||
|
container: {
|
||||||
|
marginTop: 32,
|
||||||
|
maxWidth: 310,
|
||||||
|
padding: '16px 12px',
|
||||||
|
background: theme.palette.ui02,
|
||||||
|
textAlign: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
borderRadius: 6
|
||||||
|
},
|
||||||
|
confNameLabel: {
|
||||||
|
...withPixelLineHeight(theme.typography.heading6),
|
||||||
|
marginBottom: 18,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis'
|
||||||
|
},
|
||||||
|
descriptionLabel: {
|
||||||
|
...withPixelLineHeight(theme.typography.bodyShortRegularLarge),
|
||||||
|
marginBottom: 18
|
||||||
|
},
|
||||||
|
separator: {
|
||||||
|
width: '100%',
|
||||||
|
height: 1,
|
||||||
|
background: theme.palette.ui04,
|
||||||
|
marginBottom: 18
|
||||||
|
},
|
||||||
|
pinLabel: {
|
||||||
|
...withPixelLineHeight(theme.typography.heading6)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const ConferenceID: React.FC<IProps> = ({ conferenceID, t }) => {
|
||||||
|
const { classes: styles } = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className = { styles.container }>
|
||||||
|
<div className = { styles.descriptionLabel }>
|
||||||
|
To join the meeting via phone, dial one of these numbers and then enter the pin
|
||||||
|
</div>
|
||||||
|
<div className = { styles.separator } />
|
||||||
|
<div className = { styles.pinLabel }>
|
||||||
|
{ `${t('info.dialInConferenceID')} ${_formatConferenceIDPin(conferenceID ?? '')}` }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default translate(ConferenceID);
|
|
@ -1,8 +1,13 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
|
||||||
|
import { Theme } from '@mui/material';
|
||||||
|
import { withStyles } from '@mui/styles';
|
||||||
|
import clsx from 'clsx';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import { translate } from '../../../../base/i18n';
|
import { translate } from '../../../../base/i18n';
|
||||||
|
import { withPixelLineHeight } from '../../../../base/styles/functions.web';
|
||||||
import { getDialInConferenceID, getDialInNumbers } from '../../../_utils';
|
import { getDialInConferenceID, getDialInNumbers } from '../../../_utils';
|
||||||
|
|
||||||
import ConferenceID from './ConferenceID';
|
import ConferenceID from './ConferenceID';
|
||||||
|
@ -20,6 +25,11 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
className: string,
|
className: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object containing the CSS classes.
|
||||||
|
*/
|
||||||
|
classes: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not numbers should include links with the telephone protocol.
|
* Whether or not numbers should include links with the telephone protocol.
|
||||||
*/
|
*/
|
||||||
|
@ -30,6 +40,16 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
room: string,
|
room: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the dial in summary container is scrollable.
|
||||||
|
*/
|
||||||
|
scrollable: Boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the room name should show as title.
|
||||||
|
*/
|
||||||
|
showTitle?: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The url where we were loaded.
|
* The url where we were loaded.
|
||||||
*/
|
*/
|
||||||
|
@ -72,6 +92,26 @@ type State = {
|
||||||
numbersEnabled: ?boolean
|
numbersEnabled: ?boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = (theme: Theme) => {
|
||||||
|
return {
|
||||||
|
hasNumbers: {
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
background: '#1E1E1E',
|
||||||
|
color: theme.palette.text01
|
||||||
|
},
|
||||||
|
scrollable: {
|
||||||
|
height: '100vh',
|
||||||
|
overflowY: 'scroll'
|
||||||
|
},
|
||||||
|
roomName: {
|
||||||
|
margin: '40px auto 8px',
|
||||||
|
...withPixelLineHeight(theme.typography.heading5)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays a page listing numbers for dialing into a conference and pin to
|
* Displays a page listing numbers for dialing into a conference and pin to
|
||||||
* the a specific conference.
|
* the a specific conference.
|
||||||
|
@ -136,24 +176,27 @@ class DialInSummary extends Component<Props, State> {
|
||||||
let contents;
|
let contents;
|
||||||
|
|
||||||
const { conferenceID, error, loading, numbersEnabled } = this.state;
|
const { conferenceID, error, loading, numbersEnabled } = this.state;
|
||||||
|
const { classes, showTitle, room, clickableNumbers, scrollable, t } = this.props;
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
contents = '';
|
contents = '';
|
||||||
} else if (numbersEnabled === false) {
|
} else if (numbersEnabled === false) {
|
||||||
contents = this.props.t('info.dialInNotSupported');
|
contents = t('info.dialInNotSupported');
|
||||||
} else if (error) {
|
} else if (error) {
|
||||||
contents = error;
|
contents = error;
|
||||||
} else {
|
} else {
|
||||||
className = 'has-numbers';
|
className = clsx(classes.hasNumbers, scrollable && classes.scrollable);
|
||||||
contents = [
|
contents = [
|
||||||
conferenceID
|
conferenceID
|
||||||
? <ConferenceID
|
? <>
|
||||||
|
{ showTitle && <div className = { classes.roomName }>{ room }</div> }
|
||||||
|
<ConferenceID
|
||||||
conferenceID = { conferenceID }
|
conferenceID = { conferenceID }
|
||||||
conferenceName = { this.props.room }
|
conferenceName = { room }
|
||||||
key = 'conferenceID' />
|
key = 'conferenceID' />
|
||||||
: null,
|
</> : null,
|
||||||
<NumbersList
|
<NumbersList
|
||||||
clickableNumbers = { this.props.clickableNumbers }
|
clickableNumbers = { clickableNumbers }
|
||||||
conferenceID = { conferenceID }
|
conferenceID = { conferenceID }
|
||||||
key = 'numbers'
|
key = 'numbers'
|
||||||
numbers = { this.state.numbers } />
|
numbers = { this.state.numbers } />
|
||||||
|
@ -161,7 +204,7 @@ class DialInSummary extends Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className = { `${this.props.className} ${className}` }>
|
<div className = { className }>
|
||||||
{ contents }
|
{ contents }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -272,4 +315,4 @@ class DialInSummary extends Component<Props, State> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate(DialInSummary);
|
export default translate(withStyles(styles)(DialInSummary));
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { BaseApp } from '../../../../base/app';
|
||||||
|
import { isMobileBrowser } from '../../../../base/environment/utils';
|
||||||
|
import GlobalStyles from '../../../../base/ui/components/GlobalStyles.web';
|
||||||
|
import JitsiThemeProvider from '../../../../base/ui/components/JitsiThemeProvider.web';
|
||||||
|
import { parseURLParams } from '../../../../base/util';
|
||||||
|
import { DIAL_IN_INFO_PAGE_PATH_NAME } from '../../../constants';
|
||||||
|
import NoRoomError from '../../dial-in-info-page/NoRoomError.web';
|
||||||
|
|
||||||
|
import DialInSummary from './DialInSummary';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper application for prejoin.
|
||||||
|
*
|
||||||
|
* @augments BaseApp
|
||||||
|
*/
|
||||||
|
export default class DialInSummaryApp extends BaseApp {
|
||||||
|
/**
|
||||||
|
* The deferred for the initialisation {{promise, resolve, reject}}.
|
||||||
|
*/
|
||||||
|
_init: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to {@link Prejoin} upon mount.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
async componentDidMount() {
|
||||||
|
await super.componentDidMount();
|
||||||
|
|
||||||
|
const { room } = parseURLParams(window.location, true, 'search');
|
||||||
|
const { href } = window.location;
|
||||||
|
const ix = href.indexOf(DIAL_IN_INFO_PAGE_PATH_NAME);
|
||||||
|
const url = (ix > 0 ? href.substring(0, ix) : href) + room;
|
||||||
|
|
||||||
|
super._navigate({
|
||||||
|
component: () => (<>
|
||||||
|
{room
|
||||||
|
? <DialInSummary
|
||||||
|
className = 'dial-in-page'
|
||||||
|
clickableNumbers = { isMobileBrowser() }
|
||||||
|
room = { decodeURIComponent(room) }
|
||||||
|
scrollable = { true }
|
||||||
|
showTitle = { true }
|
||||||
|
url = { url } />
|
||||||
|
: <NoRoomError className = 'dial-in-page' />}
|
||||||
|
</>)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides the parent method to inject {@link AtlasKitThemeProvider} as
|
||||||
|
* the top most component.
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
_createMainElement(component, props) {
|
||||||
|
return (
|
||||||
|
<JitsiThemeProvider>
|
||||||
|
<AtlasKitThemeProvider mode = 'dark'>
|
||||||
|
<GlobalStyles />
|
||||||
|
{super._createMainElement(component, props)}
|
||||||
|
</AtlasKitThemeProvider>
|
||||||
|
</JitsiThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the platform specific dialog container.
|
||||||
|
*
|
||||||
|
* @returns {React$Element}
|
||||||
|
*/
|
||||||
|
_renderDialogContainer() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,244 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
|
|
||||||
import { translate } from '../../../../base/i18n';
|
|
||||||
import { Icon, IconPhoneRinging } from '../../../../base/icons';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not numbers should include links with the telephone protocol.
|
|
||||||
*/
|
|
||||||
clickableNumbers: boolean,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The conference ID for dialing in.
|
|
||||||
*/
|
|
||||||
conferenceID: number,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The phone numbers to display. Can be an array of number Objects or an
|
|
||||||
* object with countries as keys and an array of numbers as values.
|
|
||||||
*/
|
|
||||||
numbers: { [string]: Array<string> } | Array<Object>,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked to obtain translated strings.
|
|
||||||
*/
|
|
||||||
t: Function
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a table with phone numbers to dial in to a conference.
|
|
||||||
*
|
|
||||||
* @augments Component
|
|
||||||
*/
|
|
||||||
class NumbersList extends Component<Props> {
|
|
||||||
/**
|
|
||||||
* Implements React's {@link Component#render()}.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
* @returns {ReactElement}
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
const { numbers } = this.props;
|
|
||||||
|
|
||||||
return this._renderWithCountries(numbers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders rows of countries and associated phone numbers.
|
|
||||||
*
|
|
||||||
* @param {Object|Array<Object>} numbersMapping - An object with country
|
|
||||||
* names as keys and values as arrays of phone numbers.
|
|
||||||
* @private
|
|
||||||
* @returns {ReactElement[]}
|
|
||||||
*/
|
|
||||||
_renderWithCountries(
|
|
||||||
numbersMapping: { numbers: Array<string> } | Array<Object>) {
|
|
||||||
const { t } = this.props;
|
|
||||||
let hasFlags = false, numbers;
|
|
||||||
|
|
||||||
if (Array.isArray(numbersMapping)) {
|
|
||||||
hasFlags = true;
|
|
||||||
numbers = numbersMapping.reduce(
|
|
||||||
(resultNumbers, number) => {
|
|
||||||
// The i18n-iso-countries package insists on upper case.
|
|
||||||
const countryCode = number.countryCode.toUpperCase();
|
|
||||||
|
|
||||||
let countryName;
|
|
||||||
|
|
||||||
if (countryCode === 'SIP') {
|
|
||||||
countryName = t('info.sip');
|
|
||||||
} else {
|
|
||||||
countryName = t(`countries:countries.${countryCode}`);
|
|
||||||
|
|
||||||
// Some countries have multiple names as US ['United States of America', 'USA']
|
|
||||||
// choose the first one if that is the case
|
|
||||||
if (!countryName) {
|
|
||||||
countryName = t(`countries:countries.${countryCode}.0`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resultNumbers[countryName]) {
|
|
||||||
resultNumbers[countryName].push(number);
|
|
||||||
} else {
|
|
||||||
resultNumbers[countryName] = [ number ];
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultNumbers;
|
|
||||||
}, {});
|
|
||||||
} else {
|
|
||||||
numbers = {};
|
|
||||||
|
|
||||||
for (const [ country, numbersArray ]
|
|
||||||
of Object.entries(numbersMapping.numbers)) {
|
|
||||||
|
|
||||||
if (Array.isArray(numbersArray)) {
|
|
||||||
/* eslint-disable arrow-body-style */
|
|
||||||
const formattedNumbers = numbersArray.map(number => ({
|
|
||||||
formattedNumber: number
|
|
||||||
}));
|
|
||||||
/* eslint-enable arrow-body-style */
|
|
||||||
|
|
||||||
numbers[country] = formattedNumbers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = [];
|
|
||||||
|
|
||||||
Object.keys(numbers).forEach((countryName: string) => {
|
|
||||||
const numbersArray = numbers[countryName];
|
|
||||||
|
|
||||||
rows.push(
|
|
||||||
<tr
|
|
||||||
className = 'number-group'
|
|
||||||
key = { countryName }>
|
|
||||||
{ this._renderFlag(numbersArray[0].countryCode) }
|
|
||||||
<td className = 'country' >{ countryName }</td>
|
|
||||||
<td className = 'numbers-list-column'>
|
|
||||||
{ this._renderNumbersList(numbersArray) }
|
|
||||||
</td>
|
|
||||||
<td className = 'toll-free-list-column' >
|
|
||||||
{ this._renderNumbersTollFreeList(numbersArray) }
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<table className = 'dial-in-numbers-list'>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
{ hasFlags ? <th /> : null}
|
|
||||||
<th>{ t('info.country') }</th>
|
|
||||||
<th>{ t('info.numbers') }</th>
|
|
||||||
<th />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className = 'dial-in-numbers-body'>
|
|
||||||
{ rows }
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a div container for a flag for the country of the phone number.
|
|
||||||
*
|
|
||||||
* @param {string} countryCode - The country code flag to display.
|
|
||||||
* @private
|
|
||||||
* @returns {ReactElement}
|
|
||||||
*/
|
|
||||||
_renderFlag(countryCode) {
|
|
||||||
if (countryCode) {
|
|
||||||
return (
|
|
||||||
<td className = 'flag-cell'>
|
|
||||||
{countryCode === 'SIP'
|
|
||||||
? <Icon src = { IconPhoneRinging } />
|
|
||||||
: <i className = { `flag iti-flag ${countryCode}` } />
|
|
||||||
}
|
|
||||||
</td>);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a div container for a phone number.
|
|
||||||
*
|
|
||||||
* @param {Array} numbers - The phone number to display.
|
|
||||||
* @private
|
|
||||||
* @returns {ReactElement[]}
|
|
||||||
*/
|
|
||||||
_renderNumbersList(numbers) {
|
|
||||||
const numbersListItems = numbers.map(number =>
|
|
||||||
(<li
|
|
||||||
className = 'dial-in-number'
|
|
||||||
key = { number.formattedNumber }>
|
|
||||||
{ this._renderNumberLink(number.formattedNumber) }
|
|
||||||
</li>));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ul className = 'numbers-list'>
|
|
||||||
{ numbersListItems }
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders list with a toll free text on the position where there is a
|
|
||||||
* number marked as toll free.
|
|
||||||
*
|
|
||||||
* @param {Array} numbers - The phone number that are displayed.
|
|
||||||
* @private
|
|
||||||
* @returns {ReactElement[]}
|
|
||||||
*/
|
|
||||||
_renderNumbersTollFreeList(numbers) {
|
|
||||||
const { t } = this.props;
|
|
||||||
|
|
||||||
const tollNumbersListItems = numbers.map(number =>
|
|
||||||
(<li
|
|
||||||
className = 'toll-free'
|
|
||||||
key = { number.formattedNumber }>
|
|
||||||
{ number.tollFree ? t('info.dialInTollFree') : '' }
|
|
||||||
</li>));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ul className = 'toll-free-list'>
|
|
||||||
{ tollNumbersListItems }
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a ReactElement for displaying a telephone number. If the
|
|
||||||
* component prop {@code clickableNumbers} is true, then the number will
|
|
||||||
* have a link with the telephone protocol.
|
|
||||||
*
|
|
||||||
* @param {string} number - The phone number to display.
|
|
||||||
* @private
|
|
||||||
* @returns {ReactElement}
|
|
||||||
*/
|
|
||||||
_renderNumberLink(number) {
|
|
||||||
if (this.props.clickableNumbers) {
|
|
||||||
// Url encode # to %23, Android phone was cutting the # after
|
|
||||||
// clicking it.
|
|
||||||
// Seems that using ',' and '%23' works on iOS and Android.
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
href = { `tel:${number},${this.props.conferenceID}%23` }
|
|
||||||
key = { number } >
|
|
||||||
{ number }
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default translate(NumbersList);
|
|
|
@ -0,0 +1,209 @@
|
||||||
|
/* eslint-disable lines-around-comment */
|
||||||
|
import countries from 'i18n-iso-countries';
|
||||||
|
import en from 'i18n-iso-countries/langs/en.json';
|
||||||
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
import { WithTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { translate } from '../../../../base/i18n/functions';
|
||||||
|
// @ts-ignore
|
||||||
|
import { Icon, IconSip } from '../../../../base/icons';
|
||||||
|
|
||||||
|
countries.registerLocale(en);
|
||||||
|
|
||||||
|
interface INormalizedNumber {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The country code.
|
||||||
|
*/
|
||||||
|
countryCode?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The formatted number.
|
||||||
|
*/
|
||||||
|
formattedNumber: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the number is toll-free.
|
||||||
|
*/
|
||||||
|
tollFree?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface INumbersMapping {
|
||||||
|
[countryName: string]: Array<INormalizedNumber>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProps extends WithTranslation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not numbers should include links with the telephone protocol.
|
||||||
|
*/
|
||||||
|
clickableNumbers: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The conference ID for dialing in.
|
||||||
|
*/
|
||||||
|
conferenceID: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The phone numbers to display. Can be an array of number Objects or an
|
||||||
|
* object with countries as keys and an array of numbers as values.
|
||||||
|
*/
|
||||||
|
numbers: INumbersMapping;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const NumbersList: React.FC<IProps> = ({ t, conferenceID, clickableNumbers, numbers: numbersMapping }) => {
|
||||||
|
const renderFlag = useCallback((countryCode: string) => {
|
||||||
|
if (countryCode) {
|
||||||
|
return (
|
||||||
|
<td className = 'flag-cell'>
|
||||||
|
{countryCode === 'SIP'
|
||||||
|
? <Icon src = { IconSip } />
|
||||||
|
: <i className = { `flag iti-flag ${countryCode}` } />
|
||||||
|
}
|
||||||
|
</td>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderNumberLink = useCallback((number: string) => {
|
||||||
|
if (clickableNumbers) {
|
||||||
|
// Url encode # to %23, Android phone was cutting the # after
|
||||||
|
// clicking it.
|
||||||
|
// Seems that using ',' and '%23' works on iOS and Android.
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href = { `tel:${number},${conferenceID}%23` }
|
||||||
|
key = { number } >
|
||||||
|
{number}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return number;
|
||||||
|
}, [ conferenceID, clickableNumbers ]);
|
||||||
|
|
||||||
|
const renderNumbersList = useCallback((numbers: Array<INormalizedNumber>) => {
|
||||||
|
const numbersListItems = numbers.map(number =>
|
||||||
|
(<li
|
||||||
|
className = 'dial-in-number'
|
||||||
|
key = { number.formattedNumber }>
|
||||||
|
{renderNumberLink(number.formattedNumber)}
|
||||||
|
</li>));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className = 'numbers-list'>
|
||||||
|
{numbersListItems}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderNumbersTollFreeList = useCallback((numbers: Array<INormalizedNumber>) => {
|
||||||
|
const tollNumbersListItems = numbers.map(number =>
|
||||||
|
(<li
|
||||||
|
className = 'toll-free'
|
||||||
|
key = { number.formattedNumber }>
|
||||||
|
{number.tollFree ? t('info.dialInTollFree') : ''}
|
||||||
|
</li>));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className = 'toll-free-list'>
|
||||||
|
{tollNumbersListItems}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderNumbers = useMemo(() => {
|
||||||
|
let numbers: INumbersMapping;
|
||||||
|
|
||||||
|
if (!numbersMapping) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(numbersMapping)) {
|
||||||
|
numbers = numbersMapping.reduce(
|
||||||
|
(resultNumbers: any, number: any) => {
|
||||||
|
// The i18n-iso-countries package insists on upper case.
|
||||||
|
const countryCode = number.countryCode.toUpperCase();
|
||||||
|
let countryName;
|
||||||
|
|
||||||
|
if (countryCode === 'SIP') {
|
||||||
|
countryName = t('info.sip');
|
||||||
|
} else {
|
||||||
|
countryName = t(`countries:countries.${countryCode}`);
|
||||||
|
|
||||||
|
// Some countries have multiple names as US ['United States of America', 'USA']
|
||||||
|
// choose the first one if that is the case
|
||||||
|
if (!countryName) {
|
||||||
|
countryName = t(`countries:countries.${countryCode}.0`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultNumbers[countryName]) {
|
||||||
|
resultNumbers[countryName].push(number);
|
||||||
|
} else {
|
||||||
|
resultNumbers[countryName] = [ number ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultNumbers;
|
||||||
|
}, {});
|
||||||
|
} else {
|
||||||
|
numbers = {};
|
||||||
|
|
||||||
|
for (const [ country, numbersArray ]
|
||||||
|
of Object.entries(numbersMapping.numbers)) {
|
||||||
|
|
||||||
|
if (Array.isArray(numbersArray)) {
|
||||||
|
/* eslint-disable arrow-body-style */
|
||||||
|
const formattedNumbers = numbersArray.map(number => ({
|
||||||
|
formattedNumber: number
|
||||||
|
}));
|
||||||
|
/* eslint-enable arrow-body-style */
|
||||||
|
|
||||||
|
numbers[country] = formattedNumbers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows: [JSX.Element] = [] as unknown as [JSX.Element];
|
||||||
|
|
||||||
|
Object.keys(numbers).forEach((countryName: string) => {
|
||||||
|
const numbersArray: Array<INormalizedNumber> = numbers[countryName];
|
||||||
|
const countryCode = numbersArray[0].countryCode
|
||||||
|
|| countries.getAlpha2Code(countryName, 'en')?.toUpperCase()
|
||||||
|
|| countryName;
|
||||||
|
|
||||||
|
rows.push(
|
||||||
|
<>
|
||||||
|
<tr
|
||||||
|
key = { countryName }>
|
||||||
|
{renderFlag(countryCode)}
|
||||||
|
<td className = 'country' >{countryName}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td />
|
||||||
|
<td className = 'numbers-list-column'>
|
||||||
|
{renderNumbersList(numbersArray)}
|
||||||
|
</td>
|
||||||
|
<td className = 'toll-free-list-column' >
|
||||||
|
{renderNumbersTollFreeList(numbersArray)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}, [ numbersMapping ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table className = 'dial-in-numbers-list'>
|
||||||
|
<tbody className = 'dial-in-numbers-body'>
|
||||||
|
{renderNumbers}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default translate(NumbersList);
|
|
@ -19,6 +19,7 @@ import { getDefaultURL } from '../../../app/functions.native';
|
||||||
import { IReduxState } from '../../../app/types';
|
import { IReduxState } from '../../../app/types';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { Avatar } from '../../../base/avatar';
|
import { Avatar } from '../../../base/avatar';
|
||||||
|
import { getLegalUrls } from '../../../base/config/functions.native';
|
||||||
import { translate } from '../../../base/i18n/functions';
|
import { translate } from '../../../base/i18n/functions';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||||
|
@ -46,19 +47,6 @@ import styles from './styles';
|
||||||
*/
|
*/
|
||||||
const { AppInfo } = NativeModules;
|
const { AppInfo } = NativeModules;
|
||||||
|
|
||||||
/**
|
|
||||||
* The URL at which the terms (of service/use) are available to the user.
|
|
||||||
*/
|
|
||||||
const TERMS_URL = 'https://jitsi.org/meet/terms';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The URL at which the privacy policy is available to the user.
|
|
||||||
*/
|
|
||||||
const PRIVACY_URL = 'https://jitsi.org/meet/privacy';
|
|
||||||
|
|
||||||
|
|
||||||
const DEFAULT_HELP_CENTRE_URL = 'https://web-cdn.jitsi.net/faq/meet-faq.html';
|
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -119,11 +107,13 @@ interface IState {
|
||||||
interface IProps extends WithTranslation {
|
interface IProps extends WithTranslation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The URL for when the help link.
|
* The legal URL's.
|
||||||
*
|
|
||||||
* @protected
|
|
||||||
*/
|
*/
|
||||||
_helpCentreUrl: string;
|
_legalUrls: {
|
||||||
|
helpCentre: string;
|
||||||
|
privacy: string;
|
||||||
|
terms: string;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the local participant.
|
* The ID of the local participant.
|
||||||
|
@ -710,7 +700,7 @@ class SettingsView extends Component<IProps, IState> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onShowHelpPressed() {
|
_onShowHelpPressed() {
|
||||||
Linking.openURL(this.props._helpCentreUrl);
|
Linking.openURL(this.props._legalUrls.helpCentre);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -719,7 +709,7 @@ class SettingsView extends Component<IProps, IState> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onShowPrivacyPressed() {
|
_onShowPrivacyPressed() {
|
||||||
Linking.openURL(PRIVACY_URL);
|
Linking.openURL(this.props._legalUrls.privacy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -728,7 +718,7 @@ class SettingsView extends Component<IProps, IState> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onShowTermsPressed() {
|
_onShowTermsPressed() {
|
||||||
Linking.openURL(TERMS_URL);
|
Linking.openURL(this.props._legalUrls.terms);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -795,7 +785,7 @@ function _mapStateToProps(state: IReduxState) {
|
||||||
const localParticipant = getLocalParticipant(state);
|
const localParticipant = getLocalParticipant(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_helpCentreUrl: state['features/base/config'].helpCentreURL || DEFAULT_HELP_CENTRE_URL,
|
_legalUrls: getLegalUrls(state),
|
||||||
_localParticipantId: localParticipant?.id,
|
_localParticipantId: localParticipant?.id,
|
||||||
_serverURL: getDefaultURL(state),
|
_serverURL: getDefaultURL(state),
|
||||||
_serverURLChangeEnabled: isServerURLChangeEnabled(state),
|
_serverURLChangeEnabled: isServerURLChangeEnabled(state),
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { App } from './features/app/components';
|
||||||
import { getLogger } from './features/base/logging/functions';
|
import { getLogger } from './features/base/logging/functions';
|
||||||
import { Platform } from './features/base/react';
|
import { Platform } from './features/base/react';
|
||||||
import { getJitsiMeetGlobalNS } from './features/base/util';
|
import { getJitsiMeetGlobalNS } from './features/base/util';
|
||||||
|
import DialInSummaryApp from './features/invite/components/dial-in-summary/web/DialInSummaryApp';
|
||||||
import PrejoinApp from './features/prejoin/components/web/PrejoinApp';
|
import PrejoinApp from './features/prejoin/components/web/PrejoinApp';
|
||||||
|
|
||||||
const logger = getLogger('index.web');
|
const logger = getLogger('index.web');
|
||||||
|
@ -43,7 +44,8 @@ const globalNS = getJitsiMeetGlobalNS();
|
||||||
|
|
||||||
globalNS.entryPoints = {
|
globalNS.entryPoints = {
|
||||||
APP: App,
|
APP: App,
|
||||||
PREJOIN: PrejoinApp
|
PREJOIN: PrejoinApp,
|
||||||
|
DIALIN: DialInSummaryApp
|
||||||
};
|
};
|
||||||
|
|
||||||
globalNS.renderEntryPoint = ({
|
globalNS.renderEntryPoint = ({
|
||||||
|
|
|
@ -1,17 +1,29 @@
|
||||||
<html>
|
<html xmlns="http://www.w3.org/1999/html">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<!--#include virtual="/base.html" -->
|
<!--#include virtual="/base.html" -->
|
||||||
<!--#include virtual="/title.html" -->
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="css/all.css">
|
<link rel="stylesheet" href="css/all.css">
|
||||||
</head>
|
<script>
|
||||||
<body>
|
window.EXCALIDRAW_ASSET_PATH = 'libs/';
|
||||||
<div id="react"></div>
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
if (!JitsiMeetJS.app) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JitsiMeetJS.app.renderEntryPoint({
|
||||||
|
Component: JitsiMeetJS.app.entryPoints.DIALIN
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<!--#include virtual="/title.html" -->
|
||||||
<script><!--#include virtual="/config.js" --></script>
|
<script><!--#include virtual="/config.js" --></script>
|
||||||
<script><!--#include virtual="/interface_config.js" --></script>
|
<script><!--#include virtual="/interface_config.js" --></script>
|
||||||
<script src="libs/dial_in_info_bundle.min.js"></script>
|
<script src="libs/lib-jitsi-meet.min.js?v=139"></script>
|
||||||
|
<script src="libs/app.bundle.min.js?v=139"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="react" role="main"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -322,20 +322,6 @@ module.exports = (_env, argv) => {
|
||||||
],
|
],
|
||||||
performance: getPerformanceHints(perfHintOptions, 800 * 1024)
|
performance: getPerformanceHints(perfHintOptions, 800 * 1024)
|
||||||
}),
|
}),
|
||||||
Object.assign({}, config, {
|
|
||||||
entry: {
|
|
||||||
'dial_in_info_bundle': './react/features/invite/components/dial-in-info-page'
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
...config.plugins,
|
|
||||||
...getBundleAnalyzerPlugin(analyzeBundle, 'dial_in_info'),
|
|
||||||
new webpack.IgnorePlugin({
|
|
||||||
resourceRegExp: /^\.\/locale$/,
|
|
||||||
contextRegExp: /moment$/
|
|
||||||
})
|
|
||||||
],
|
|
||||||
performance: getPerformanceHints(perfHintOptions, 500 * 1024)
|
|
||||||
}),
|
|
||||||
Object.assign({}, config, {
|
Object.assign({}, config, {
|
||||||
entry: {
|
entry: {
|
||||||
'do_external_connect': './connection_optimization/do_external_connect.js'
|
'do_external_connect': './connection_optimization/do_external_connect.js'
|
||||||
|
|
Loading…
Reference in New Issue