Browse Source

Introduce internationalisation

pull/2/head
freddytuxworth 2 years ago committed by GitHub
parent
commit
2efa8b057e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      app/features/navbar/components/HelpButton.js
  2. 78
      app/features/onboarding/components/AlwaysOnTopWindowSpotlight.js
  3. 70
      app/features/onboarding/components/ConferenceURLSpotlight.js
  4. 68
      app/features/onboarding/components/EmailSettingSpotlight.js
  5. 69
      app/features/onboarding/components/NameSettingSpotlight.js
  6. 4
      app/features/onboarding/components/Onboarding.js
  7. 19
      app/features/onboarding/components/OnboardingModal.js
  8. 68
      app/features/onboarding/components/OnboardingSpotlight.js
  9. 69
      app/features/onboarding/components/ServerSettingSpotlight.js
  10. 67
      app/features/onboarding/components/ServerTimeoutSpotlight.js
  11. 68
      app/features/onboarding/components/SettingsDrawerSpotlight.js
  12. 69
      app/features/onboarding/components/StartMutedTogglesSpotlight.js
  13. 9
      app/features/onboarding/components/index.js
  14. 77
      app/features/onboarding/constants.js
  15. 84
      app/features/settings/components/AlwaysOnTopWindowToggle.js
  16. 13
      app/features/settings/components/ServerTimeoutField.js
  17. 16
      app/features/settings/components/ServerURLField.js
  18. 66
      app/features/settings/components/SettingToggle.js
  19. 41
      app/features/settings/components/SettingsDrawer.js
  20. 145
      app/features/settings/components/StartMutedToggles.js
  21. 15
      app/features/welcome/components/Welcome.js
  22. 24
      app/i18n/index.js
  23. 38
      app/i18n/lang/en.json
  24. 1
      app/index.html
  25. 9
      app/index.js
  26. 3
      app/preload/preload.js
  27. 52
      package-lock.json
  28. 6
      package.json

21
app/features/navbar/components/HelpButton.js

@ -4,6 +4,7 @@ import Droplist, { Item, Group } from '@atlaskit/droplist';
import HelpIcon from '@atlaskit/icon/glyph/question-circle';
import React, { Component } from 'react';
import { withTranslation } from 'react-i18next';
import config from '../../config';
import { openExternalLink } from '../../utils';
@ -20,7 +21,7 @@ type State = {
/**
* Help button for Navigation Bar.
*/
export default class HelpButton extends Component< *, State> {
class HelpButton extends Component<*, State> {
/**
* Initializes a new {@code HelpButton} instance.
*
@ -87,6 +88,8 @@ export default class HelpButton extends Component< *, State> {
* @returns {ReactElement}
*/
render() {
const { t } = this.props;
return (
<Droplist
isOpen = { this.state.droplistOpen }
@ -94,27 +97,29 @@ export default class HelpButton extends Component< *, State> {
onOpenChange = { this._onOpenChange }
position = 'right bottom'
trigger = { <HelpIcon /> }>
<Group heading = 'Help'>
<Group heading = { t('help') } >
<Item onActivate = { this._onTermsClick }>
Terms
{ t('termsLink') }
</Item>
<Item onActivate = { this._onPrivacyClick }>
Privacy
{ t('privacyLink') }
</Item>
<Item onActivate = { this._onSendFeedbackClick }>
Send Feedback
{ t('sendFeedbackLink') }
</Item>
<Item onActivate = { this._onAboutClick }>
About
{ t('aboutLink') }
</Item>
<Item onActivate = { this._onSourceClick }>
Source
{ t('sourceLink') }
</Item>
<Item>
Version: { version }
{ t('versionLabel', { version }) }
</Item>
</Group>
</Droplist>
);
}
}
export default withTranslation()(HelpButton);

78
app/features/onboarding/components/AlwaysOnTopWindowSpotlight.js

@ -1,78 +0,0 @@
// @flow
import { Spotlight } from '@atlaskit/onboarding';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { closeDrawer } from '../../navbar';
import { continueOnboarding } from '../actions';
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
};
/**
* Always on Top Windows Spotlight Component.
*/
class AlwaysOnTopWindowSpotlight extends Component<Props, *> {
/**
* Initializes a new {@code StartMutedTogglesSpotlight} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._next = this._next.bind(this);
}
/**
* Render function of component.
*
* @returns {ReactElement}
*/
render() {
return (
<Spotlight
actions = { [
{
onClick: this._next,
text: 'Next'
}
] }
dialogPlacement = 'top right'
target = { 'always-on-top-window' } >
You can toggle whether you want to enable the "always-on-top" window,
which is displayed when the main window loses focus.
This will be applied to all conferences.
</Spotlight>
);
}
_next: (*) => void;
/**
* Close the spotlight component.
*
* @returns {void}
*/
_next() {
const { dispatch } = this.props;
dispatch(continueOnboarding());
// FIXME: find a better way to do this.
setTimeout(() => {
dispatch(closeDrawer());
}, 300);
}
}
export default connect()(AlwaysOnTopWindowSpotlight);

70
app/features/onboarding/components/ConferenceURLSpotlight.js

@ -1,70 +0,0 @@
// @flow
import { Spotlight } from '@atlaskit/onboarding';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { continueOnboarding } from '../actions';
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
};
/**
* Conference URL Spotlight Component.
*/
class ConferenceURLSpotlight extends Component<Props, *> {
/**
* Initializes a new {@code ComponentURLSpotlight} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._next = this._next.bind(this);
}
/**
* Render function of component.
*
* @returns {ReactElement}
*/
render() {
return (
<Spotlight
actions = { [
{
onClick: this._next,
text: 'Next'
}
] }
dialogPlacement = 'bottom center'
target = { 'conference-url' } >
Enter the name (or full URL) of the room you want to join. You
may make a name up, just let others know so they enter the same
name.
</Spotlight>
);
}
_next: (*) => void;
/**
* Close the spotlight component.
*
* @returns {void}
*/
_next() {
this.props.dispatch(continueOnboarding());
}
}
export default connect()(ConferenceURLSpotlight);

68
app/features/onboarding/components/EmailSettingSpotlight.js

@ -1,68 +0,0 @@
// @flow
import { Spotlight } from '@atlaskit/onboarding';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { continueOnboarding } from '../actions';
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
};
/**
* Email Setting Spotlight Component.
*/
class EmailSettingSpotlight extends Component<Props, *> {
/**
* Initializes a new {@code EmailSettingSpotlight} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._next = this._next.bind(this);
}
/**
* Render function of component.
*
* @returns {ReactElement}
*/
render() {
return (
<Spotlight
actions = { [
{
onClick: this._next,
text: 'Next'
}
] }
dialogPlacement = 'top right'
target = { 'email-setting' } >
The email you enter here will be part of your user profile.
</Spotlight>
);
}
_next: (*) => void;
/**
* Close the spotlight component.
*
* @returns {void}
*/
_next() {
this.props.dispatch(continueOnboarding());
}
}
export default connect()(EmailSettingSpotlight);

69
app/features/onboarding/components/NameSettingSpotlight.js

@ -1,69 +0,0 @@
// @flow
import { Spotlight } from '@atlaskit/onboarding';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { continueOnboarding } from '../actions';
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
};
/**
* Name Setting Spotlight Component.
*/
class NameSettingSpotlight extends Component<Props, *> {
/**
* Initializes a new {@code NameSettingSpotlight} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._next = this._next.bind(this);
}
/**
* Render function of component.
*
* @returns {ReactElement}
*/
render() {
return (
<Spotlight
actions = { [
{
onClick: this._next,
text: 'Next'
}
] }
dialogPlacement = 'top right'
target = { 'name-setting' } >
This will be your display name, others will see you with this
name.
</Spotlight>
);
}
_next: (*) => void;
/**
* Close the spotlight component.
*
* @returns {void}
*/
_next() {
this.props.dispatch(continueOnboarding());
}
}
export default connect()(NameSettingSpotlight);

4
app/features/onboarding/components/Onboarding.js

@ -38,9 +38,9 @@ class Onboarding extends Component<Props, *> {
const steps = onboardingSteps[section];
if (_activeOnboarding && steps.includes(_activeOnboarding)) {
const ActiveOnboarding = onboardingComponents[_activeOnboarding];
const { type: ActiveOnboarding, ...props } = onboardingComponents[_activeOnboarding];
return <ActiveOnboarding />;
return <ActiveOnboarding { ...props } />;
}
return null;

19
app/features/onboarding/components/OnboardingModal.js

@ -3,8 +3,10 @@
import { Modal } from '@atlaskit/onboarding';
import React, { Component } from 'react';
import { withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { compose } from 'redux';
import OnboardingModalImage from '../../../images/onboarding.png';
@ -18,6 +20,11 @@ type Props = {
* Redux dispatch.
*/
dispatch: Dispatch<*>;
/**
* I18next translation function.
*/
t: Function;
};
/**
@ -43,21 +50,23 @@ class OnboardingModal extends Component<Props, *> {
* @returns {ReactElement}
*/
render() {
const { t } = this.props;
return (
<Modal
actions = { [
{
onClick: this._next,
text: 'Start Tour'
text: t('onboarding.startTour')
},
{
onClick: this._skip,
text: 'Skip'
text: t('onboarding.skip')
}
] }
heading = { `Welcome to ${config.appName}` }
heading = { t('onboarding.welcome', { appName: config.appName }) }
image = { OnboardingModalImage } >
<p> Let us show you around!</p>
<p> { t('onboarding.letUsShowYouAround') }</p>
</Modal>
);
}
@ -86,4 +95,4 @@ class OnboardingModal extends Component<Props, *> {
}
export default connect()(OnboardingModal);
export default compose(connect(), withTranslation())(OnboardingModal);

68
app/features/onboarding/components/OnboardingSpotlight.js

@ -0,0 +1,68 @@
// @flow
import { Spotlight } from '@atlaskit/onboarding';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { continueOnboarding } from '../actions';
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
/**
* Spotlight dialog placement.
*/
dialogPlacement: String;
/**
* Callback when "next" clicked.
*/
onNext: Function;
/**
* I18next translation function.
*/
t: Function;
/**
* Spotlight target.
*/
target: String;
/**
* Spotlight text.
*/
text: String;
};
const OnboardingSpotlight = (props: Props) => {
const { t } = useTranslation();
return (
<Spotlight
actions = { [
{
onClick: () => {
props.dispatch(continueOnboarding());
props.onNext && props.onNext(props);
},
text: t('onboarding.next')
}
] }
dialogPlacement = { props.dialogPlacement }
target = { props.target } >
{ t(props.text) }
</Spotlight>
);
};
export default connect()(OnboardingSpotlight);

69
app/features/onboarding/components/ServerSettingSpotlight.js

@ -1,69 +0,0 @@
// @flow
import { Spotlight } from '@atlaskit/onboarding';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { continueOnboarding } from '../actions';
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
};
/**
* Server Setting Spotlight Component.
*/
class ServerSettingSpotlight extends Component<Props, *> {
/**
* Initializes a new {@code ServerSettingSpotlight} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._next = this._next.bind(this);
}
/**
* Render function of component.
*
* @returns {ReactElement}
*/
render() {
return (
<Spotlight
actions = { [
{
onClick: this._next,
text: 'Next'
}
] }
dialogPlacement = 'top right'
target = { 'server-setting' } >
This will be the server where your conferences will take place.
You can use your own, but you don't need to!
</Spotlight>
);
}
_next: (*) => void;
/**
* Close the spotlight component.
*
* @returns {void}
*/
_next() {
this.props.dispatch(continueOnboarding());
}
}
export default connect()(ServerSettingSpotlight);

67
app/features/onboarding/components/ServerTimeoutSpotlight.js

@ -1,67 +0,0 @@
// @flow
import { Spotlight } from '@atlaskit/onboarding';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { continueOnboarding } from '../actions';
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
};
/**
* Server Setting Spotlight Component.
*/
class ServerTimeoutSpotlight extends Component<Props, *> {
/**
* Initializes a new {@code ServerSettingSpotlight} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._next = this._next.bind(this);
}
/**
* Render function of component.
*
* @returns {ReactElement}
*/
render() {
return (
<Spotlight
actions = { [
{
onClick: this._next,
text: 'Next'
}
] }
dialogPlacement = 'right top'
target = { 'server-timeout' } >
Timeout to join a meeting, if the meeting hasn't been joined before the timeout hits, it's cancelled.
</Spotlight>
);
}
_next: (*) => void;
/**
* Close the spotlight component.
*
* @returns {void}
*/
_next() {
this.props.dispatch(continueOnboarding());
}
}
export default connect()(ServerTimeoutSpotlight);

68
app/features/onboarding/components/SettingsDrawerSpotlight.js

@ -1,68 +0,0 @@
// @flow
import { Spotlight } from '@atlaskit/onboarding';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { openDrawer } from '../../navbar';
import { SettingsDrawer } from '../../settings';
import { continueOnboarding } from '../actions';
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
};
/**
* Settings Drawer Spotlight Component.
*/
class SettingsDrawerSpotlight extends Component<Props, *> {
/**
* Initializes a new {@code SettingsDrawerSpotlight} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._next = this._next.bind(this);
}
/**
* Render function of component.
*
* @returns {ReactElement}
*/
render() {
return (
<Spotlight
dialogPlacement = 'top right'
target = { 'settings-drawer-button' }
targetOnClick = { this._next }>
Click here to open the settings drawer.
</Spotlight>
);
}
_next: (*) => void;
/**
* Close the spotlight component and opens Settings Drawer and shows
* onboarding.
*
* @returns {void}
*/
_next() {
this.props.dispatch(openDrawer(SettingsDrawer));
this.props.dispatch(continueOnboarding());
}
}
export default connect()(SettingsDrawerSpotlight);

69
app/features/onboarding/components/StartMutedTogglesSpotlight.js

@ -1,69 +0,0 @@
// @flow
import { Spotlight } from '@atlaskit/onboarding';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { continueOnboarding } from '../actions';
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
};
/**
* Start Muted Toggles Spotlight Component.
*/
class StartMutedTogglesSpotlight extends Component<Props, *> {
/**
* Initializes a new {@code StartMutedTogglesSpotlight} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._next = this._next.bind(this);
}
/**
* Render function of component.
*
* @returns {ReactElement}
*/
render() {
return (
<Spotlight
actions = { [
{
onClick: this._next,
text: 'Next'
}
] }
dialogPlacement = 'top right'
target = { 'start-muted-toggles' } >
You can toggle if you want to start with your audio or video
muted here. This will be applied to all conferences.
</Spotlight>
);
}
_next: (*) => void;
/**
* Close the spotlight component.
*
* @returns {void}
*/
_next() {
this.props.dispatch(continueOnboarding());
}
}
export default connect()(StartMutedTogglesSpotlight);

9
app/features/onboarding/components/index.js

@ -1,10 +1,3 @@
export { default as ConferenceURLSpotlight } from './ConferenceURLSpotlight';
export { default as EmailSettingSpotlight } from './EmailSettingSpotlight';
export { default as NameSettingSpotlight } from './NameSettingSpotlight';
export { default as OnboardingSpotlight } from './OnboardingSpotlight';
export { default as Onboarding } from './Onboarding';
export { default as OnboardingModal } from './OnboardingModal';
export { default as ServerSettingSpotlight } from './ServerSettingSpotlight';
export { default as ServerTimeoutSpotlight } from './ServerTimeoutSpotlight';
export { default as SettingsDrawerSpotlight } from './SettingsDrawerSpotlight';
export { default as StartMutedTogglesSpotlight } from './StartMutedTogglesSpotlight';
export { default as AlwaysOnTopWindowSpotlight } from './AlwaysOnTopWindowSpotlight';

77
app/features/onboarding/constants.js

@ -1,16 +1,7 @@
// @flow
import {
OnboardingModal,
ConferenceURLSpotlight,
SettingsDrawerSpotlight,
NameSettingSpotlight,
EmailSettingSpotlight,
StartMutedTogglesSpotlight,
ServerSettingSpotlight,
ServerTimeoutSpotlight,
AlwaysOnTopWindowSpotlight
} from './components';
import { OnboardingModal, OnboardingSpotlight } from './components';
import { openDrawer, closeDrawer } from '../navbar';
import { SettingsDrawer } from '../settings';
export const advenaceSettingsSteps = [
'server-setting',
@ -33,13 +24,57 @@ export const onboardingSteps = {
};
export const onboardingComponents = {
'onboarding-modal': OnboardingModal,
'conference-url': ConferenceURLSpotlight,
'settings-drawer-button': SettingsDrawerSpotlight,
'name-setting': NameSettingSpotlight,
'email-setting': EmailSettingSpotlight,
'start-muted-toggles': StartMutedTogglesSpotlight,
'server-setting': ServerSettingSpotlight,
'server-timeout': ServerTimeoutSpotlight,
'always-on-top-window': AlwaysOnTopWindowSpotlight
'onboarding-modal': { type: OnboardingModal },
'conference-url': {
type: OnboardingSpotlight,
dialogPlacement: 'bottom center',
target: 'conference-url',
text: 'onboarding.conferenceUrl'
},
'settings-drawer-button': {
type: OnboardingSpotlight,
dialogPlacement: 'top right',
target: 'settings-drawer-button',
text: 'onboarding.settingsDrawerButton',
onNext: (props: OnboardingSpotlight.props) => props.dispatch(openDrawer(SettingsDrawer))
},
'name-setting': {
type: OnboardingSpotlight,
dialogPlacement: 'top right',
target: 'name-setting',
text: 'onboarding.nameSetting'
},
'email-setting': {
type: OnboardingSpotlight,
dialogPlacement: 'top right',
target: 'email-setting',
text: 'onboarding.emailSetting'
},
'start-muted-toggles': {
type: OnboardingSpotlight,
dialogPlacement: 'top right',
target: 'start-muted-toggles',
text: 'onboarding.startMutedToggles'
},
'server-setting': {
type: OnboardingSpotlight,
dialogPlacement: 'top right',
target: 'server-setting',
text: 'onboarding.serverSetting'
},
'server-timeout': {
type: OnboardingSpotlight,
dialogPlacement: 'top right',
target: 'server-timeout',
text: 'onboarding.serverTimeout'
},
'always-on-top-window': {
type: OnboardingSpotlight,
dialogPlacement: 'top right',
target: 'always-on-top-window',
text: 'onboarding.alwaysOnTop',
onNext: (props: OnboardingSpotlight.props) => setTimeout(() => {
props.dispatch(closeDrawer());
}, 300)
}
};

84
app/features/settings/components/AlwaysOnTopWindowToggle.js

@ -1,84 +0,0 @@
// @flow
import React, { Component } from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { setWindowAlwaysOnTop } from '../actions';
import ToggleWithLabel from './ToggleWithLabel';
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
/**
* Window Always on Top value in (redux) state.
*/
_alwaysOnTopWindowEnabled: boolean;
};
/**
* Window always open on top placed in Settings Drawer.
*/
class AlwaysOnTopWindowToggle extends Component<Props> {
/**
* Initializes a new {@code AlwaysOnTopWindowToggle} instance.
*
* @inheritdoc
*/
constructor(props) {
super(props);
this._onAlwaysOnTopWindowToggleChange
= this._onAlwaysOnTopWindowToggleChange.bind(this);
}
/**
* Render function of component.
*
* @returns {ReactElement}
*/
render() {
return (
<ToggleWithLabel
isDefaultChecked = { this.props._alwaysOnTopWindowEnabled }
label = 'Always on Top Window'
onChange = { this._onAlwaysOnTopWindowToggleChange }
value = { this.props._alwaysOnTopWindowEnabled } />
);
}
_onAlwaysOnTopWindowToggleChange: (*) => void;
/**
* Toggles alwaysOnTopWindowEnabled.
*
* @returns {void}
*/
_onAlwaysOnTopWindowToggleChange() {
const { _alwaysOnTopWindowEnabled } = this.props;
const newState = !_alwaysOnTopWindowEnabled;
this.props.dispatch(setWindowAlwaysOnTop(newState));
}
}
/**
* Maps (parts of) the redux state to the React props.
*
* @param {Object} state - The redux state.
* @returns {{
* _alwaysOnTopWindowEnabled: boolean,
* }}
*/
function _mapStateToProps(state: Object) {
return {
_alwaysOnTopWindowEnabled: state.settings.alwaysOnTopWindowEnabled
};
}
export default connect(_mapStateToProps)(AlwaysOnTopWindowToggle);

13
app/features/settings/components/ServerTimeoutField.js

@ -3,7 +3,9 @@
import { FieldTextStateless } from '@atlaskit/field-text';
import React, { Component } from 'react';
import { withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { compose } from 'redux';
import type { Dispatch } from 'redux';
import config from '../../config';
@ -22,6 +24,11 @@ type Props = {
* Default Jitsi Meet Server Timeout in (redux) store.
*/
_serverTimeout: number;
/**
* I18next translation function.
*/
t: Function;
};
type State = {
@ -64,6 +71,8 @@ class ServerTimeoutField extends Component<Props, State> {
* @returns {ReactElement}
*/
render() {
const { t } = this.props;
return (
<Form onSubmit = { this._onServerTimeoutSubmit }>
<FieldTextStateless
@ -71,7 +80,7 @@ class ServerTimeoutField extends Component<Props, State> {
= { 'Invalid Timeout' }
isInvalid = { !this.state.isValid }
isValidationHidden = { this.state.isValid }
label = 'Server Timeout (in seconds)'
label = { t('settings.serverTimeout') }
onBlur = { this._onServerTimeoutSubmit }
onChange = { this._onServerTimeoutChange }
placeholder = { config.defaultServerTimeout }
@ -138,4 +147,4 @@ function _mapStateToProps(state: Object) {
};
}
export default connect(_mapStateToProps)(ServerTimeoutField);
export default compose(connect(_mapStateToProps), withTranslation())(ServerTimeoutField);

16
app/features/settings/components/ServerURLField.js

@ -3,8 +3,10 @@
import { FieldTextStateless } from '@atlaskit/field-text';
import React, { Component } from 'react';
import { withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { compose } from 'redux';
import config from '../../config';
import { getExternalApiURL } from '../../utils';
@ -23,6 +25,11 @@ type Props = {
* Default Jitsi Meet Server URL in (redux) store.
*/
_serverURL: string;
/**
* I18next translation function.
*/
t: Function;
};
type State = {
@ -65,14 +72,15 @@ class ServerURLField extends Component<Props, State> {
* @returns {ReactElement}
*/
render() {
const { t } = this.props;
return (
<Form onSubmit = { this._onServerURLSubmit }>
<FieldTextStateless
invalidMessage
= { 'Invalid Server URL or external API not enabled' }
invalidMessage = { t('settings.invalidServer') }
isInvalid = { !this.state.isValid }
isValidationHidden = { this.state.isValid }
label = 'Server URL'
label = { t('settings.serverUrl') }
onBlur = { this._onServerURLSubmit }
onChange = { this._onServerURLChange }
placeholder = { config.defaultServerURL }
@ -150,4 +158,4 @@ function _mapStateToProps(state: Object) {
};
}
export default connect(_mapStateToProps)(ServerURLField);
export default compose(connect(_mapStateToProps), withTranslation())(ServerURLField);

66
app/features/settings/components/SettingToggle.js

@ -0,0 +1,66 @@
// @flow
import React, { useCallback } from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import ToggleWithLabel from './ToggleWithLabel';
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
/**
* The label for the toggle.
*/
label: String;
/**
* The name of the setting.
*/
settingName: String;
/**
* A function to produce setting change events.
*/
settingChangeEvent: Function;
};
/**
* Maps (parts of) the redux state to the React props.
*
* @param {Object} state - The redux state.
* @param {Object} ownProps - The props of the redux wrapper component.
* @returns {Object} A props object including the current value of the setting.
*/
const mapStateToProps = (state, ownProps: Props) => {
return {
value: state.settings[ownProps.settingName],
...ownProps
};
};
/**
* A component to control a single boolean redux setting.
*
* @param {Object} props - The props provided by mapStateToProps.
* @returns {Object} A rendered toggle component with correct state.
*/
function SettingToggle(props: Object) {
const onChange = useCallback(
() => props.dispatch(props.settingChangeEvent(!props.value)));
return (
<ToggleWithLabel
isDefaultChecked = { props.value }
label = { props.label }
onChange = { onChange }
value = { props.value } />
);
}
export default connect(mapStateToProps)(SettingToggle);

41
app/features/settings/components/SettingsDrawer.js

@ -7,18 +7,22 @@ import { SpotlightTarget } from '@atlaskit/onboarding';
import Panel from '@atlaskit/panel';
import React, { Component } from 'react';
import { withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { compose } from 'redux';
import { closeDrawer, DrawerContainer, Logo } from '../../navbar';
import { Onboarding, advenaceSettingsSteps, startOnboarding } from '../../onboarding';
import { Form, SettingsContainer, TogglesContainer } from '../styled';
import { setEmail, setName } from '../actions';
import {
setEmail, setName, setWindowAlwaysOnTop,
setStartWithAudioMuted, setStartWithVideoMuted
} from '../actions';
import AlwaysOnTopWindowToggle from './AlwaysOnTopWindowToggle';
import SettingToggle from './SettingToggle';
import ServerURLField from './ServerURLField';
import ServerTimeoutField from './ServerTimeoutField';
import StartMutedToggles from './StartMutedToggles';
type Props = {
@ -46,6 +50,11 @@ type Props = {
* Name of the user.
*/
_name: string;
/**
* I18next translation function.
*/
t: Function;
};
/**
@ -92,9 +101,11 @@ class SettingsDrawer extends Component<Props, *> {
* @returns {ReactElement}
*/
render() {
const { t } = this.props;
return (
<AkCustomDrawer
backIcon = { <ArrowLeft label = 'Back' /> }
backIcon = { <ArrowLeft label = { t('settings.back') } /> }
isOpen = { this.props.isOpen }
onBackButton = { this._onBackButton }
primaryIcon = { <Logo /> } >
@ -104,7 +115,7 @@ class SettingsDrawer extends Component<Props, *> {
name = 'name-setting'>
<Form onSubmit = { this._onNameFormSubmit }>
<FieldText
label = 'Name'
label = { t('settings.name') }
onBlur = { this._onNameBlur }
shouldFitContainer = { true }
type = 'text'
@ -115,7 +126,7 @@ class SettingsDrawer extends Component<Props, *> {
name = 'email-setting'>
<Form onSubmit = { this._onEmailFormSubmit }>
<FieldText
label = 'Email'
label = { t('settings.email') }
onBlur = { this._onEmailBlur }
shouldFitContainer = { true }
type = 'text'
@ -125,11 +136,18 @@ class SettingsDrawer extends Component<Props, *> {
<TogglesContainer>
<SpotlightTarget
name = 'start-muted-toggles'>
<StartMutedToggles />
<SettingToggle
label = { t('settings.startWithAudioMuted') }
settingChangeEvent = { setStartWithAudioMuted }
settingName = 'startWithAudioMuted' />
<SettingToggle
label = { t('settings.startWithVideoMuted') }
settingChangeEvent = { setStartWithVideoMuted }
settingName = 'startWithVideoMuted' />
</SpotlightTarget>
</TogglesContainer>
<Panel
header = 'Advanced Settings'
header = { t('settings.advancedSettings') }
isDefaultExpanded = { this.props._isOnboardingAdvancedSettings }>
<SpotlightTarget name = 'server-setting'>
<ServerURLField />
@ -140,7 +158,10 @@ class SettingsDrawer extends Component<Props, *> {
<TogglesContainer>
<SpotlightTarget
name = 'always-on-top-window'>
<AlwaysOnTopWindowToggle />
<SettingToggle
label = { t('settings.alwaysOnTopWindow') }
settingChangeEvent = { setWindowAlwaysOnTop }
settingName = 'alwaysOnTopWindowEnabled' />
</SpotlightTarget>
</TogglesContainer>
</Panel>
@ -236,4 +257,4 @@ function _mapStateToProps(state: Object) {
};
}
export default connect(_mapStateToProps)(SettingsDrawer);
export default compose(connect(_mapStateToProps), withTranslation())(SettingsDrawer);

145
app/features/settings/components/StartMutedToggles.js

@ -1,145 +0,0 @@
// @flow
import React, { Component } from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import {
setStartWithAudioMuted,
setStartWithVideoMuted
} from '../actions';
import ToggleWithLabel from './ToggleWithLabel';
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
/**
* Start with Audio Muted value in (redux) state.
*/
_startWithAudioMuted: boolean;
/**
* Start with Video Muted value in (redux) state.
*/
_startWithVideoMuted: boolean;
};
type State = {
/**
* Start with Audio Muted value in (local) state.
*/
startWithAudioMuted: boolean;
/**
* Start with Video Muted value in (local) state.
*/
startWithVideoMuted: boolean;
};
/**
* Start Muted toggles for audio and video placed in Settings Drawer.
*/
class StartMutedToggles extends Component<Props, State> {
/**
* Initializes a new {@code StartMutedToggles} instance.
*
* @inheritdoc
*/
constructor(props) {
super(props);
this.state = {
startWithAudioMuted: false,
startWithVideoMuted: false
};
this._onAudioToggleChange = this._onAudioToggleChange.bind(this);
this._onVideoToggleChange = this._onVideoToggleChange.bind(this);
}
/**
* This updates the startWithAudioMuted and startWithVideoMuted in (local)
* state when there is a change in redux store.
*
* @param {Props} props - New props of the component.
* @returns {State} - New state of the component.
*/
static getDerivedStateFromProps(props) {
return {
startWithAudioMuted: props._startWithAudioMuted,
startWithVideoMuted: props._startWithVideoMuted
};
}
/**
* Render function of component.
*
* @returns {ReactElement}
*/
render() {
return (
<>
<ToggleWithLabel
isDefaultChecked = { this.props._startWithAudioMuted }
label = 'Start with Audio muted'
onChange = { this._onAudioToggleChange }
value = { this.state.startWithAudioMuted } />
<ToggleWithLabel
isDefaultChecked = { this.props._startWithVideoMuted }
label = 'Start with Video muted'
onChange = { this._onVideoToggleChange }
value = { this.state.startWithVideoMuted } />
</>
);
}
_onAudioToggleChange: (*) => void;
/**
* Toggles startWithAudioMuted.
*
* @returns {void}
*/
_onAudioToggleChange() {
const { startWithAudioMuted } = this.state;
this.props.dispatch(setStartWithAudioMuted(!startWithAudioMuted));
}
_onVideoToggleChange: (*) => void;