From 035002ceedf78d5ec73eabc0df7f06139939b967 Mon Sep 17 00:00:00 2001 From: Amine El Mouafik <412895+kytwb@users.noreply.github.com> Date: Mon, 8 Feb 2021 10:34:45 +0100 Subject: Synchronize with Franz 5.6.0 (#1033) Co-authored-by: FranzBot Co-authored-by: vantezzen Co-authored-by: Makazzz Co-authored-by: Stefan Malzner Co-authored-by: Amine Mouafik --- src/actions/service.js | 9 +- src/components/auth/Pricing.js | 29 +- src/components/auth/SetupAssistant.js | 319 +++++++++ src/components/layout/Sidebar.js | 3 + src/components/services/content/ServiceView.js | 73 +-- src/components/services/content/ServiceWebview.js | 5 +- src/components/services/content/Services.js | 6 +- src/components/services/tabs/TabItem.js | 87 ++- .../settings/services/EditServiceForm.js | 13 +- .../settings/settings/EditSettingsForm.js | 14 +- src/components/ui/FeatureList.js | 2 +- src/components/ui/Modal/index.js | 4 +- src/config.js | 5 +- src/containers/auth/SetupAssistantScreen.js | 133 ++++ src/containers/layout/AppLayoutContainer.js | 2 + src/containers/settings/EditServiceScreen.js | 14 +- src/containers/settings/EditSettingsScreen.js | 5 + src/electron/Settings.js | 4 + src/electron/ipc-api/cld.js | 20 + src/electron/ipc-api/dnd.js | 16 + src/electron/ipc-api/download.js | 26 +- src/electron/ipc-api/index.js | 4 + src/electron/ipc-api/settings.js | 4 +- src/features/todos/actions.js | 9 +- src/features/todos/components/TodosWebview.js | 31 +- src/features/todos/containers/TodosScreen.js | 3 + src/features/todos/index.js | 2 + src/features/todos/preload.js | 7 +- src/features/todos/store.js | 50 +- src/helpers/userAgent-helpers.js | 2 +- src/i18n/languages.js | 116 +++- src/i18n/locales/bg.json | 439 +++++++++++++ src/i18n/locales/defaultMessages.json | 712 +++++++++++++-------- src/i18n/locales/en-US.json | 14 +- src/i18n/locales/et.json | 439 +++++++++++++ src/i18n/locales/fa.json | 439 +++++++++++++ src/i18n/locales/fil.json | 439 +++++++++++++ src/i18n/locales/hi.json | 439 +++++++++++++ src/i18n/locales/kk.json | 439 +++++++++++++ src/i18n/locales/ms.json | 439 +++++++++++++ src/i18n/locales/si.json | 439 +++++++++++++ src/i18n/locales/sq.json | 439 +++++++++++++ src/i18n/locales/whitelist_en-US.json | 3 +- src/i18n/locales/zh-HANS.json | 439 +++++++++++++ .../src/components/auth/ServiceAssistant.json | 93 +++ .../src/components/auth/SetupAssistant.json | 54 ++ .../src/components/services/content/Services.json | 20 +- .../src/components/services/tabs/TabItem.json | 36 +- .../settings/services/EditServiceForm.json | 118 ++-- .../src/containers/settings/EditServiceScreen.json | 6 +- .../containers/settings/EditSettingsScreen.json | 186 +++--- .../features/todos/components/TodosWebview.json | 12 +- src/i18n/messages/src/lib/Menu.json | 181 +++--- src/index.js | 31 +- src/lib/Menu.js | 12 + src/models/Recipe.js | 4 + src/models/Service.js | 31 +- src/routes.js | 2 + src/stores/AppStore.js | 136 ++-- src/stores/RecipesStore.js | 2 +- src/stores/ServicesStore.js | 131 +++- src/stores/UserStore.js | 19 +- src/styles/auth.scss | 2 +- src/styles/layout.scss | 5 + src/styles/services.scss | 4 +- src/webview/contextMenu.js | 12 +- src/webview/contextMenuBuilder.js | 48 +- src/webview/recipe.js | 61 +- src/webview/spellchecker.js | 73 +-- 69 files changed, 6545 insertions(+), 870 deletions(-) create mode 100644 src/components/auth/SetupAssistant.js create mode 100644 src/containers/auth/SetupAssistantScreen.js create mode 100644 src/electron/ipc-api/cld.js create mode 100644 src/electron/ipc-api/dnd.js create mode 100644 src/i18n/locales/bg.json create mode 100644 src/i18n/locales/et.json create mode 100644 src/i18n/locales/fa.json create mode 100644 src/i18n/locales/fil.json create mode 100644 src/i18n/locales/hi.json create mode 100644 src/i18n/locales/kk.json create mode 100644 src/i18n/locales/ms.json create mode 100644 src/i18n/locales/si.json create mode 100644 src/i18n/locales/sq.json create mode 100644 src/i18n/locales/zh-HANS.json create mode 100644 src/i18n/messages/src/components/auth/ServiceAssistant.json create mode 100644 src/i18n/messages/src/components/auth/SetupAssistant.json (limited to 'src') diff --git a/src/actions/service.js b/src/actions/service.js index f0c42e8aa..578fdeca8 100644 --- a/src/actions/service.js +++ b/src/actions/service.js @@ -95,9 +95,14 @@ export default { serviceId: PropTypes.string.isRequired, }, openDevToolsForActiveService: {}, - setHibernation: { + hibernate: { serviceId: PropTypes.string.isRequired, - hibernating: PropTypes.bool.isRequired, + }, + awake: { + serviceId: PropTypes.string.isRequired, + }, + resetLastPollTimer: { + serviceId: PropTypes.string, }, shareSettingsWithServiceProcess: {}, }; diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js index 593cb9c4b..4f5a76c8a 100644 --- a/src/components/auth/Pricing.js +++ b/src/components/auth/Pricing.js @@ -67,6 +67,15 @@ const messages = defineMessages({ }); const styles = theme => ({ + root: { + width: '500px !important', + textAlign: 'center', + padding: 20, + zIndex: 100, + + '& h1': { + }, + }, container: { position: 'relative', marginLeft: -150, @@ -86,8 +95,8 @@ const styles = theme => ({ featureContainer: { width: 300, position: 'absolute', - left: 'calc(100% / 2 + 225px)', - top: 155, + left: 'calc(100% / 2 + 250px)', + marginTop: 20, background: theme.signup.pricing.feature.background, height: 'auto', padding: 20, @@ -174,8 +183,8 @@ export default @injectSheet(styles) @observer class Signup extends Component { const [intPart, fractionPart] = (price).toString().split('.'); return ( -
-
+ <> +
{isLoadingRequiredData ? : ( {currency} - 0 + 0 00

@@ -234,7 +243,7 @@ export default @injectSheet(styles) @observer class Signup extends Component {
{trialActivationError && ( -

{intl.formatMessage(messages.activationError)}

+

{intl.formatMessage(messages.activationError)}

)}
@@ -256,7 +265,7 @@ export default @injectSheet(styles) @observer class Signup extends Component {
- + ); } } diff --git a/src/components/auth/SetupAssistant.js b/src/components/auth/SetupAssistant.js new file mode 100644 index 000000000..e03cf9101 --- /dev/null +++ b/src/components/auth/SetupAssistant.js @@ -0,0 +1,319 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; +import injectSheet from 'react-jss'; +import classnames from 'classnames'; + +import { Input, Button } from '@meetfranz/forms'; +import { Badge } from '@meetfranz/ui'; +import Modal from '../ui/Modal'; +import Infobox from '../ui/Infobox'; +import Appear from '../ui/effects/Appear'; + +import { CDN_URL } from '../../config'; + +const SLACK_ID = 'slack'; + +const messages = defineMessages({ + headline: { + id: 'setupAssistant.headline', + defaultMessage: '!!!Let\'s get started', + }, + subHeadline: { + id: 'setupAssistant.subheadline', + defaultMessage: '!!!Choose from our most used services and get back on top of your messaging now.', + }, + submitButtonLabel: { + id: 'setupAssistant.submit.label', + defaultMessage: '!!!Let\'s go', + }, + inviteSuccessInfo: { + id: 'invite.successInfo', + defaultMessage: '!!!Invitations sent successfully', + }, +}); + +const styles = theme => ({ + root: { + width: '500px !important', + textAlign: 'center', + padding: 20, + + '& h1': { + }, + }, + servicesGrid: { + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'space-between', + }, + serviceContainer: { + background: theme.colorBackground, + position: 'relative', + width: '32%', + display: 'flex', + alignItems: 'center', + flexDirection: 'column', + justifyContent: 'center', + padding: 20, + borderRadius: theme.borderRadius, + marginBottom: 10, + opacity: 0.5, + transition: 'all 0.25s', + border: [3, 'solid', 'transparent'], + + '& h2': { + margin: [10, 0, 0], + color: theme.colorText, + }, + + '&:hover': { + border: [3, 'solid', theme.brandPrimary], + '& $serviceIcon': { + }, + }, + + }, + selected: { + border: [3, 'solid', theme.brandPrimary], + background: `${theme.brandPrimary}47`, + opacity: 1, + }, + serviceIcon: { + width: 50, + transition: 'all 0.25s', + }, + + slackModalContent: { + textAlign: 'center', + + '& img': { + width: 50, + marginBottom: 20, + }, + }, + modalActionContainer: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }, + ctaCancel: { + background: 'none !important', + }, + slackBadge: { + position: 'absolute', + bottom: 4, + height: 'auto', + padding: '0px 4px', + borderRadius: theme.borderRadiusSmall, + margin: 0, + display: 'flex', + overflow: 'hidden', + }, + clearSlackWorkspace: { + background: theme.inputPrefixColor, + marginLeft: 5, + height: '100%', + color: theme.colorText, + display: 'inline-flex', + justifyContent: 'center', + alignItems: 'center', + marginRight: -4, + padding: [0, 5], + }, +}); + +@injectSheet(styles) @observer +class SetupAssistant extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + onSubmit: PropTypes.func.isRequired, + isInviteSuccessful: PropTypes.bool, + services: PropTypes.object.isRequired, + isSettingUpServices: PropTypes.bool.isRequired, + }; + + static defaultProps = { + isInviteSuccessful: false, + }; + + static contextTypes = { + intl: intlShape, + }; + + state = { + services: [{ + id: 'whatsapp', + }, { + id: 'messenger', + }, { + id: 'gmail', + }], + isSlackModalOpen: false, + slackWorkspace: '', + }; + + slackWorkspaceHandler() { + const { slackWorkspace = '', services } = this.state; + + const sanitizedWorkspace = slackWorkspace.trim().replace(/^https?:\/\//, ''); + + if (sanitizedWorkspace) { + const index = services.findIndex(s => s.id === SLACK_ID); + + if (index === -1) { + const newServices = services; + newServices.push({ id: SLACK_ID, team: sanitizedWorkspace }); + this.setState({ services: newServices }); + } + } + + this.setState({ + isSlackModalOpen: false, + slackWorkspace: sanitizedWorkspace, + }); + } + + render() { + const { intl } = this.context; + const { + classes, isInviteSuccessful, onSubmit, services, isSettingUpServices, + } = this.props; + const { isSlackModalOpen, slackWorkspace, services: addedServices } = this.state; + + return ( +
+ {this.state.showSuccessInfo && isInviteSuccessful && ( + + + {intl.formatMessage(messages.inviteSuccessInfo)} + + + )} + + +

+ {intl.formatMessage(messages.headline)} +

+

+ {intl.formatMessage(messages.subHeadline)} +

+
+ {Object.keys(services).map((id) => { + const service = services[id]; + return ( + + + )} + + ); + })} +
+ this.setState({ isSlackModalOpen: false })} + > +
+ +

Create your first Slack workspace

+
{ + e.preventDefault(); + this.slackWorkspaceHandler(); + }} + > + this.setState({ slackWorkspace: e.target.value })} + value={slackWorkspace} + /> +
+
+
+
+
+
+ ); + } +} + +export default SetupAssistant; diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js index 90bbe86e9..a47e74db0 100644 --- a/src/components/layout/Sidebar.js +++ b/src/components/layout/Sidebar.js @@ -60,6 +60,7 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp isAppMuted: PropTypes.bool.isRequired, isWorkspaceDrawerOpen: PropTypes.bool.isRequired, toggleWorkspaceDrawer: PropTypes.func.isRequired, + isTodosServiceActive: PropTypes.bool.isRequired, }; static contextTypes = { @@ -96,6 +97,7 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp toggleWorkspaceDrawer, stores, actions, + isTodosServiceActive, } = this.props; const { intl } = this.context; const todosToggleMessage = ( @@ -140,6 +142,7 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp todoActions.toggleTodosPanel(); this.updateToolTip(); }} + disblaed={isTodosServiceActive} className={`sidebar__button sidebar__button--todos ${todosStore.isTodosPanelVisible ? 'is-active' : ''}`} data-tip={`${intl.formatMessage(todosToggleMessage)} (${ctrlKey}+T)`} > diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js index d91016c71..444d5fea4 100644 --- a/src/components/services/content/ServiceView.js +++ b/src/components/services/content/ServiceView.js @@ -1,6 +1,6 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; -import { autorun, reaction } from 'mobx'; +import { autorun } from 'mobx'; import { observer, inject } from 'mobx-react'; import classnames from 'classnames'; @@ -27,11 +27,7 @@ export default @inject('stores', 'actions') @observer class ServiceView extends stores: PropTypes.shape({ settings: PropTypes.instanceOf(SettingsStore).isRequired, }).isRequired, - actions: PropTypes.shape({ - service: PropTypes.shape({ - setHibernation: PropTypes.func.isRequired, - }).isRequired, - }).isRequired, + isSpellcheckerEnabled: PropTypes.bool.isRequired, }; static defaultProps = { @@ -50,12 +46,6 @@ export default @inject('stores', 'actions') @observer class ServiceView extends forceRepaintTimeout = null; - constructor(props) { - super(props); - - this.startHibernationTimer = this.startHibernationTimer.bind(this); - } - componentDidMount() { this.autorunDisposer = autorun(() => { if (this.props.service.isActive) { @@ -65,32 +55,6 @@ export default @inject('stores', 'actions') @observer class ServiceView extends }, 100); } }); - - reaction( - () => this.props.service.isActive, - () => { - if (!this.props.service.isActive && this.props.stores.settings.all.app.hibernate) { - // Service is inactive - start hibernation countdown - this.startHibernationTimer(); - } else { - if (this.hibernationTimer) { - // Service is active but we have an active hibernation timer: Clear timeout - clearTimeout(this.hibernationTimer); - } - - // Service is active, wake up service from hibernation - this.props.actions.service.setHibernation({ - serviceId: this.props.service.id, - hibernating: false, - }); - } - }, - ); - - // Start hibernation counter if we are in background - if (!this.props.service.isActive && this.props.stores.settings.all.app.hibernate) { - this.startHibernationTimer(); - } } componentWillUnmount() { @@ -110,19 +74,6 @@ export default @inject('stores', 'actions') @observer class ServiceView extends }); }; - startHibernationTimer() { - const timerDuration = (Number(this.props.stores.settings.all.app.hibernationStrategy) || 300) * 1000; - - const hibernationTimer = setTimeout(() => { - this.props.actions.service.setHibernation({ - serviceId: this.props.service.id, - hibernating: true, - }); - }, timerDuration); - - this.hibernationTimer = hibernationTimer; - } - render() { const { detachService, @@ -132,6 +83,7 @@ export default @inject('stores', 'actions') @observer class ServiceView extends edit, enable, stores, + isSpellcheckerEnabled, } = this.props; const { @@ -193,22 +145,19 @@ export default @inject('stores', 'actions') @observer class ServiceView extends ) : ( <> - {(!service.isHibernating || service.disableHibernation) ? ( + {(!service.isHibernating || service.isHibernationEnabled) ? ( <> {showNavBar && ( )} - - {/* {service.lostRecipeConnection && ( - - )} */} + )} ) : (
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js index 2e3354279..4edbde5e2 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js @@ -15,6 +15,7 @@ class ServiceWebview extends Component { service: PropTypes.instanceOf(ServiceModel).isRequired, setWebviewReference: PropTypes.func.isRequired, detachService: PropTypes.func.isRequired, + isSpellcheckerEnabled: PropTypes.bool.isRequired, }; @observable webview = null; @@ -55,6 +56,7 @@ class ServiceWebview extends Component { const { service, setWebviewReference, + isSpellcheckerEnabled, } = this.props; const preloadScript = path.join(__dirname, '../../../', 'webview', 'recipe.js'); @@ -70,7 +72,7 @@ class ServiceWebview extends Component { autosize src={service.url} preload={preloadScript} - partition={`persist:service-${service.id}`} + partition={service.partition} onDidAttach={() => { setWebviewReference({ serviceId: service.id, @@ -81,6 +83,7 @@ class ServiceWebview extends Component { useragent={service.userAgent} disablewebsecurity={service.recipe.disablewebsecurity ? true : undefined} allowpopups + webpreferences={`spellcheck=${isSpellcheckerEnabled ? 1 : 0}`} /> ); } diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js index da2ee0b9e..f679eeed0 100644 --- a/src/components/services/content/Services.js +++ b/src/components/services/content/Services.js @@ -10,6 +10,7 @@ import injectSheet from 'react-jss'; import ServiceView from './ServiceView'; import Appear from '../../ui/effects/Appear'; import serverlessLogin from '../../../helpers/serverless-helpers'; +import { TODOS_RECIPE_ID } from '../../../features/todos'; const messages = defineMessages({ welcome: { @@ -58,6 +59,7 @@ export default @injectSheet(styles) @inject('actions') @observer class Services hasActivatedTrial: PropTypes.bool.isRequired, classes: PropTypes.object.isRequired, actions: PropTypes.object.isRequired, + isSpellcheckerEnabled: PropTypes.bool.isRequired, }; static defaultProps = { @@ -111,6 +113,7 @@ export default @injectSheet(styles) @inject('actions') @observer class Services userHasCompletedSignup, hasActivatedTrial, classes, + isSpellcheckerEnabled, } = this.props; const { @@ -168,7 +171,7 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
)} - {services.map(service => ( + {services.filter(service => service.recipe.id !== TODOS_RECIPE_ID).map(service => ( openSettings({ path: 'user' })} + isSpellcheckerEnabled={isSpellcheckerEnabled} /> ))} diff --git a/src/components/services/tabs/TabItem.js b/src/components/services/tabs/TabItem.js index efa5fa60c..479f151a6 100644 --- a/src/components/services/tabs/TabItem.js +++ b/src/components/services/tabs/TabItem.js @@ -5,9 +5,14 @@ import PropTypes from 'prop-types'; import { observer } from 'mobx-react'; import classnames from 'classnames'; import { SortableElement } from 'react-sortable-hoc'; +import injectSheet from 'react-jss'; +import ms from 'ms'; +import { observable, autorun } from 'mobx'; import ServiceModel from '../../../models/Service'; -import { ctrlKey } from '../../../environment'; +import { ctrlKey, cmdKey } from '../../../environment'; + +const IS_SERVICE_DEBUGGING_ENABLED = (localStorage.getItem('debug') || '').includes('Franz:Service'); const { Menu } = remote; @@ -50,9 +55,38 @@ const messages = defineMessages({ }, }); -@observer -class TabItem extends Component { +const styles = { + pollIndicator: { + position: 'absolute', + bottom: 2, + width: 10, + height: 10, + borderRadius: 5, + background: 'gray', + transition: 'background 0.5s', + }, + pollIndicatorPoll: { + left: 2, + }, + pollIndicatorAnswer: { + left: 14, + }, + polled: { + background: 'yellow !important', + transition: 'background 0.1s', + }, + pollAnswered: { + background: 'green !important', + transition: 'background 0.1s', + }, + stale: { + background: 'red !important', + }, +}; + +@injectSheet(styles) @observer class TabItem extends Component { static propTypes = { + classes: PropTypes.object.isRequired, service: PropTypes.instanceOf(ServiceModel).isRequired, clickHandler: PropTypes.func.isRequired, shortcutIndex: PropTypes.number.isRequired, @@ -71,8 +105,33 @@ class TabItem extends Component { intl: intlShape, }; + @observable isPolled = false; + + @observable isPollAnswered = false; + + componentDidMount() { + const { service } = this.props; + + if (IS_SERVICE_DEBUGGING_ENABLED) { + autorun(() => { + if (Date.now() - service.lastPoll < ms('0.2s')) { + this.isPolled = true; + + setTimeout(() => { this.isPolled = false; }, ms('1s')); + } + + if (Date.now() - service.lastPollAnswer < ms('0.2s')) { + this.isPollAnswered = true; + + setTimeout(() => { this.isPollAnswered = false; }, ms('1s')); + } + }); + } + } + render() { const { + classes, service, clickHandler, shortcutIndex, @@ -97,6 +156,7 @@ class TabItem extends Component { }, { label: intl.formatMessage(messages.reload), click: reload, + accelerator: `${cmdKey}+R`, }, { label: intl.formatMessage(messages.edit), click: () => openSettings({ @@ -141,7 +201,7 @@ class TabItem extends Component { • )} - {service.isHibernating && !service.disableHibernation && ( + {service.isHibernating && !service.isHibernationEnabled && ( @@ -153,6 +213,7 @@ class TabItem extends Component { return (
  • {notificationBadge} + {IS_SERVICE_DEBUGGING_ENABLED && ( + <> +
    +
    + + )}
  • ); } diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index f1e70ce59..80f60b3e1 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js @@ -20,7 +20,6 @@ import Select from '../../ui/Select'; import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer'; import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox'; import { serviceLimitStore } from '../../../features/serviceLimit'; - import { isMac } from '../../../environment'; const messages = defineMessages({ @@ -96,9 +95,9 @@ const messages = defineMessages({ id: 'settings.service.form.isMutedInfo', defaultMessage: '!!!When disabled, all notification sounds and audio playback are muted', }, - disableHibernationInfo: { - id: 'settings.service.form.disableHibernationInfo', - defaultMessage: '!!!You currently have hibernation enabled but you can disable hibernation for individual services using this option.', + isHibernationEnabledInfo: { + id: 'settings.service.form.isHibernatedEnabledInfo', + defaultMessage: '!!!When enabled, a service will be shut down after a period of time to save system resources.', }, headlineNotifications: { id: 'settings.service.form.headlineNotifications', @@ -375,9 +374,9 @@ export default @observer class EditServiceForm extends Component { {isHibernationFeatureActive && ( <> - +

    - {intl.formatMessage(messages.disableHibernationInfo)} + {intl.formatMessage(messages.isHibernationEnabledInfo)}

    )} @@ -409,7 +408,7 @@ export default @observer class EditServiceForm extends Component { gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }} >
    -
    )} diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js index cd772214f..f20c38bc9 100644 --- a/src/components/settings/settings/EditSettingsForm.js +++ b/src/components/settings/settings/EditSettingsForm.js @@ -173,6 +173,8 @@ export default @observer class EditSettingsForm extends Component { isAdaptableDarkModeEnabled: PropTypes.bool.isRequired, isNightlyEnabled: PropTypes.bool.isRequired, openProcessManager: PropTypes.func.isRequired, + hasAddedTodosAsService: PropTypes.bool.isRequired, + isOnline: PropTypes.bool.isRequired, }; static contextTypes = { @@ -227,6 +229,8 @@ export default @observer class EditSettingsForm extends Component { openProcessManager, isTodosActivated, isNightlyEnabled, + hasAddedTodosAsService, + isOnline, } = this.props; const { intl } = this.context; @@ -346,7 +350,7 @@ export default @observer class EditSettingsForm extends Component {
    - {isTodosEnabled && ( + {isTodosEnabled && !hasAddedTodosAsService && ( <> {isTodosActivated && ( @@ -535,10 +539,10 @@ export default @observer class EditSettingsForm extends Component { - {form.$('enableSpellchecking').value && !isMac && ( - )} - {form.$('enableSpellchecking').value && isMac && ( + {isMac && form.$('enableSpellchecking').value && (

    {intl.formatMessage(messages.spellCheckerLanguageInfo)}

    )} @@ -625,7 +629,7 @@ export default @observer class EditSettingsForm extends Component { buttonType="secondary" label={intl.formatMessage(updateButtonLabelMessage)} onClick={checkForUpdates} - disabled={!automaticUpdates || isCheckingForUpdates || isUpdateAvailable} + disabled={!automaticUpdates || isCheckingForUpdates || isUpdateAvailable || !isOnline} loaded={!isCheckingForUpdates || !isUpdateAvailable} /> )} diff --git a/src/components/ui/FeatureList.js b/src/components/ui/FeatureList.js index dbc2a9078..72c799819 100644 --- a/src/components/ui/FeatureList.js +++ b/src/components/ui/FeatureList.js @@ -76,7 +76,7 @@ export class FeatureList extends Component { static propTypes = { className: PropTypes.string, featureClassName: PropTypes.string, - plan: PropTypes.oneOf(Object.values(PLANS)), + plan: PropTypes.oneOf(Object.keys(PLANS)), }; static defaultProps = { diff --git a/src/components/ui/Modal/index.js b/src/components/ui/Modal/index.js index 0af521452..a9fa0cd1b 100644 --- a/src/components/ui/Modal/index.js +++ b/src/components/ui/Modal/index.js @@ -41,8 +41,6 @@ export default @injectCSS(styles) class Modal extends Component { showClose, } = this.props; - const appRoot = document.getElementById('root'); - return ( {showClose && close && (