From 45373f655f68fdd0b320cde175b6108454ad4731 Mon Sep 17 00:00:00 2001 From: Vijay A Date: Sat, 17 Jul 2021 20:32:22 +0530 Subject: Removed Franz paid plans features: - serviceLimit - planSelection - trialStatusBar and other Franz features that were for different tiers of subscription. --- .../services/content/ServiceRestricted.js | 78 ---------------------- src/components/services/content/Services.js | 5 +- 2 files changed, 1 insertion(+), 82 deletions(-) delete mode 100644 src/components/services/content/ServiceRestricted.js (limited to 'src/components/services') diff --git a/src/components/services/content/ServiceRestricted.js b/src/components/services/content/ServiceRestricted.js deleted file mode 100644 index 4b8d926aa..000000000 --- a/src/components/services/content/ServiceRestricted.js +++ /dev/null @@ -1,78 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { observer } from 'mobx-react'; -import { defineMessages, intlShape } from 'react-intl'; - -import { serviceLimitStore } from '../../../features/serviceLimit'; -import Button from '../../ui/Button'; -import { RESTRICTION_TYPES } from '../../../models/Service'; - -const messages = defineMessages({ - headlineServiceLimit: { - id: 'service.restrictedHandler.serviceLimit.headline', - defaultMessage: '!!!You have reached your service limit.', - }, - textServiceLimit: { - id: 'service.restrictedHandler.serviceLimit.text', - defaultMessage: '!!!Please upgrade your account to use more than {count} services.', - }, - headlineCustomUrl: { - id: 'service.restrictedHandler.customUrl.headline', - defaultMessage: '!!!Franz Professional Plan required', - }, - textCustomUrl: { - id: 'service.restrictedHandler.customUrl.text', - defaultMessage: '!!!Please upgrade to the Franz Professional plan to use custom urls & self hosted services.', - }, - action: { - id: 'service.restrictedHandler.action', - defaultMessage: '!!!Upgrade Account', - }, -}); - -export default @observer class ServiceRestricted extends Component { - static propTypes = { - name: PropTypes.string.isRequired, - upgrade: PropTypes.func.isRequired, - type: PropTypes.number.isRequired, - }; - - static contextTypes = { - intl: intlShape, - }; - - countdownInterval = null; - - countdownIntervalTimeout = 1000; - - render() { - const { - name, - upgrade, - type, - } = this.props; - const { intl } = this.context; - - return ( -
- {type === RESTRICTION_TYPES.SERVICE_LIMIT && ( - <> -

{intl.formatMessage(messages.headlineServiceLimit)}

-

{intl.formatMessage(messages.textServiceLimit, { count: serviceLimitStore.serviceLimit })}

- - )} - {type === RESTRICTION_TYPES.CUSTOM_URL && ( - <> -

{intl.formatMessage(messages.headlineCustomUrl)}

-

{intl.formatMessage(messages.textCustomUrl)}

- - )} -
- ); - } -} diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js index caa3cf9aa..6e46a60d2 100644 --- a/src/components/services/content/Services.js +++ b/src/components/services/content/Services.js @@ -54,7 +54,6 @@ export default @injectSheet(styles) @inject('actions') @observer class Services openSettings: PropTypes.func.isRequired, update: PropTypes.func.isRequired, userHasCompletedSignup: PropTypes.bool.isRequired, - hasActivatedTrial: PropTypes.bool.isRequired, classes: PropTypes.object.isRequired, actions: PropTypes.object.isRequired, isSpellcheckerEnabled: PropTypes.bool.isRequired, @@ -109,7 +108,6 @@ export default @injectSheet(styles) @inject('actions') @observer class Services openSettings, update, userHasCompletedSignup, - hasActivatedTrial, classes, isSpellcheckerEnabled, } = this.props; @@ -123,7 +121,7 @@ export default @injectSheet(styles) @inject('actions') @observer class Services return (
- {(userHasCompletedSignup || hasActivatedTrial) && ( + {userHasCompletedSignup && (
openSettings({ path: 'user' })} isSpellcheckerEnabled={isSpellcheckerEnabled} /> ))} -- cgit v1.2.3-70-g09d2 From 012e55ebf87559f2d782e5400fb885df8b80a445 Mon Sep 17 00:00:00 2001 From: kytwb <412895+kytwb@users.noreply.github.com> Date: Fri, 23 Jul 2021 10:04:43 +0100 Subject: Fix hibernation mode (#1486) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use hibernation strategy from settings instead of hardcoded 5 minutes * Fix conditions with isHibernationEnabled, previously disableHibernation * Make service hibernation obey global setting Also refactors hibernation to move some hibernation enablement logic into the Service model * Remove global hibernation enable switch Implements option 4 from https://github.com/getferdi/ferdi/pull/1486#issuecomment-860290992 according to https://github.com/getferdi/ferdi/pull/1486#issuecomment-876558694 * Implements #865 : Add 'hibernate service' and 'wake up service' in the sidebar context menu. * Removed 'hibernationEnabled' check on main settings screen Since this is an (imo) incongruous behavior for the first time user. They will see a message, but with no ability to choose the hibernation strategy. * Autogenerated files from conflict fixes Co-authored-by: Kristóf Marussy Co-authored-by: Vijay A --- src/components/layout/Sidebar.js | 12 ++ src/components/services/content/ServiceView.js | 16 ++- src/components/services/tabs/TabBarSortableList.js | 6 + src/components/services/tabs/TabItem.js | 18 ++- src/components/services/tabs/Tabbar.js | 16 +++ .../settings/services/EditServiceForm.js | 14 +-- .../settings/settings/EditSettingsForm.js | 11 +- src/containers/layout/AppLayoutContainer.js | 4 + src/containers/settings/EditServiceScreen.js | 3 +- src/containers/settings/EditSettingsScreen.js | 10 -- src/environment.js | 1 - src/i18n/locales/defaultMessages.json | 34 +++++- src/i18n/locales/en-US.json | 2 + .../src/components/services/tabs/TabItem.json | 34 +++++- .../containers/settings/EditSettingsScreen.json | 129 +++++++++------------ .../workspaces/components/WorkspaceDrawer.json | 2 +- src/models/Service.js | 21 ++-- src/stores/ServicesStore.js | 14 ++- 18 files changed, 211 insertions(+), 136 deletions(-) (limited to 'src/components/services') diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js index 802538eba..9f3cacd38 100644 --- a/src/components/layout/Sidebar.js +++ b/src/components/layout/Sidebar.js @@ -58,6 +58,18 @@ const messages = defineMessages({ export default @inject('stores', 'actions') @observer class Sidebar extends Component { static propTypes = { openSettings: PropTypes.func.isRequired, + closeSettings: PropTypes.func.isRequired, + setActive: PropTypes.func.isRequired, + reorder: PropTypes.func.isRequired, + reload: PropTypes.func.isRequired, + toggleNotifications: PropTypes.func.isRequired, + toggleAudio: PropTypes.func.isRequired, + showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, + showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, + deleteService: PropTypes.func.isRequired, + updateService: PropTypes.func.isRequired, + hibernateService: PropTypes.func.isRequired, + wakeUpService: PropTypes.func.isRequired, toggleMuteApp: PropTypes.func.isRequired, isAppMuted: PropTypes.bool.isRequired, isWorkspaceDrawerOpen: PropTypes.bool.isRequired, diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js index 17d2db5a0..3fc084ff0 100644 --- a/src/components/services/content/ServiceView.js +++ b/src/components/services/content/ServiceView.js @@ -145,19 +145,17 @@ export default @inject('stores', 'actions') @observer class ServiceView extends ) : ( <> - {(!service.isHibernating || service.isHibernationEnabled) ? ( + {!service.isHibernating ? ( <> {showNavBar && ( )} - {!service.isHibernating && ( - - )} + ) : (
diff --git a/src/components/services/tabs/TabBarSortableList.js b/src/components/services/tabs/TabBarSortableList.js index 489027d57..f12d90602 100644 --- a/src/components/services/tabs/TabBarSortableList.js +++ b/src/components/services/tabs/TabBarSortableList.js @@ -17,6 +17,8 @@ class TabBarSortableList extends Component { deleteService: PropTypes.func.isRequired, disableService: PropTypes.func.isRequired, enableService: PropTypes.func.isRequired, + hibernateService: PropTypes.func.isRequired, + wakeUpService: PropTypes.func.isRequired, showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, } @@ -31,6 +33,8 @@ class TabBarSortableList extends Component { deleteService, disableService, enableService, + hibernateService, + wakeUpService, openSettings, showMessageBadgeWhenMutedSetting, showMessageBadgesEvenWhenMuted, @@ -53,6 +57,8 @@ class TabBarSortableList extends Component { deleteService={() => deleteService({ serviceId: service.id })} disableService={() => disableService({ serviceId: service.id })} enableService={() => enableService({ serviceId: service.id })} + hibernateService={() => hibernateService({ serviceId: service.id })} + wakeUpService={() => wakeUpService({ serviceId: service.id })} openSettings={openSettings} showMessageBadgeWhenMutedSetting={showMessageBadgeWhenMutedSetting} showMessageBadgesEvenWhenMuted={showMessageBadgesEvenWhenMuted} diff --git a/src/components/services/tabs/TabItem.js b/src/components/services/tabs/TabItem.js index 5c3149a11..ccf3333f8 100644 --- a/src/components/services/tabs/TabItem.js +++ b/src/components/services/tabs/TabItem.js @@ -49,6 +49,14 @@ const messages = defineMessages({ id: 'tabs.item.enableService', defaultMessage: '!!!Enable Service', }, + hibernateService: { + id: 'tabs.item.hibernateService', + defaultMessage: '!!!Hibernate Service', + }, + wakeUpService: { + id: 'tabs.item.wakeUpService', + defaultMessage: '!!!Wake Up Service', + }, deleteService: { id: 'tabs.item.deleteService', defaultMessage: '!!!Delete Service', @@ -101,6 +109,8 @@ const styles = { deleteService: PropTypes.func.isRequired, disableService: PropTypes.func.isRequired, enableService: PropTypes.func.isRequired, + hibernateService: PropTypes.func.isRequired, + wakeUpService: PropTypes.func.isRequired, showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, }; @@ -145,6 +155,8 @@ const styles = { deleteService, disableService, enableService, + hibernateService, + wakeUpService, openSettings, showMessageBadgeWhenMutedSetting, showMessageBadgesEvenWhenMuted, @@ -180,6 +192,10 @@ const styles = { }, { label: intl.formatMessage(service.isEnabled ? messages.disableService : messages.enableService), click: () => (service.isEnabled ? disableService() : enableService()), + }, { + label: intl.formatMessage(service.isHibernating ? messages.wakeUpService : messages.hibernateService), + click: () => (service.isHibernating ? wakeUpService() : hibernateService()), + enabled: service.canHibernate, }, { type: 'separator', }, { @@ -217,7 +233,7 @@ const styles = { • )} - {service.isHibernating && !service.isHibernationEnabled && ( + {service.isHibernating && ( diff --git a/src/components/services/tabs/Tabbar.js b/src/components/services/tabs/Tabbar.js index 5e8260ad0..db7a69bfc 100644 --- a/src/components/services/tabs/Tabbar.js +++ b/src/components/services/tabs/Tabbar.js @@ -17,6 +17,8 @@ export default @observer class TabBar extends Component { toggleAudio: PropTypes.func.isRequired, deleteService: PropTypes.func.isRequired, updateService: PropTypes.func.isRequired, + hibernateService: PropTypes.func.isRequired, + wakeUpService: PropTypes.func.isRequired, showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, }; @@ -55,6 +57,18 @@ export default @observer class TabBar extends Component { this.toggleService({ serviceId, isEnabled: true }); } + hibernateService({ serviceId }) { + if (serviceId) { + this.props.hibernateService({ serviceId }); + } + } + + wakeUpService({ serviceId }) { + if (serviceId) { + this.props.wakeUpService({ serviceId }); + } + } + render() { const { services, @@ -83,6 +97,8 @@ export default @observer class TabBar extends Component { deleteService={deleteService} disableService={args => this.disableService(args)} enableService={args => this.enableService(args)} + hibernateService={args => this.hibernateService(args)} + wakeUpService={args => this.wakeUpService(args)} openSettings={openSettings} distance={20} axis="y" diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index 0f7c29de5..56e5f9c94 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js @@ -145,7 +145,6 @@ export default @observer class EditServiceForm extends Component { isSaving: PropTypes.bool.isRequired, isDeleting: PropTypes.bool.isRequired, isProxyFeatureEnabled: PropTypes.bool.isRequired, - isHibernationFeatureActive: PropTypes.bool.isRequired, }; static defaultProps = { @@ -208,7 +207,6 @@ export default @observer class EditServiceForm extends Component { onDelete, openRecipeFile, isProxyFeatureEnabled, - isHibernationFeatureActive, } = this.props; const { intl } = this.context; @@ -341,14 +339,10 @@ export default @observer class EditServiceForm extends Component {

{intl.formatMessage(messages.headlineGeneral)}

- {isHibernationFeatureActive && ( - <> - -

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

- - )} + +

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

{form.$('isDarkModeEnabled').value && ( diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js index a8ba8748d..d2a9eb6e0 100644 --- a/src/components/settings/settings/EditSettingsForm.js +++ b/src/components/settings/settings/EditSettingsForm.js @@ -171,7 +171,6 @@ export default @observer class EditSettingsForm extends Component { isTodosActivated: PropTypes.bool.isRequired, isWorkspaceEnabled: PropTypes.bool.isRequired, automaticUpdates: PropTypes.bool.isRequired, - hibernationEnabled: PropTypes.bool.isRequired, isDarkmodeEnabled: PropTypes.bool.isRequired, isAdaptableDarkModeEnabled: PropTypes.bool.isRequired, isNightlyEnabled: PropTypes.bool.isRequired, @@ -225,7 +224,6 @@ export default @observer class EditSettingsForm extends Component { isTodosEnabled, isWorkspaceEnabled, automaticUpdates, - hibernationEnabled, isDarkmodeEnabled, isTodosActivated, isNightlyEnabled, @@ -336,13 +334,8 @@ export default @observer class EditSettingsForm extends Component {
- - {hibernationEnabled && ( - <> - +

this.deleteService()} openRecipeFile={file => this.openRecipeFile(file)} isProxyFeatureEnabled={proxyFeature.isEnabled} - isHibernationFeatureActive={settings.app.hibernate} /> ); diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js index 3b8f03ae4..5f29ee385 100644 --- a/src/containers/settings/EditSettingsScreen.js +++ b/src/containers/settings/EditSettingsScreen.js @@ -87,10 +87,6 @@ const messages = defineMessages({ id: 'settings.app.form.sentry', defaultMessage: '!!!Send telemetry data', }, - hibernate: { - id: 'settings.app.form.hibernate', - defaultMessage: '!!!Enable service hibernation', - }, hibernateOnStartup: { id: 'settings.app.form.hibernateOnStartup', defaultMessage: '!!!Keep services in hibernation on startup', @@ -432,11 +428,6 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e value: settings.all.app.sentry, default: DEFAULT_APP_SETTINGS.sentry, }, - hibernate: { - label: intl.formatMessage(messages.hibernate), - value: settings.all.app.hibernate, - default: DEFAULT_APP_SETTINGS.hibernate, - }, hibernateOnStartup: { label: intl.formatMessage(messages.hibernateOnStartup), value: settings.all.app.hibernateOnStartup, @@ -653,7 +644,6 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e isWorkspaceEnabled={workspaces.isFeatureActive} lockingFeatureEnabled={lockingFeatureEnabled} automaticUpdates={this.props.stores.settings.app.automaticUpdates} - hibernationEnabled={this.props.stores.settings.app.hibernate} isDarkmodeEnabled={this.props.stores.settings.app.darkMode} isAdaptableDarkModeEnabled={this.props.stores.settings.app.adaptableDarkMode} isTodosActivated={this.props.stores.todos.isFeatureEnabledByUser} diff --git a/src/environment.js b/src/environment.js index e13e5f676..6a126e0c6 100644 --- a/src/environment.js +++ b/src/environment.js @@ -132,7 +132,6 @@ export const DEFAULT_APP_SETTINGS = { scheduledDNDEnabled: false, scheduledDNDStart: '17:00', scheduledDNDEnd: '09:00', - hibernate: false, hibernateOnStartup: true, hibernationStrategy: 300, inactivityLock: 0, diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index 0cedeb4cb..7d4a0fe06 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -1867,29 +1867,55 @@ } }, { - "defaultMessage": "!!!Delete Service", + "defaultMessage": "!!!Hibernate Service", "end": { "column": 3, "line": 55 }, "file": "src/components/services/tabs/TabItem.js", + "id": "tabs.item.hibernateService", + "start": { + "column": 20, + "line": 52 + } + }, + { + "defaultMessage": "!!!Wake Up Service", + "end": { + "column": 3, + "line": 59 + }, + "file": "src/components/services/tabs/TabItem.js", + "id": "tabs.item.wakeUpService", + "start": { + "column": 17, + "line": 56 + } + }, + { + "defaultMessage": "!!!Delete Service", + "end": { + "column": 3, + "line": 63 + }, + "file": "src/components/services/tabs/TabItem.js", "id": "tabs.item.deleteService", "start": { "column": 17, - "line": 52 + "line": 60 } }, { "defaultMessage": "!!!Do you really want to delete the {serviceName} service?", "end": { "column": 3, - "line": 59 + "line": 67 }, "file": "src/components/services/tabs/TabItem.js", "id": "tabs.item.confirmDeleteService", "start": { "column": 24, - "line": 56 + "line": 64 } } ], diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 5ff146ea5..76fc7cfc2 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -453,7 +453,9 @@ "tabs.item.enableAudio": "Enable audio", "tabs.item.enableNotification": "Enable notifications", "tabs.item.enableService": "Enable service", + "tabs.item.hibernateService": "Hibernate service", "tabs.item.reload": "Reload", + "tabs.item.wakeUpService": "Wake up service", "validation.email": "{field} is not valid", "validation.minLength": "{field} should be at least {length} characters long", "validation.oneRequired": "At least one is required", diff --git a/src/i18n/messages/src/components/services/tabs/TabItem.json b/src/i18n/messages/src/components/services/tabs/TabItem.json index a1b0d2435..6d4d5f6fd 100644 --- a/src/i18n/messages/src/components/services/tabs/TabItem.json +++ b/src/i18n/messages/src/components/services/tabs/TabItem.json @@ -103,16 +103,42 @@ "column": 3 } }, + { + "id": "tabs.item.hibernateService", + "defaultMessage": "!!!Hibernate Service", + "file": "src/components/services/tabs/TabItem.js", + "start": { + "line": 52, + "column": 20 + }, + "end": { + "line": 55, + "column": 3 + } + }, + { + "id": "tabs.item.wakeUpService", + "defaultMessage": "!!!Wake Up Service", + "file": "src/components/services/tabs/TabItem.js", + "start": { + "line": 56, + "column": 17 + }, + "end": { + "line": 59, + "column": 3 + } + }, { "id": "tabs.item.deleteService", "defaultMessage": "!!!Delete Service", "file": "src/components/services/tabs/TabItem.js", "start": { - "line": 52, + "line": 60, "column": 17 }, "end": { - "line": 55, + "line": 63, "column": 3 } }, @@ -121,11 +147,11 @@ "defaultMessage": "!!!Do you really want to delete the {serviceName} service?", "file": "src/components/services/tabs/TabItem.js", "start": { - "line": 56, + "line": 64, "column": 24 }, "end": { - "line": 59, + "line": 67, "column": 3 } } diff --git a/src/i18n/messages/src/containers/settings/EditSettingsScreen.json b/src/i18n/messages/src/containers/settings/EditSettingsScreen.json index 5c15933e4..33bcde0de 100644 --- a/src/i18n/messages/src/containers/settings/EditSettingsScreen.json +++ b/src/i18n/messages/src/containers/settings/EditSettingsScreen.json @@ -194,29 +194,16 @@ "column": 3 } }, - { - "id": "settings.app.form.hibernate", - "defaultMessage": "!!!Enable service hibernation", - "file": "src/containers/settings/EditSettingsScreen.js", - "start": { - "line": 90, - "column": 13 - }, - "end": { - "line": 93, - "column": 3 - } - }, { "id": "settings.app.form.hibernateOnStartup", "defaultMessage": "!!!Keep services in hibernation on startup", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 94, + "line": 90, "column": 22 }, "end": { - "line": 97, + "line": 93, "column": 3 } }, @@ -225,11 +212,11 @@ "defaultMessage": "!!!Hibernation strategy", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 98, + "line": 94, "column": 23 }, "end": { - "line": 101, + "line": 97, "column": 3 } }, @@ -238,11 +225,11 @@ "defaultMessage": "!!!Todo Server", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 102, + "line": 98, "column": 24 }, "end": { - "line": 105, + "line": 101, "column": 3 } }, @@ -251,11 +238,11 @@ "defaultMessage": "!!!Custom TodoServer", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 106, + "line": 102, "column": 20 }, "end": { - "line": 109, + "line": 105, "column": 3 } }, @@ -264,11 +251,11 @@ "defaultMessage": "!!!Enable Password Lock", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 110, + "line": 106, "column": 14 }, "end": { - "line": 113, + "line": 109, "column": 3 } }, @@ -277,11 +264,11 @@ "defaultMessage": "!!!Password", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 114, + "line": 110, "column": 16 }, "end": { - "line": 117, + "line": 113, "column": 3 } }, @@ -290,11 +277,11 @@ "defaultMessage": "!!!Allow using Touch ID to unlock", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 118, + "line": 114, "column": 22 }, "end": { - "line": 121, + "line": 117, "column": 3 } }, @@ -303,11 +290,11 @@ "defaultMessage": "!!!Lock after inactivity", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 122, + "line": 118, "column": 18 }, "end": { - "line": 125, + "line": 121, "column": 3 } }, @@ -316,11 +303,11 @@ "defaultMessage": "!!!Enable scheduled Do-not-Disturb", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 126, + "line": 122, "column": 23 }, "end": { - "line": 129, + "line": 125, "column": 3 } }, @@ -329,11 +316,11 @@ "defaultMessage": "!!!From", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 130, + "line": 126, "column": 21 }, "end": { - "line": 133, + "line": 129, "column": 3 } }, @@ -342,11 +329,11 @@ "defaultMessage": "!!!To", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 134, + "line": 130, "column": 19 }, "end": { - "line": 137, + "line": 133, "column": 3 } }, @@ -355,11 +342,11 @@ "defaultMessage": "!!!Language", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 138, + "line": 134, "column": 12 }, "end": { - "line": 141, + "line": 137, "column": 3 } }, @@ -368,11 +355,11 @@ "defaultMessage": "!!!Dark Mode", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 142, + "line": 138, "column": 12 }, "end": { - "line": 145, + "line": 141, "column": 3 } }, @@ -381,11 +368,11 @@ "defaultMessage": "!!!Synchronize dark mode with my OS's dark mode setting", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 146, + "line": 142, "column": 21 }, "end": { - "line": 149, + "line": 145, "column": 3 } }, @@ -394,11 +381,11 @@ "defaultMessage": "!!!Enable universal Dark Mode", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 150, + "line": 146, "column": 21 }, "end": { - "line": 153, + "line": 149, "column": 3 } }, @@ -407,11 +394,11 @@ "defaultMessage": "!!!Sidebar width", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 154, + "line": 150, "column": 22 }, "end": { - "line": 157, + "line": 153, "column": 3 } }, @@ -420,11 +407,11 @@ "defaultMessage": "!!!Service icon size", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 158, + "line": 154, "column": 12 }, "end": { - "line": 161, + "line": 157, "column": 3 } }, @@ -433,11 +420,11 @@ "defaultMessage": "!!!Use vertical style", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 162, + "line": 158, "column": 20 }, "end": { - "line": 165, + "line": 161, "column": 3 } }, @@ -446,11 +433,11 @@ "defaultMessage": "!!!Always show workspace drawer", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 166, + "line": 162, "column": 24 }, "end": { - "line": 169, + "line": 165, "column": 3 } }, @@ -459,11 +446,11 @@ "defaultMessage": "!!!Accent color", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 170, + "line": 166, "column": 15 }, "end": { - "line": 173, + "line": 169, "column": 3 } }, @@ -472,11 +459,11 @@ "defaultMessage": "!!!Display disabled services tabs", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 174, + "line": 170, "column": 24 }, "end": { - "line": 177, + "line": 173, "column": 3 } }, @@ -485,11 +472,11 @@ "defaultMessage": "!!!Show unread message badge when notifications are disabled", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 178, + "line": 174, "column": 29 }, "end": { - "line": 181, + "line": 177, "column": 3 } }, @@ -498,11 +485,11 @@ "defaultMessage": "!!!Show draggable area on window", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 182, + "line": 178, "column": 16 }, "end": { - "line": 185, + "line": 181, "column": 3 } }, @@ -511,11 +498,11 @@ "defaultMessage": "!!!Enable spell checking", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 186, + "line": 182, "column": 23 }, "end": { - "line": 189, + "line": 185, "column": 3 } }, @@ -524,11 +511,11 @@ "defaultMessage": "!!!Enable GPU Acceleration", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 190, + "line": 186, "column": 25 }, "end": { - "line": 193, + "line": 189, "column": 3 } }, @@ -537,11 +524,11 @@ "defaultMessage": "!!!Include beta versions", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 194, + "line": 190, "column": 8 }, "end": { - "line": 197, + "line": 193, "column": 3 } }, @@ -550,11 +537,11 @@ "defaultMessage": "!!!Enable updates", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 198, + "line": 194, "column": 20 }, "end": { - "line": 201, + "line": 197, "column": 3 } }, @@ -563,11 +550,11 @@ "defaultMessage": "!!!Enable Franz Todos", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 202, + "line": 198, "column": 15 }, "end": { - "line": 205, + "line": 201, "column": 3 } }, @@ -576,11 +563,11 @@ "defaultMessage": "!!!Keep all workspaces loaded", "file": "src/containers/settings/EditSettingsScreen.js", "start": { - "line": 206, + "line": 202, "column": 27 }, "end": { - "line": 209, + "line": 205, "column": 3 } } diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json index a8ccb5a39..431f12710 100644 --- a/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json +++ b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json @@ -64,4 +64,4 @@ "column": 3 } } -] +] \ No newline at end of file diff --git a/src/models/Service.js b/src/models/Service.js index 397950787..162dcea65 100644 --- a/src/models/Service.js +++ b/src/models/Service.js @@ -37,8 +37,6 @@ export default class Service { @observable isMuted = false; - @observable isHibernating = false; - @observable team = ''; @observable customUrl = ''; @@ -77,7 +75,7 @@ export default class Service { @observable isHibernationEnabled = false; - @observable isHibernating = false; + @observable isHibernationRequested = false; @observable lastUsed = Date.now(); // timestamp @@ -145,14 +143,11 @@ export default class Service { this.recipe = recipe; // Check if "Hibernate on Startup" is enabled and hibernate all services except active one - const { - hibernate, - hibernateOnStartup, - } = window.ferdi.stores.settings.app; + const { hibernateOnStartup } = window.ferdi.stores.settings.app; // The service store is probably not loaded yet so we need to use localStorage data to get active service const isActive = window.localStorage.service && JSON.parse(window.localStorage.service).activeService === this.id; - if (hibernate && hibernateOnStartup && !isActive) { - this.isHibernating = true; + if (hibernateOnStartup && !isActive) { + this.isHibernationRequested = true; } autorun(() => { @@ -185,6 +180,14 @@ export default class Service { return this.recipe.id === todosStore.todoRecipeId; } + @computed get canHibernate() { + return this.isHibernationEnabled; + } + + @computed get isHibernating() { + return this.canHibernate && this.isHibernationRequested; + } + get webview() { if (this.isTodosService) { return todosStore.webview; diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index 9521f8493..6064b9929 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js @@ -166,8 +166,8 @@ export default class ServicesStore extends Store { _serviceMaintenance() { this.all.forEach((service) => { // Defines which services should be hibernated. - if (!service.isActive && (Date.now() - service.lastUsed > ms('5m'))) { - // If service is stale for 5 min, hibernate it. + if (!service.isActive && (Date.now() - service.lastUsed > ms(`${this.stores.settings.all.app.hibernationStrategy}s`))) { + // If service is stale, hibernate it. this._hibernate({ serviceId: service.id }); } @@ -820,19 +820,23 @@ export default class ServicesStore extends Store { @action _hibernate({ serviceId }) { const service = this.one(serviceId); - if (service.isActive || !service.isHibernationEnabled) { + if (!service.canHibernate) { + return; + } + if (service.isActive) { debug('Skipping service hibernation'); return; } debug(`Hibernate ${service.name}`); - service.isHibernating = true; + service.isHibernationRequested = true; } @action _awake({ serviceId }) { + debug('Waking up from service hibernation'); const service = this.one(serviceId); - service.isHibernating = false; + service.isHibernationRequested = false; service.liveFrom = Date.now(); } -- cgit v1.2.3-70-g09d2 From 9c3c441941ad5060ec2db89b805a958a914547f3 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sat, 24 Jul 2021 02:23:48 +0200 Subject: Recipe context isolation (#1456) * Enable service contextIsolation * Enable contextIsolation on the service webviews * Expose a new API window.ferdi in the service main world to allow calling back into the service isolated world * Expose a new IPC message inject-js-unsafe from the service isolated world to execute Javascript in the service main world (i.e., run code without context isolation). While the name contains the "unsafe" suffix to show the lack of context isolation, this should mostly be safe, as no nodejs APIs are available in the injected code. * Refactor the Notifications shim into a part in the isolated world that handles displaying and modifying notifications, and a shim in the main world for the Notifications class. The two communicate via the window.ferdi endpoint and a Promise object can be used to detect notification clicks. * Refactor the screen sharing shim into a part in the isolated world that enumerated shareable screens and windows and a shim in the main world that displays the media selector and completes the media selection promise. * Expose the injectJSUnsafe API to recipes to inject javascript code into the main world without context isolation. * Expose setBadge to the main world The window.ferdi.setBadge API can be used to update the service badge from injected unsafe Javascript * Safer script injection into the service main world Make sure that we don't try to serialize stray objects back from the main world to the isolated world by always surrounding the script to be executed by an anonymous function. * Always read recipe assets as utf8 * Remove window.log from recipes We didn't use it anywhere and its behavior was confusing in production mode. * Inject multiple unsafe scripts at the same time * Find in page without remote module Remove the @electron/remote dependency from the find in page (Ctrl+F) functionality. The remote webContents is replaced with Electron IPC. Synchronous IPC messages are handled in the main Electron process, because the renderer process cannot reply to IPC messages synchronously. * Update to latest contextIsolation recipes * Fixing issue with missing 'fs' functions. Co-authored-by: Vijay A --- recipes | 2 +- src/components/services/content/ServiceWebview.js | 2 +- src/index.js | 30 +++++ src/models/Service.js | 21 +++- src/webview/badge.js | 33 ++++++ src/webview/find.js | 23 ++++ src/webview/lib/RecipeWebview.js | 48 ++++---- src/webview/notifications.js | 88 +++++++++------ src/webview/recipe.js | 132 ++++++++++++---------- src/webview/screenshare.js | 81 ++++++------- 10 files changed, 287 insertions(+), 173 deletions(-) create mode 100644 src/webview/badge.js create mode 100644 src/webview/find.js (limited to 'src/components/services') diff --git a/recipes b/recipes index ebb2cc3c6..3108591b2 160000 --- a/recipes +++ b/recipes @@ -1 +1 @@ -Subproject commit ebb2cc3c68f74ce1d8b8a61d128078753d9a0398 +Subproject commit 3108591b234d011e9681bf9aca366555b64cb8e4 diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js index 9e5fed996..4edbde5e2 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js @@ -83,7 +83,7 @@ class ServiceWebview extends Component { useragent={service.userAgent} disablewebsecurity={service.recipe.disablewebsecurity ? true : undefined} allowpopups - webpreferences={`spellcheck=${isSpellcheckerEnabled ? 1 : 0}, contextIsolation=false`} + webpreferences={`spellcheck=${isSpellcheckerEnabled ? 1 : 0}`} /> ); } diff --git a/src/index.js b/src/index.js index 91004aac7..63e6e3d0f 100644 --- a/src/index.js +++ b/src/index.js @@ -449,6 +449,36 @@ ipcMain.on('feature-basic-auth-cancel', () => { authCallback = noop; }); +// Handle synchronous messages from service webviews. + +ipcMain.on('find-in-page', (e, text, options) => { + const { sender: webContents } = e; + if (webContents !== mainWindow.webContents && typeof (text) === 'string') { + const sanitizedOptions = {}; + for (const option of ['forward', 'findNext', 'matchCase']) { + if (option in options) { + sanitizedOptions[option] = !!options[option]; + } + } + const requestId = webContents.findInPage(text, sanitizedOptions); + debug('Find in page', text, options, requestId); + e.returnValue = requestId; + } else { + e.returnValue = null; + } +}); + +ipcMain.on('stop-find-in-page', (e, action) => { + const { sender: webContents } = e; + if (webContents !== mainWindow.webContents) { + const validActions = ['clearSelection', 'keepSelection', 'activateSelection']; + if (validActions.includes(action)) { + webContents.stopFindInPage(action); + } + } + e.returnValue = null; +}); + // Quit when all windows are closed. app.on('window-all-closed', () => { // On OS X it is common for applications and their menu bar diff --git a/src/models/Service.js b/src/models/Service.js index 74e100ea4..36b310da1 100644 --- a/src/models/Service.js +++ b/src/models/Service.js @@ -275,11 +275,17 @@ export default class Service { debug(this.name, 'modifyRequestHeaders is not defined in the recipe'); } - this.webview.addEventListener('ipc-message', e => handleIPCMessage({ - serviceId: this.id, - channel: e.channel, - args: e.args, - })); + this.webview.addEventListener('ipc-message', async (e) => { + if (e.channel === 'inject-js-unsafe') { + await Promise.all(e.args.map(script => this.webview.executeJavaScript(`"use strict"; (() => { ${script} })();`))); + } else { + handleIPCMessage({ + serviceId: this.id, + channel: e.channel, + args: e.args, + }); + } + }); this.webview.addEventListener('new-window', (event, url, frameName, options) => { debug('new-window', event, url, frameName, options); @@ -334,6 +340,11 @@ export default class Service { this.hasCrashed = true; }); + this.webview.addEventListener('found-in-page', ({ result }) => { + debug('Found in page', result); + this.webview.send('found-in-page', result); + }); + webviewWebContents.on('login', (event, request, authInfo, callback) => { // const authCallback = callback; debug('browser login event', authInfo); diff --git a/src/webview/badge.js b/src/webview/badge.js new file mode 100644 index 000000000..1e02fb56a --- /dev/null +++ b/src/webview/badge.js @@ -0,0 +1,33 @@ +const { ipcRenderer } = require('electron'); + +const debug = require('debug')('Ferdi:Plugin:BadgeHandler'); + +export class BadgeHandler { + constructor() { + this.countCache = { + direct: 0, + indirect: 0, + }; + } + + setBadge(direct, indirect) { + if (this.countCache.direct === direct + && this.countCache.indirect === indirect) return; + + // Parse number to integer + // This will correct errors that recipes may introduce, e.g. + // by sending a String instead of an integer + const directInt = parseInt(direct, 10); + const indirectInt = parseInt(indirect, 10); + + const count = { + direct: Math.max(directInt, 0), + indirect: Math.max(indirectInt, 0), + }; + + ipcRenderer.sendToHost('message-counts', count); + Object.assign(this.countCache, count); + + debug('Sending badge count to host', count); + } +} diff --git a/src/webview/find.js b/src/webview/find.js new file mode 100644 index 000000000..040811d68 --- /dev/null +++ b/src/webview/find.js @@ -0,0 +1,23 @@ +import { ipcRenderer } from 'electron'; +import { FindInPage as ElectronFindInPage } from 'electron-find'; + +// Shim to expose webContents functionality to electron-find without @electron/remote +const webContentsShim = { + findInPage: (text, options = {}) => ipcRenderer.sendSync('find-in-page', text, options), + stopFindInPage: (action) => { + ipcRenderer.sendSync('stop-find-in-page', action); + }, + on: (eventName, listener) => { + if (eventName === 'found-in-page') { + ipcRenderer.on('found-in-page', (_, result) => { + listener({ sender: this }, result); + }); + } + }, +}; + +export default class FindInPage extends ElectronFindInPage { + constructor(options = {}) { + super(webContentsShim, options); + } +} diff --git a/src/webview/lib/RecipeWebview.js b/src/webview/lib/RecipeWebview.js index b8fe7dc52..3bb9352f6 100644 --- a/src/webview/lib/RecipeWebview.js +++ b/src/webview/lib/RecipeWebview.js @@ -1,14 +1,12 @@ import { ipcRenderer } from 'electron'; -import { pathExistsSync, readFile } from 'fs-extra'; +import { exists, pathExistsSync, readFile } from 'fs-extra'; const debug = require('debug')('Ferdi:Plugin:RecipeWebview'); class RecipeWebview { - constructor() { - this.countCache = { - direct: 0, - indirect: 0, - }; + constructor(badgeHandler, notificationsHandler) { + this.badgeHandler = badgeHandler; + this.notificationsHandler = notificationsHandler; ipcRenderer.on('poll', () => { this.loopFunc(); @@ -45,24 +43,7 @@ class RecipeWebview { * me directly to me eg. in a channel */ setBadge(direct = 0, indirect = 0) { - if (this.countCache.direct === direct - && this.countCache.indirect === indirect) return; - - // Parse number to integer - // This will correct errors that recipes may introduce, e.g. - // by sending a String instead of an integer - const directInt = parseInt(direct, 10); - const indirectInt = parseInt(indirect, 10); - - const count = { - direct: Math.max(directInt, 0), - indirect: Math.max(indirectInt, 0), - }; - - ipcRenderer.sendToHost('message-counts', count); - Object.assign(this.countCache, count); - - debug('Sending badge count to host', count); + this.badgeHandler.setBadge(direct, indirect); } /** @@ -85,6 +66,23 @@ class RecipeWebview { }); } + injectJSUnsafe(...files) { + Promise.all(files.map(async (file) => { + if (await exists(file)) { + const data = await readFile(file, 'utf8'); + return data; + } + debug('Script not found', file); + return null; + })).then(async (scripts) => { + const scriptsFound = scripts.filter(script => script !== null); + if (scriptsFound.length > 0) { + debug('Inject scripts to main world', scriptsFound); + ipcRenderer.sendToHost('inject-js-unsafe', ...scriptsFound); + } + }); + } + /** * Set a custom handler for turning on and off dark mode * @@ -96,7 +94,7 @@ class RecipeWebview { onNotify(fn) { if (typeof fn === 'function') { - window.Notification.prototype.onNotify = fn; + this.notificationsHandler.onNotify = fn; } } diff --git a/src/webview/notifications.js b/src/webview/notifications.js index 021f05cc3..39a515143 100644 --- a/src/webview/notifications.js +++ b/src/webview/notifications.js @@ -3,49 +3,65 @@ import uuidV1 from 'uuid/v1'; const debug = require('debug')('Ferdi:Notifications'); -class Notification { - static permission = 'granted'; - - constructor(title = '', options = {}) { - debug('New notification', title, options); - this.title = title; - this.options = options; - this.notificationId = uuidV1(); - - ipcRenderer.sendToHost('notification', this.onNotify({ - title: this.title, - options: this.options, - notificationId: this.notificationId, - })); - - ipcRenderer.once(`notification-onclick:${this.notificationId}`, () => { - if (typeof this.onclick === 'function') { - this.onclick(); - } +export class NotificationsHandler { + onNotify = data => data; + + displayNotification(title, options) { + return new Promise((resolve) => { + debug('New notification', title, options); + + const notificationId = uuidV1(); + + ipcRenderer.sendToHost('notification', this.onNotify({ + title, + options, + notificationId, + })); + + ipcRenderer.once(`notification-onclick:${notificationId}`, () => { + resolve(); + }); }); } +} - static requestPermission(cb = null) { - if (!cb) { - return new Promise((resolve) => { - resolve(Notification.permission); - }); - } +export const notificationsClassDefinition = `(() => { + class Notification { + static permission = 'granted'; - if (typeof (cb) === 'function') { - return cb(Notification.permission); + constructor(title = '', options = {}) { + this.title = title; + this.options = options; + window.ferdi.displayNotification(title, options) + .then(() => { + if (typeof (this.onClick) === 'function') { + this.onClick(); + } + }); } - return Notification.permission; - } + static requestPermission(cb = null) { + if (!cb) { + return new Promise((resolve) => { + resolve(Notification.permission); + }); + } - onNotify(data) { - return data; - } + if (typeof (cb) === 'function') { + return cb(Notification.permission); + } - onClick() {} + return Notification.permission; + } - close() {} -} + onNotify(data) { + return data; + } + + onClick() {} + + close() {} + } -window.Notification = Notification; + window.Notification = Notification; +})();`; diff --git a/src/webview/recipe.js b/src/webview/recipe.js index 8da45864b..d143675dc 100644 --- a/src/webview/recipe.js +++ b/src/webview/recipe.js @@ -1,11 +1,9 @@ /* eslint-disable import/first */ -import { ipcRenderer } from 'electron'; -import { getCurrentWebContents } from '@electron/remote'; +import { contextBridge, ipcRenderer } from 'electron'; import path from 'path'; import { autorun, computed, observable } from 'mobx'; import fs from 'fs-extra'; import { debounce } from 'lodash'; -import { FindInPage } from 'electron-find'; // For some services darkreader tries to use the chrome extension message API // This will cause the service to fail loading @@ -23,16 +21,81 @@ import customDarkModeCss from './darkmode/custom'; import RecipeWebview from './lib/RecipeWebview'; import Userscript from './lib/Userscript'; -import { switchDict, getSpellcheckerLocaleByFuzzyIdentifier } from './spellchecker'; -import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode'; +import { BadgeHandler } from './badge'; import contextMenu from './contextMenu'; -import './notifications'; -import { screenShareCss } from './screenshare'; +import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode'; +import FindInPage from './find'; +import { NotificationsHandler, notificationsClassDefinition } from './notifications'; +import { getDisplayMediaSelector, screenShareCss, screenShareJs } from './screenshare'; +import { switchDict, getSpellcheckerLocaleByFuzzyIdentifier } from './spellchecker'; -import { DEFAULT_APP_SETTINGS, isDevMode } from '../environment'; +import { DEFAULT_APP_SETTINGS } from '../environment'; const debug = require('debug')('Ferdi:Plugin'); +const badgeHandler = new BadgeHandler(); + +const notificationsHandler = new NotificationsHandler(); + +// Patching window.open +const originalWindowOpen = window.open; + +window.open = (url, frameName, features) => { + debug('window.open', url, frameName, features); + if (!url) { + // The service hasn't yet supplied a URL (as used in Skype). + // Return a new dummy window object and wait for the service to change the properties + const newWindow = { + location: { + href: '', + }, + }; + + const checkInterval = setInterval(() => { + // Has the service changed the URL yet? + if (newWindow.location.href !== '') { + if (features) { + originalWindowOpen(newWindow.location.href, frameName, features); + } else { + // Open the new URL + ipcRenderer.sendToHost('new-window', newWindow.location.href); + } + clearInterval(checkInterval); + } + }, 0); + + setTimeout(() => { + // Stop checking for location changes after 1 second + clearInterval(checkInterval); + }, 1000); + + return newWindow; + } + + // We need to differentiate if the link should be opened in a popup or in the systems default browser + if (!frameName && !features && typeof features !== 'string') { + return ipcRenderer.sendToHost('new-window', url); + } + + if (url) { + return originalWindowOpen(url, frameName, features); + } +}; + +// We can't override APIs here, so we first expose functions via window.ferdi, +// then overwrite the corresponding field of the window object by injected JS. +contextBridge.exposeInMainWorld('ferdi', { + open: window.open, + setBadge: (direct, indirect) => badgeHandler.setBadge(direct || 0, indirect || 0), + displayNotification: (title, options) => notificationsHandler.displayNotification(title, options), + getDisplayMediaSelector, +}); + +ipcRenderer.sendToHost('inject-js-unsafe', + 'window.open = window.ferdi.open;', + notificationsClassDefinition, + screenShareJs); + class RecipeController { @observable settings = { overrideSpellcheckerLanguage: false, @@ -97,7 +160,7 @@ class RecipeController { autorun(() => this.update()); document.addEventListener('DOMContentLoaded', () => { - this.findInPage = new FindInPage(getCurrentWebContents(), { + this.findInPage = new FindInPage({ inputFocusColor: '#CE9FFC', textColor: '#212121', }); @@ -111,7 +174,7 @@ class RecipeController { // Delete module from cache delete require.cache[require.resolve(modulePath)]; try { - this.recipe = new RecipeWebview(); + this.recipe = new RecipeWebview(badgeHandler, notificationsHandler); // eslint-disable-next-line require(modulePath)(this.recipe, {...config, recipe,}); debug('Initialize Recipe', config, recipe); @@ -327,52 +390,3 @@ class RecipeController { /* eslint-disable no-new */ new RecipeController(); /* eslint-enable no-new */ - -// Patching window.open -const originalWindowOpen = window.open; - -window.open = (url, frameName, features) => { - debug('window.open', url, frameName, features); - if (!url) { - // The service hasn't yet supplied a URL (as used in Skype). - // Return a new dummy window object and wait for the service to change the properties - const newWindow = { - location: { - href: '', - }, - }; - - const checkInterval = setInterval(() => { - // Has the service changed the URL yet? - if (newWindow.location.href !== '') { - if (features) { - originalWindowOpen(newWindow.location.href, frameName, features); - } else { - // Open the new URL - ipcRenderer.sendToHost('new-window', newWindow.location.href); - } - clearInterval(checkInterval); - } - }, 0); - - setTimeout(() => { - // Stop checking for location changes after 1 second - clearInterval(checkInterval); - }, 1000); - - return newWindow; - } - - // We need to differentiate if the link should be opened in a popup or in the systems default browser - if (!frameName && !features && typeof features !== 'string') { - return ipcRenderer.sendToHost('new-window', url); - } - - if (url) { - return originalWindowOpen(url, frameName, features); - } -}; - -if (isDevMode) { - window.log = console.log; -} diff --git a/src/webview/screenshare.js b/src/webview/screenshare.js index 84d2e1e95..ab548a625 100644 --- a/src/webview/screenshare.js +++ b/src/webview/screenshare.js @@ -2,6 +2,27 @@ import { desktopCapturer } from 'electron'; const CANCEL_ID = 'desktop-capturer-selection__cancel'; +export async function getDisplayMediaSelector() { + const sources = await desktopCapturer.getSources({ types: ['screen', 'window'] }); + return `

+
    + ${sources.map(({ id, name, thumbnail }) => ` +
  • + +
  • + `).join('')} +
  • + +
  • +
+
`; +} + export const screenShareCss = ` .desktop-capturer-selection { position: fixed; @@ -72,38 +93,12 @@ export const screenShareCss = ` } `; -// Patch getDisplayMedia for screen sharing -window.navigator.mediaDevices.getDisplayMedia = () => async (resolve, reject) => { +export const screenShareJs = ` +window.navigator.mediaDevices.getDisplayMedia = () => new Promise(async (resolve, reject) => { try { - const sources = await desktopCapturer.getSources({ - types: ['screen', 'window'], - }); - const selectionElem = document.createElement('div'); - selectionElem.classList = 'desktop-capturer-selection'; - selectionElem.innerHTML = ` -
-
    - ${sources - .map( - ({ id, name, thumbnail }) => ` -
  • - -
  • - `, - ) - .join('')} -
  • - -
  • -
-
- `; + selectionElem.classList = ['desktop-capturer-selection']; + selectionElem.innerHTML = await window.ferdi.getDisplayMediaSelector(); document.body.appendChild(selectionElem); document @@ -112,25 +107,18 @@ window.navigator.mediaDevices.getDisplayMedia = () => async (resolve, reject) => button.addEventListener('click', async () => { try { const id = button.getAttribute('data-id'); - if (id === CANCEL_ID) { + if (id === '${CANCEL_ID}') { reject(new Error('Cancelled by user')); } else { - const mediaSource = sources.find((source) => source.id === id); - if (!mediaSource) { - throw new Error(`Source with id ${id} does not exist`); - } - - const stream = await window.navigator.mediaDevices.getUserMedia( - { - audio: false, - video: { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: mediaSource.id, - }, + const stream = await window.navigator.mediaDevices.getUserMedia({ + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: id, }, }, - ); + }); resolve(stream); } } catch (err) { @@ -143,4 +131,5 @@ window.navigator.mediaDevices.getDisplayMedia = () => async (resolve, reject) => } catch (err) { reject(err); } -}; +}); +`; -- cgit v1.2.3-70-g09d2 From f4b4416ea52d564bc2dbe543a82084ed98843ccc Mon Sep 17 00:00:00 2001 From: Markus Hatvan Date: Fri, 30 Jul 2021 10:54:54 +0200 Subject: chore: migrate from tslint to @typescript-eslint (#1706) - update .eslintrc to work for .js and .ts - update devDependencies - lint properly both root /src and nested /packages - update webhint recommended setting for tsconfig.json to shrink output - Manage all eslint rules from the repo root - escape single quotes in scripts to please windows build Co-authored-by: Vijay A --- .eslintignore | 3 +- .eslintrc | 163 +++++--- package-lock.json | 439 ++++++--------------- package.json | 11 +- packages/forms/src/button/index.tsx | 29 +- packages/forms/src/error/index.tsx | 13 +- packages/forms/src/input/index.tsx | 63 ++- packages/forms/src/input/scorePassword.ts | 6 +- packages/forms/src/label/index.tsx | 13 +- packages/forms/src/select/index.tsx | 119 +++--- packages/forms/src/textarea/index.tsx | 22 +- packages/forms/src/toggle/index.tsx | 2 +- packages/forms/src/wrapper/index.tsx | 9 +- packages/forms/tslint.json | 3 - packages/theme/src/index.ts | 12 +- packages/theme/src/themes/IStyleTypes.ts | 1 - packages/theme/src/themes/dark/index.ts | 30 +- packages/theme/src/themes/default/index.ts | 14 +- packages/theme/tslint.json | 3 - packages/typings/types/react-loader.d.ts | 2 +- packages/ui/src/badge/ProBadge.tsx | 11 +- packages/ui/src/badge/index.tsx | 9 +- packages/ui/src/headline/index.tsx | 15 +- packages/ui/src/icon/index.tsx | 7 +- packages/ui/src/infobox/index.tsx | 49 +-- packages/ui/src/loader/index.tsx | 10 +- packages/ui/tslint.json | 3 - src/actions/lib/actions.js | 4 +- src/api/server/LocalApi.js | 20 +- src/api/server/ServerApi.js | 288 ++++++++------ src/app.js | 8 +- src/components/auth/AuthLayout.js | 28 +- src/components/auth/ChangeServer.js | 4 +- src/components/auth/Locked.js | 2 +- src/components/auth/Login.js | 2 +- src/components/auth/Password.js | 2 +- src/components/auth/SetupAssistant.js | 10 +- src/components/auth/Signup.js | 2 +- src/components/auth/Welcome.js | 2 +- src/components/layout/AppLayout.js | 4 +- .../services/content/ConnectionLostBanner.js | 2 +- .../services/content/ErrorHandlers/styles.js | 2 +- src/components/services/content/Services.js | 2 +- .../services/content/WebviewCrashHandler.js | 2 +- src/components/services/tabs/Tabbar.js | 10 +- .../settings/services/EditServiceForm.js | 2 +- .../settings/services/ServicesDashboard.js | 4 +- .../settings/settings/EditSettingsForm.js | 23 +- .../settings/supportFerdi/SupportFerdiDashboard.js | 18 +- src/components/settings/user/EditUserForm.js | 2 +- src/components/ui/AppLoader/index.js | 2 +- src/components/ui/FeatureItem.js | 2 +- src/components/ui/FeatureList.js | 2 +- src/components/ui/Link.js | 2 +- src/components/ui/Modal/styles.js | 2 +- src/components/ui/Radio.js | 2 +- src/components/ui/SearchInput.js | 2 +- src/components/ui/Select.js | 4 +- src/components/ui/ServiceIcon.js | 2 +- src/components/ui/Slider.js | 2 +- src/components/ui/Tabs/TabItem.js | 8 +- src/components/ui/Tabs/Tabs.js | 2 +- src/components/ui/Toggle.js | 2 +- src/components/ui/ToggleRaw.js | 2 +- src/components/ui/WebviewLoader/styles.js | 2 +- src/components/util/ErrorBoundary/styles.js | 2 +- src/containers/auth/SetupAssistantScreen.js | 2 +- src/containers/auth/SignupScreen.js | 2 +- src/containers/layout/AppLayoutContainer.js | 4 +- src/containers/settings/AccountScreen.js | 5 +- src/containers/settings/EditServiceScreen.js | 4 +- src/containers/settings/EditSettingsScreen.js | 2 +- src/containers/settings/EditUserScreen.js | 2 +- src/containers/settings/RecipesScreen.js | 10 +- .../announcements/components/AnnouncementScreen.js | 2 +- src/features/communityRecipes/store.js | 2 +- src/features/publishDebugInfo/Component.js | 6 +- src/features/quickSwitch/Component.js | 4 +- src/features/shareFranz/Component.js | 2 +- src/features/todos/containers/TodosScreen.js | 4 +- src/features/utils/ActionBinding.js | 4 +- src/features/utils/FeatureStore.js | 8 +- src/features/webControls/components/WebControls.js | 4 +- .../webControls/containers/WebControlsScreen.js | 2 +- src/features/workspaces/api.js | 2 +- .../workspaces/components/WorkspaceDrawer.js | 2 +- .../workspaces/components/WorkspaceDrawerItem.js | 2 +- .../workspaces/components/WorkspaceItem.js | 2 +- .../components/WorkspaceServiceListItem.js | 2 +- .../components/WorkspaceSwitchingIndicator.js | 2 +- .../workspaces/components/WorkspacesDashboard.js | 4 +- .../workspaces/containers/WorkspacesScreen.js | 4 +- src/features/workspaces/models/Workspace.js | 4 +- src/helpers/array-helpers.js | 6 +- src/helpers/async-helpers.js | 2 +- src/helpers/service-helpers.js | 2 +- src/helpers/validation-helpers.js | 2 +- src/models/Recipe.js | 2 +- src/models/Service.js | 2 +- src/models/UserAgent.js | 4 +- src/stores/AppStore.js | 105 +++-- src/stores/NewsStore.js | 2 +- src/stores/RecipePreviewsStore.js | 2 +- src/stores/RecipesStore.js | 8 +- src/stores/ServicesStore.js | 44 +-- src/stores/UIStore.js | 2 +- src/stores/UserStore.js | 4 +- src/stores/lib/CachedRequest.js | 4 +- src/stores/lib/Reaction.js | 4 +- src/stores/lib/Request.js | 2 +- src/stores/lib/Store.js | 6 +- src/webview/contextMenuBuilder.js | 4 +- src/webview/lib/RecipeWebview.js | 2 +- src/webview/notifications.js | 2 +- src/webview/spellchecker.js | 2 +- tsconfig.settings.json | 3 + tslint.json | 12 - uidev/src/app.tsx | 2 +- uidev/src/stores/stories.ts | 2 - uidev/src/stories/button.stories.tsx | 72 ++-- uidev/src/stories/headline.stories.tsx | 3 - uidev/src/stories/infobox.stories.tsx | 10 +- uidev/src/stories/input.stories.tsx | 4 +- uidev/src/stories/loader.stories.tsx | 3 - uidev/src/stories/select.stories.tsx | 2 +- uidev/src/stories/textarea.stories.tsx | 2 +- uidev/src/stories/toggle.stories.tsx | 19 +- uidev/src/withTheme/index.tsx | 16 +- uidev/tslint.json | 3 - 129 files changed, 964 insertions(+), 1039 deletions(-) delete mode 100644 packages/forms/tslint.json delete mode 100644 packages/theme/tslint.json delete mode 100644 packages/ui/tslint.json delete mode 100644 tslint.json delete mode 100644 uidev/tslint.json (limited to 'src/components/services') diff --git a/.eslintignore b/.eslintignore index 52fde4c3e..5385bbb5f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,6 @@ build/ out/ packages/*/lib +packages/**/*.test.* src/internal-server -recipes/ \ No newline at end of file +recipes/ diff --git a/.eslintrc b/.eslintrc index 668f688ae..5e8d4cebf 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,71 +3,124 @@ "parser": "@babel/eslint-parser", "extends": "eslint-config-airbnb", "plugins": ["jest"], - "rules": { - "arrow-parens": 0, - "consistent-return": 0, - "no-param-reassign": 0, - "import/extensions": 0, - "import/no-extraneous-dependencies": 0, - "import/no-unresolved": [ - 2, - { - "ignore": ["electron"] - } - ], - "import/prefer-default-export": 0, - "linebreak-style": 0, - "react/static-property-placement": 0, - "react/state-in-constructor": 0, - "react/jsx-props-no-spreading": 0, - "react/prefer-stateless-function": 0, - "react/jsx-filename-extension": [ - 1, - { - "extensions": [".js", ".jsx"] - } - ], - "react/forbid-prop-types": 0, - "react/destructuring-assignment": 0, - "prefer-destructuring": 1, - "no-underscore-dangle": 0, - "max-len": 0, - "class-methods-use-this": 0, - "no-console": 0, - "react/jsx-no-bind": 0, - "react/sort-comp": 0, - "jsx-a11y/no-static-element-interactions": 0, - "react/jsx-no-target-blank": 0, - "no-restricted-syntax": [0, "ForInStatement"], - "jsx-a11y/no-noninteractive-element-interactions": 1, - "jsx-a11y/label-has-for": [ - 2, - { - "components": ["Label"], - "required": { - "every": ["id"] - }, - "allowChildren": false + "overrides": [ + { + "files": ["**/*.ts", "**/*.tsx"], + "env": { "browser": true, "es6": true, "node": true }, + "extends": ["airbnb-typescript"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { "jsx": true }, + "ecmaVersion": 2018, + "sourceType": "module", + "project": "./tsconfig.json" + }, + "plugins": ["@typescript-eslint"], + "rules": { + // eslint + "arrow-parens": 0, + "array-callback-return": 1, + "class-methods-use-this": 0, + "consistent-return": 0, + "implicit-arrow-linebreak": 0, + "linebreak-style": 0, + "max-len": 0, + "no-confusing-arrow": 0, + "no-console": 0, + "no-param-reassign": 0, + "no-return-assign": 1, + "no-underscore-dangle": 0, + "no-use-before-define": 0, + "prefer-destructuring": 1, + "object-curly-newline": 0, + "operator-linebreak": 0, + // @typescript-eslint + "@typescript-eslint/indent": 0, + "@typescript-eslint/no-shadow": 0, + "@typescript-eslint/no-unused-expressions": 0, + // eslint-plugin-import + "import/extensions": 0, + "import/no-cycle": 1, + "import/no-extraneous-dependencies": 0, + "import/no-unresolved": 0, + "import/prefer-default-export": 0, + // eslint-plugin-react + "react/destructuring-assignment": 0, + "react/button-has-type": 0, + "react/forbid-prop-types": 0, + "react/jsx-curly-newline": 0, + "react/jsx-no-bind": 0, + "react/jsx-no-target-blank": 0, + "react/jsx-props-no-spreading": 0, + "react/no-deprecated": 1, + "react/no-array-index-key": 0, + "react/prefer-stateless-function": 0, + "react/sort-comp": 0, + "react/state-in-constructor": 0, + "react/static-property-placement": 0, + // eslint-plugin-jsx-a11y + "jsx-a11y/click-events-have-key-events": 1, + "jsx-a11y/mouse-events-have-key-events": 1, + "jsx-a11y/label-has-for": [ + 2, + { + "components": ["Label"], + "required": { + "every": ["id"] + }, + "allowChildren": false + } + ], + "jsx-a11y/no-static-element-interactions": 0, + "jsx-a11y/no-noninteractive-element-interactions": 1 } - ], - "jsx-a11y/click-events-have-key-events": 1 + } + ], + "settings": { + "react": { + "pragma": "React", // Pragma to use, default to "React" + "version": "detect" // React version. "detect" automatically picks the version you have installed. + } }, "globals": { "window": true, "document": true, - "ENV": true, "FormData": true, "localStorage": true, "navigator": true, - "Worker": true, - "atob": true, - "btoa": true, - "ga": true, - "mocha": true, - "Element": true, - "electron": true + "Element": true }, "env": { "jest/globals": true + }, + "rules": { + // eslint + "prefer-destructuring": 1, + "class-methods-use-this": 1, + "consistent-return": 1, + "max-len": 0, + "no-param-reassign": 1, + "no-restricted-syntax": 0, + "no-underscore-dangle": 0, + "operator-linebreak": 0, + // eslint-plugin-import + "import/extensions": 1, + "import/prefer-default-export": 0, + "import/no-extraneous-dependencies": 1, + "import/no-unresolved": 1, + // eslint-plugin-react + "react/forbid-prop-types": 1, + "react/destructuring-assignment": 0, + "react/jsx-filename-extension": 1, + "react/jsx-no-bind": 1, + "react/jsx-props-no-spreading": 0, + "react/prefer-stateless-function": 1, + "react/static-property-placement": 0, + "react/state-in-constructor": 1, + "react/sort-comp": 0, + // eslint-plugin-jsx-a11y + "jsx-a11y/click-events-have-key-events": 1, + "jsx-a11y/no-static-element-interactions": 1, + "jsx-a11y/no-noninteractive-element-interactions": 1 } } diff --git a/package-lock.json b/package-lock.json index 45c38f01b..cb80ba47a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3526,46 +3526,6 @@ } } }, - "@fimbul/bifrost": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@fimbul/bifrost/-/bifrost-0.21.0.tgz", - "integrity": "sha512-ou8VU+nTmOW1jeg+FT+sn+an/M0Xb9G16RucrfhjXGWv1Q97kCoM5CG9Qj7GYOSdu7km72k7nY83Eyr53Bkakg==", - "dev": true, - "requires": { - "@fimbul/ymir": "^0.21.0", - "get-caller-file": "^2.0.0", - "tslib": "^1.8.1", - "tsutils": "^3.5.0" - }, - "dependencies": { - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "tsutils": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", - "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - } - } - }, - "@fimbul/ymir": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@fimbul/ymir/-/ymir-0.21.0.tgz", - "integrity": "sha512-T/y7WqPsm4n3zhT08EpB5sfdm2Kvw3gurAxr2Lr5dQeLi8ZsMlNT/Jby+ZmuuAAd1PnXYzKp+2SXgIkQIIMCUg==", - "dev": true, - "requires": { - "inversify": "^5.0.0", - "reflect-metadata": "^0.1.12", - "tslib": "^1.8.1" - } - }, "@hapi/address": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", @@ -8175,22 +8135,6 @@ "@types/ms": "*" } }, - "@types/eslint": { - "version": "7.2.13", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.13.tgz", - "integrity": "sha512-LKmQCWAlnVHvvXq4oasNUMTJJb2GwSyTY8+1C7OH5ILR8mPLaljv1jxL1bXW3xB3jFbQxTKxJAvI8PyjB09aBg==", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/estree": { - "version": "0.0.48", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz", - "integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==", - "dev": true - }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -8484,6 +8428,38 @@ "integrity": "sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==", "dev": true }, + "@typescript-eslint/eslint-plugin": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.5.tgz", + "integrity": "sha512-m31cPEnbuCqXtEZQJOXAHsHvtoDi9OVaeL5wZnO2KZTnkvELk+u6J6jHg+NzvWQxk+87Zjbc4lJS4NHmgImz6Q==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "4.28.5", + "@typescript-eslint/scope-manager": "4.28.5", + "debug": "^4.3.1", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.1.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "@typescript-eslint/experimental-utils": { "version": "4.28.5", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.5.tgz", @@ -8536,6 +8512,35 @@ } } }, + "@typescript-eslint/parser": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.5.tgz", + "integrity": "sha512-NPCOGhTnkXGMqTznqgVbA5LqVsnw+i3+XA1UKLnAb+MG1Y1rP4ZSK9GX0kJBmAZTMIktf+dTwXToT6kFwyimbw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "4.28.5", + "@typescript-eslint/types": "4.28.5", + "@typescript-eslint/typescript-estree": "4.28.5", + "debug": "^4.3.1" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "@typescript-eslint/scope-manager": { "version": "4.28.5", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.5.tgz", @@ -11698,12 +11703,6 @@ } } }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, "builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -16965,6 +16964,23 @@ } } }, + "eslint-config-airbnb-typescript": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-12.3.1.tgz", + "integrity": "sha512-ql/Pe6/hppYuRp4m3iPaHJqkBB7dgeEmGPQ6X0UNmrQOfTF+dXw29/ZjU2kQ6RDoLxaxOA+Xqv07Vbef6oVTWw==", + "dev": true, + "requires": { + "@typescript-eslint/parser": "^4.4.1", + "eslint-config-airbnb": "^18.2.0", + "eslint-config-airbnb-base": "^14.2.0" + } + }, + "eslint-config-prettier": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true + }, "eslint-import-resolver-node": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", @@ -17237,6 +17253,15 @@ } } }, + "eslint-plugin-prettier": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz", + "integrity": "sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, "eslint-plugin-react": { "version": "7.24.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz", @@ -17289,6 +17314,12 @@ } } }, + "eslint-plugin-react-hooks": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", + "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", + "dev": true + }, "eslint-scope": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", @@ -17322,120 +17353,6 @@ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true }, - "eslint-webpack-plugin": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-2.5.4.tgz", - "integrity": "sha512-7rYh0m76KyKSDE+B+2PUQrlNS4HJ51t3WKpkJg6vo2jFMbEPTG99cBV0Dm7LXSHucN4WGCG65wQcRiTFrj7iWw==", - "dev": true, - "requires": { - "@types/eslint": "^7.2.6", - "arrify": "^2.0.1", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "normalize-path": "^3.0.0", - "schema-utils": "^3.0.0" - }, - "dependencies": { - "arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "dev": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - } - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true - }, - "schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, "espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", @@ -17927,6 +17844,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-glob": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", @@ -22075,12 +21998,6 @@ "loose-envify": "^1.0.0" } }, - "inversify": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.1.tgz", - "integrity": "sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ==", - "dev": true - }, "invert-kv": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", @@ -30130,6 +30047,15 @@ "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", "dev": true }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -31734,12 +31660,6 @@ } } }, - "reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "dev": true - }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -34842,152 +34762,15 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" }, - "tslint": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", - "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^4.0.1", - "glob": "^7.1.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.8.0", - "tsutils": "^2.29.0" - }, - "dependencies": { - "diff": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", - "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", - "dev": true - }, - "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "tslint-config-airbnb": { - "version": "5.11.2", - "resolved": "https://registry.npmjs.org/tslint-config-airbnb/-/tslint-config-airbnb-5.11.2.tgz", - "integrity": "sha512-mUpHPTeeCFx8XARGG/kzYP4dPSOgoCqNiYbGHh09qTH8q+Y1ghsOgaeZKYYQT7IyxMos523z/QBaiv2zKNBcow==", - "dev": true, - "requires": { - "tslint-consistent-codestyle": "^1.14.1", - "tslint-eslint-rules": "^5.4.0", - "tslint-microsoft-contrib": "~5.2.1" - } - }, - "tslint-consistent-codestyle": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/tslint-consistent-codestyle/-/tslint-consistent-codestyle-1.16.0.tgz", - "integrity": "sha512-ebR/xHyMEuU36hGNOgCfjGBNYxBPixf0yU1Yoo6s3BrpBRFccjPOmIVaVvQsWAUAMdmfzHOCihVkcaMfimqvHw==", - "dev": true, - "requires": { - "@fimbul/bifrost": "^0.21.0", - "tslib": "^1.7.1", - "tsutils": "^2.29.0" - } - }, - "tslint-eslint-rules": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-5.4.0.tgz", - "integrity": "sha512-WlSXE+J2vY/VPgIcqQuijMQiel+UtmXS+4nvK4ZzlDiqBfXse8FAvkNnTcYhnQyOTW5KFM+uRRGXxYhFpuBc6w==", - "dev": true, - "requires": { - "doctrine": "0.7.2", - "tslib": "1.9.0", - "tsutils": "^3.0.0" - }, - "dependencies": { - "doctrine": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz", - "integrity": "sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=", - "dev": true, - "requires": { - "esutils": "^1.1.6", - "isarray": "0.0.1" - } - }, - "esutils": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz", - "integrity": "sha1-wBzKqa5LiXxtDD4hCuUvPHqEQ3U=", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "tslib": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", - "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==", - "dev": true - }, - "tsutils": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", - "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - } - } - }, - "tslint-microsoft-contrib": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/tslint-microsoft-contrib/-/tslint-microsoft-contrib-5.2.1.tgz", - "integrity": "sha512-PDYjvpo0gN9IfMULwKk0KpVOPMhU6cNoT9VwCOLeDl/QS8v8W2yspRpFFuUS7/c5EIH/n8ApMi8TxJAz1tfFUA==", - "dev": true, - "requires": { - "tsutils": "^2.27.2 <2.29.0" - }, - "dependencies": { - "tsutils": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.28.0.tgz", - "integrity": "sha512-bh5nAtW0tuhvOJnx1GLRn5ScraRLICGyJV5wJhtRWOLsxW70Kk5tZtpK3O/hW6LDnqKS9mlUMPZj9fEMJ0gxqA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - } - } - }, "tsscmp": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==" }, "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "requires": { "tslib": "^1.8.1" diff --git a/package.json b/package.json index 524a445f1..6bc86ea6d 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "dev": "cross-env NODE_ENV=development gulp dev", "test": "jest", "test:watch": "jest --watch", - "lint": "eslint --quiet --fix src", + "lint": "eslint \"{src,packages,uidev}/**/*.{js,jsx,ts,tsx}\" --quiet --fix", "manage-translations": "node ./src/i18n/manage-translations.js", "prebuild": "preval-build-info-cli && gulp build", "build": "electron-builder", @@ -156,6 +156,8 @@ "@types/react": "16.14.6", "@types/react-dom": "16.9.13", "@types/uuid": "3.4.9", + "@typescript-eslint/eslint-plugin": "4.28.5", + "@typescript-eslint/parser": "4.28.5", "all-contributors-cli": "6.20.0", "babel-loader": "8.2.2", "babel-plugin-react-intl": "3.5.1", @@ -169,11 +171,14 @@ "electron-rebuild": "2.3.5", "eslint": "7.31.0", "eslint-config-airbnb": "18.2.1", + "eslint-config-airbnb-typescript": "12.3.1", + "eslint-config-prettier": "8.3.0", "eslint-plugin-import": "2.23.4", "eslint-plugin-jest": "24.4.0", "eslint-plugin-jsx-a11y": "6.4.1", + "eslint-plugin-prettier": "3.4.0", "eslint-plugin-react": "7.24.0", - "eslint-webpack-plugin": "2.5.4", + "eslint-plugin-react-hooks": "4.2.0", "expect.js": "0.3.1", "gulp": "4.0.0", "gulp-babel": "8.0.0", @@ -196,8 +201,6 @@ "sass": "1.36.0", "terser-webpack-plugin": "1.4.5", "ts-loader": "5.4.5", - "tslint": "5.20.1", - "tslint-config-airbnb": "5.11.2", "typescript": "3.9.10", "webpack": "4.46.0", "webpack-cli": "3.3.12", diff --git a/packages/forms/src/button/index.tsx b/packages/forms/src/button/index.tsx index ecb2876ca..c08c4e97d 100644 --- a/packages/forms/src/button/index.tsx +++ b/packages/forms/src/button/index.tsx @@ -8,14 +8,24 @@ import Loader from 'react-loader'; import { IFormField, IWithStyle } from '../typings/generic'; import { Theme } from '../../../theme'; -type ButtonType = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'inverted'; +type ButtonType = + | 'primary' + | 'secondary' + | 'success' + | 'danger' + | 'warning' + | 'inverted'; interface IProps extends IFormField, IWithStyle { className?: string; disabled?: boolean; id?: string; type?: 'button' | 'reset' | 'submit' | undefined; - onClick: (event: React.MouseEvent | React.MouseEvent) => void; + onClick: ( + event: + | React.MouseEvent + | React.MouseEvent, + ) => void; buttonType?: ButtonType; stretch?: boolean; loaded?: boolean; @@ -25,10 +35,6 @@ interface IProps extends IFormField, IWithStyle { target?: string; } -interface IState { - busy: boolean; -} - const styles = (theme: Theme) => ({ button: { borderRadius: theme.borderRadiusSmall, @@ -40,7 +46,8 @@ const styles = (theme: Theme) => ({ outline: 'none', alignItems: 'center', padding: 0, - width: (props: IProps) => (props.stretch ? '100%' : 'auto') as Property.Width, + width: (props: IProps) => + (props.stretch ? '100%' : 'auto') as Property.Width, fontSize: theme.uiFontSize, textDecoration: 'none', // height: theme.buttonHeight, @@ -125,7 +132,8 @@ const styles = (theme: Theme) => ({ transition: 'all 0.3s', marginLeft: (props: IProps): number => (!props.busy ? 10 : 20), marginRight: (props: IProps): number => (!props.busy ? -10 : -20), - position: (props: IProps): Property.Position => props.stretch ? 'absolute' : 'inherit', + position: (props: IProps): Property.Position => + props.stretch ? 'absolute' : 'inherit', }, icon: { margin: [1, 10, 0, -5], @@ -175,7 +183,6 @@ class ButtonComponent extends Component { buttonType, loaded, icon, - busy: busyProp, href, target, } = this.props; @@ -185,7 +192,9 @@ class ButtonComponent extends Component { let showLoader = false; if (loaded) { showLoader = !loaded; - console.warn('Ferdi Button prop `loaded` will be deprecated in the future. Please use `busy` instead'); + console.warn( + 'Ferdi Button prop `loaded` will be deprecated in the future. Please use `busy` instead', + ); } if (busy) { showLoader = busy; diff --git a/packages/forms/src/error/index.tsx b/packages/forms/src/error/index.tsx index a487bb281..243321d97 100644 --- a/packages/forms/src/error/index.tsx +++ b/packages/forms/src/error/index.tsx @@ -11,18 +11,9 @@ interface IProps { class ErrorComponent extends Component { render() { - const { - classes, - message, - } = this.props; + const { classes, message } = this.props; - return ( -

- {message} -

- ); + return

{message}

; } } diff --git a/packages/forms/src/input/index.tsx b/packages/forms/src/input/index.tsx index b96dbc12d..41751710a 100644 --- a/packages/forms/src/input/index.tsx +++ b/packages/forms/src/input/index.tsx @@ -17,7 +17,10 @@ interface IData { [index: string]: string; } -interface IProps extends React.InputHTMLAttributes, IFormField, IWithStyle { +interface IProps + extends React.InputHTMLAttributes, + IFormField, + IWithStyle { focus?: boolean; prefix?: string; suffix?: string; @@ -62,23 +65,24 @@ class InputComponent extends Component { } if (data) { - Object.keys(data).map(key => this.inputRef.current!.dataset[key] = data[key]); + Object.keys(data).map( + key => (this.inputRef.current!.dataset[key] = data[key]), + ); } } } onChange(e: React.ChangeEvent) { - const { - scorePassword, - onChange, - } = this.props; + const { scorePassword, onChange } = this.props; if (onChange) { onChange(e); } if (this.inputRef && this.inputRef.current && scorePassword) { - this.setState({ passwordScore: scorePasswordFunc(this.inputRef.current.value) }); + this.setState({ + passwordScore: scorePasswordFunc(this.inputRef.current.value), + }); } } @@ -117,10 +121,7 @@ class InputComponent extends Component { noMargin, } = this.props; - const { - showPassword, - passwordScore, - } = this.state; + const { showPassword, passwordScore } = this.state; const inputType = type === 'password' && showPassword ? 'text' : type; @@ -144,12 +145,9 @@ class InputComponent extends Component { [`${classes.wrapper}`]: true, [`${classes.disabled}`]: disabled, [`${classes.hasError}`]: error, - })}> - {prefix && ( - - {prefix} - - )} + })} + > + {prefix && {prefix}} { max={max} step={step} /> - {suffix && ( - - {suffix} - - )} + {suffix && {suffix}} {showPasswordToggle && ( )}
{scorePassword && ( -
+
{
)} - {error && ( - - )} + {error && } ); } diff --git a/packages/forms/src/input/scorePassword.ts b/packages/forms/src/input/scorePassword.ts index 0b7719ec1..bc30de4b8 100644 --- a/packages/forms/src/input/scorePassword.ts +++ b/packages/forms/src/input/scorePassword.ts @@ -11,7 +11,7 @@ interface IVariations { } export function scorePasswordFunc(password: string): number { - let score: number = 0; + let score = 0; if (!password) { return score; } @@ -32,8 +32,8 @@ export function scorePasswordFunc(password: string): number { }; let variationCount = 0; - Object.keys(variations).forEach((key) => { - variationCount += (variations[key] === true) ? 1 : 0; + Object.keys(variations).forEach(key => { + variationCount += variations[key] === true ? 1 : 0; }); score += (variationCount - 1) * 10; diff --git a/packages/forms/src/label/index.tsx b/packages/forms/src/label/index.tsx index 1b33ba22c..ad503b785 100644 --- a/packages/forms/src/label/index.tsx +++ b/packages/forms/src/label/index.tsx @@ -7,7 +7,9 @@ import { IFormField } from '../typings/generic'; import styles from './styles'; -interface ILabel extends IFormField, React.LabelHTMLAttributes { +interface ILabel + extends IFormField, + React.LabelHTMLAttributes { classes: Classes; isRequired: boolean; } @@ -38,11 +40,12 @@ class LabelComponent extends Component { htmlFor={htmlFor} > {showLabel && ( - {title}{isRequired && ' *'} + + {title} + {isRequired && ' *'} + )} -
- {children} -
+
{children}
); } diff --git a/packages/forms/src/select/index.tsx b/packages/forms/src/select/index.tsx index e5b59cb19..4a5775579 100644 --- a/packages/forms/src/select/index.tsx +++ b/packages/forms/src/select/index.tsx @@ -1,4 +1,8 @@ -import { mdiArrowRightDropCircleOutline, mdiCloseCircle, mdiMagnify } from '@mdi/js'; +import { + mdiArrowRightDropCircleOutline, + mdiCloseCircle, + mdiMagnify, +} from '@mdi/js'; import Icon from '@mdi/react'; import classnames from 'classnames'; import React, { Component, createRef } from 'react'; @@ -58,7 +62,7 @@ const styles = (theme: Theme) => ({ label: { '& > div': { marginTop: 5, - } + }, }, popup: { opacity: 0, @@ -153,9 +157,13 @@ class SelectComponent extends Component { }; private componentRef = createRef(); + private inputRef = createRef(); + private searchInputRef = createRef(); + private scrollContainerRef = createRef(); + private activeOptionRef = createRef(); private keyListener: any; @@ -168,7 +176,7 @@ class SelectComponent extends Component { } } - componentDidUpdate(prevProps: IProps, prevState: IState) { + componentDidUpdate() { const { open } = this.state; if (this.searchInputRef && this.searchInputRef.current) { @@ -183,7 +191,9 @@ class SelectComponent extends Component { const { data } = this.props; if (data) { - Object.keys(data).map(key => this.inputRef.current!.dataset[key] = data[key]); + Object.keys(data).map( + key => (this.inputRef.current!.dataset[key] = data[key]), + ); } } @@ -194,7 +204,10 @@ class SelectComponent extends Component { const { value } = this.props; if (this.componentRef && this.componentRef.current) { - this.componentRef.current.removeEventListener('keydown', this.keyListener); + this.componentRef.current.removeEventListener( + 'keydown', + this.keyListener, + ); } if (value) { @@ -210,13 +223,18 @@ class SelectComponent extends Component { window.removeEventListener('keydown', this.arrowKeysHandler.bind(this)); } - setFilter(needle: string = '') { + setFilter(needle = '') { const { options } = this.props; let filteredOptions = {}; if (needle) { - Object.keys(options).map((key) => { - if (key.toLocaleLowerCase().startsWith(needle.toLocaleLowerCase()) || options[key].toLocaleLowerCase().startsWith(needle.toLocaleLowerCase())) { + Object.keys(options).map(key => { + if ( + key.toLocaleLowerCase().startsWith(needle.toLocaleLowerCase()) || + options[key] + .toLocaleLowerCase() + .startsWith(needle.toLocaleLowerCase()) + ) { Object.assign(filteredOptions, { [`${key}`]: options[key], }); @@ -234,7 +252,7 @@ class SelectComponent extends Component { } select(key: string) { - this.setState((state: IState) => ({ + this.setState(() => ({ value: key, open: false, })); @@ -247,11 +265,7 @@ class SelectComponent extends Component { } arrowKeysHandler(e: KeyboardEvent) { - const { - selected, - open, - options, - } = this.state; + const { selected, open, options } = this.state; if (!open) return; @@ -264,7 +278,10 @@ class SelectComponent extends Component { this.setState((state: IState) => ({ selected: state.selected - 1, })); - } else if (e.keyCode === 40 && selected < Object.keys(options!).length - 1) { + } else if ( + e.keyCode === 40 && + selected < Object.keys(options!).length - 1 + ) { this.setState((state: IState) => ({ selected: state.selected + 1, })); @@ -272,7 +289,12 @@ class SelectComponent extends Component { this.select(Object.keys(options!)[selected]); } - if (this.activeOptionRef && this.activeOptionRef.current && this.scrollContainerRef && this.scrollContainerRef.current) { + if ( + this.activeOptionRef && + this.activeOptionRef.current && + this.scrollContainerRef && + this.scrollContainerRef.current + ) { const containerTopOffset = this.scrollContainerRef.current.offsetTop; const optionTopOffset = this.activeOptionRef.current.offsetTop; @@ -282,10 +304,15 @@ class SelectComponent extends Component { } } - switch (e.keyCode){ - case 37: case 39: case 38: case 40: // Arrow keys - case 32: break; // Space - default: break; // do not block other keys + switch (e.keyCode) { + case 37: + case 39: + case 38: + case 40: // Arrow keys + case 32: + break; // Space + default: + break; // do not block other keys } } @@ -307,13 +334,7 @@ class SelectComponent extends Component { required, } = this.props; - const { - open, - needle, - value, - selected, - options, - } = this.state; + const { open, needle, value, selected, options } = this.state; let selection = ''; if (!value && defaultValue && options![defaultValue]) { @@ -325,10 +346,7 @@ class SelectComponent extends Component { } return ( - +
{ ref={this.inputRef} /> - {error && ( - - )} + {error && } ); } diff --git a/packages/forms/src/textarea/index.tsx b/packages/forms/src/textarea/index.tsx index 31c572d1c..2d89d1c9f 100644 --- a/packages/forms/src/textarea/index.tsx +++ b/packages/forms/src/textarea/index.tsx @@ -14,7 +14,10 @@ interface IData { [index: string]: string; } -interface IProps extends React.TextareaHTMLAttributes, IFormField, IWithStyle { +interface IProps + extends React.TextareaHTMLAttributes, + IFormField, + IWithStyle { focus?: boolean; data: IData; textareaClassName?: string; @@ -37,14 +40,14 @@ class TextareaComponent extends Component { const { data } = this.props; if (this.textareaRef && this.textareaRef.current && data) { - Object.keys(data).map(key => this.textareaRef.current!.dataset[key] = data[key]); + Object.keys(data).map( + key => (this.textareaRef.current!.dataset[key] = data[key]), + ); } } onChange(e: React.ChangeEvent) { - const { - onChange, - } = this.props; + const { onChange } = this.props; if (onChange) { onChange(e); @@ -57,7 +60,6 @@ class TextareaComponent extends Component { className, disabled, error, - focus, id, textareaClassName, label, @@ -94,9 +96,9 @@ class TextareaComponent extends Component { [`${classes.wrapper}`]: true, [`${classes.disabled}`]: disabled, [`${classes.hasError}`]: error, - })}> + })} + >
- {error && ( - - )} + {error && } ); } diff --git a/packages/forms/src/toggle/index.tsx b/packages/forms/src/toggle/index.tsx index b146236df..a9970c8f1 100644 --- a/packages/forms/src/toggle/index.tsx +++ b/packages/forms/src/toggle/index.tsx @@ -3,7 +3,7 @@ import { Property } from 'csstype'; import React, { Component } from 'react'; import injectStyle from 'react-jss'; -import { IFormField, IWithStyle, Omit } from '../typings/generic'; +import { IFormField, IWithStyle } from '../typings/generic'; import { Theme } from '../../../theme'; import { Error } from '../error'; diff --git a/packages/forms/src/wrapper/index.tsx b/packages/forms/src/wrapper/index.tsx index cf179bc5e..3ae551e2c 100644 --- a/packages/forms/src/wrapper/index.tsx +++ b/packages/forms/src/wrapper/index.tsx @@ -12,18 +12,13 @@ interface IProps extends IWithStyle { const styles = { container: { - marginBottom: (props: IProps) => props.noMargin ? 0 : 20, + marginBottom: (props: IProps) => (props.noMargin ? 0 : 20), }, }; class WrapperComponent extends Component { render() { - const { - children, - classes, - className, - identifier, - } = this.props; + const { children, classes, className, identifier } = this.props; return (
{ const inputColor = legacyStyles.darkThemeGrayLightest; const inputBackground = legacyStyles.themeGrayDark; const inputBorder = `1px solid ${legacyStyles.darkThemeGrayLight}`; - const inputPrefixColor = color(legacyStyles.darkThemeGrayLighter).lighten(0.3).hex(); + const inputPrefixColor = color(legacyStyles.darkThemeGrayLighter) + .lighten(0.3) + .hex(); const buttonSecondaryTextColor = legacyStyles.darkThemeTextColor; const selectColor = inputColor; const drawerBg = color(colorBackground).lighten(0.3).hex(); @@ -47,7 +49,10 @@ export default (brandPrimary: string) => { // Loader colorFullscreenLoaderSpinner: '#FFF', - colorWebviewLoaderBackground: color(legacyStyles.darkThemeGrayDarkest).alpha(0.5).rgb().string(), + colorWebviewLoaderBackground: color(legacyStyles.darkThemeGrayDarkest) + .alpha(0.5) + .rgb() + .string(), // Input labelColor: legacyStyles.darkThemeTextColor, @@ -58,8 +63,12 @@ export default (brandPrimary: string) => { inputPrefixBackground: legacyStyles.darkThemeGray, inputDisabledOpacity: 0.5, inputScorePasswordBackground: legacyStyles.darkThemeGrayDark, - inputModifierColor: color(legacyStyles.darkThemeGrayLighter).lighten(0.3).hex(), - inputPlaceholderColor: color(legacyStyles.darkThemeGrayLighter).darken(0.1).hex(), + inputModifierColor: color(legacyStyles.darkThemeGrayLighter) + .lighten(0.3) + .hex(), + inputPlaceholderColor: color(legacyStyles.darkThemeGrayLighter) + .darken(0.1) + .hex(), // Toggle toggleBackground: legacyStyles.darkThemeGray, @@ -91,13 +100,20 @@ export default (brandPrimary: string) => { selectToggleColor: inputPrefixColor, selectPopupBackground: legacyStyles.darkThemeGrayLight, selectOptionColor: '#FFF', - selectOptionBorder: `1px solid ${color(legacyStyles.darkThemeGrayLight).darken(0.2).hex()}`, - selectOptionItemHover: color(legacyStyles.darkThemeGrayLight).darken(0.2).hex(), + selectOptionBorder: `1px solid ${color(legacyStyles.darkThemeGrayLight) + .darken(0.2) + .hex()}`, + selectOptionItemHover: color(legacyStyles.darkThemeGrayLight) + .darken(0.2) + .hex(), selectOptionItemHoverColor: selectColor, selectSearchColor: inputBackground, // Modal - colorModalOverlayBackground: color(legacyStyles.darkThemeBlack).alpha(0.9).rgb().string(), + colorModalOverlayBackground: color(legacyStyles.darkThemeBlack) + .alpha(0.9) + .rgb() + .string(), colorModalBackground: legacyStyles.darkThemeGrayDark, // Services diff --git a/packages/theme/src/themes/default/index.ts b/packages/theme/src/themes/default/index.ts index b8f3e3201..21017bbe0 100644 --- a/packages/theme/src/themes/default/index.ts +++ b/packages/theme/src/themes/default/index.ts @@ -82,7 +82,10 @@ export default (brandPrimary: string) => { colorText, - defaultContentBorder: color(legacyStyles.themeGrayLighter).darken(0.1).rgb().string(), + defaultContentBorder: color(legacyStyles.themeGrayLighter) + .darken(0.1) + .rgb() + .string(), // Subscription Container Component colorSubscriptionContainerBackground: 'none', @@ -94,7 +97,10 @@ export default (brandPrimary: string) => { // Loader colorAppLoaderSpinner: '#FFF', colorFullscreenLoaderSpinner: legacyStyles.themeGrayDark, - colorWebviewLoaderBackground: color(legacyStyles.themeGrayLighter).alpha(0.8).rgb().string(), + colorWebviewLoaderBackground: color(legacyStyles.themeGrayLighter) + .alpha(0.8) + .rgb() + .string(), // Input labelColor: legacyStyles.themeGrayLight, @@ -103,7 +109,9 @@ export default (brandPrimary: string) => { inputBackground, inputBorder, inputModifierColor: legacyStyles.themeGrayLight, - inputPlaceholderColor: color(legacyStyles.themeGrayLight).lighten(0.3).hex(), + inputPlaceholderColor: color(legacyStyles.themeGrayLight) + .lighten(0.3) + .hex(), inputPrefixColor, inputPrefixBackground: legacyStyles.themeGrayLighter, inputDisabledOpacity, diff --git a/packages/theme/tslint.json b/packages/theme/tslint.json deleted file mode 100644 index 0946f2096..000000000 --- a/packages/theme/tslint.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../tslint.json" -} diff --git a/packages/typings/types/react-loader.d.ts b/packages/typings/types/react-loader.d.ts index 8dc36b71f..728e3dfa0 100644 --- a/packages/typings/types/react-loader.d.ts +++ b/packages/typings/types/react-loader.d.ts @@ -36,7 +36,7 @@ interface LoaderProps extends LoaderOptions { className?: string; } -declare class ReactLoader extends Component { +declare class ReactLoader extends Component { } declare namespace ReactLoader { diff --git a/packages/ui/src/badge/ProBadge.tsx b/packages/ui/src/badge/ProBadge.tsx index 73db47068..63d5d673a 100644 --- a/packages/ui/src/badge/ProBadge.tsx +++ b/packages/ui/src/badge/ProBadge.tsx @@ -3,7 +3,7 @@ import classnames from 'classnames'; import React, { Component } from 'react'; import injectStyle from 'react-jss'; -import { Badge, Icon } from '../'; +import { Badge, Icon } from '..'; import { Theme } from '../../../theme'; import { IWithStyle } from '../typings/generic'; @@ -34,13 +34,8 @@ const styles = (theme: Theme) => ({ class ProBadgeComponent extends Component { render() { - const { - classes, - badgeClasses, - iconClasses, - inverted, - className, - } = this.props; + const { classes, badgeClasses, iconClasses, inverted, className } = + this.props; return ( { const styles = {}; - Object.keys(theme.styleTypes).map((style) => { + Object.keys(theme.styleTypes).map(style => { Object.assign(styles, { [style]: { background: theme.styleTypes[style].accent, @@ -51,12 +51,7 @@ class BadgeComponent extends Component { }; render() { - const { - classes, - children, - type, - className, - } = this.props; + const { classes, children, type, className } = this.props; return (
({ class HeadlineComponent extends Component { render() { - const { - classes, - level, - className, - children, - id, - } = this.props; + const { classes, level, className, children, id } = this.props; return React.createElement( `h${level}`, @@ -63,7 +57,12 @@ class HeadlineComponent extends Component { const Headline = injectStyle(styles)(HeadlineComponent); -const createH = (level: number) => (props: Omit) => {props.children}; +const createH = (level: number) => (props: Omit) => + ( + + {props.children} + + ); export const H1 = createH(1); export const H2 = createH(2); diff --git a/packages/ui/src/icon/index.tsx b/packages/ui/src/icon/index.tsx index ed55eccfe..b644a9234 100644 --- a/packages/ui/src/icon/index.tsx +++ b/packages/ui/src/icon/index.tsx @@ -24,12 +24,7 @@ class IconComponent extends Component { }; render() { - const { - classes, - icon, - size, - className, - } = this.props; + const { classes, icon, size, className } = this.props; if (!icon) { console.warn('No Icon specified'); diff --git a/packages/ui/src/infobox/index.tsx b/packages/ui/src/infobox/index.tsx index bd62fc4ea..961262001 100644 --- a/packages/ui/src/infobox/index.tsx +++ b/packages/ui/src/infobox/index.tsx @@ -3,8 +3,8 @@ import classnames from 'classnames'; import React, { Component } from 'react'; import injectStyle from 'react-jss'; +import { Icon } from '..'; import { Theme } from '../../../theme'; -import { Icon } from '../'; import { IWithStyle } from '../typings/generic'; interface IProps extends IWithStyle { @@ -27,7 +27,7 @@ interface IState { const buttonStyles = (theme: Theme) => { const styles = {}; - Object.keys(theme.styleTypes).map((style) => { + Object.keys(theme.styleTypes).map(style => { Object.assign(styles, { [style]: { background: theme.styleTypes[style].accent, @@ -73,18 +73,21 @@ const styles = (theme: Theme) => ({ marginRight: 10, }, close: { - color: (props: IProps) => theme.styleTypes[props.type ? props.type : 'primary'].contrast, + color: (props: IProps) => + theme.styleTypes[props.type ? props.type : 'primary'].contrast, marginRight: -5, border: 0, background: 'none', }, cta: { - borderColor: (props: IProps) => theme.styleTypes[props.type ? props.type : 'primary'].contrast, + borderColor: (props: IProps) => + theme.styleTypes[props.type ? props.type : 'primary'].contrast, borderRadius: theme.borderRadiusSmall, borderStyle: 'solid', borderWidth: 1, background: 'none', - color: (props: IProps) => theme.styleTypes[props.type ? props.type : 'primary'].contrast, + color: (props: IProps) => + theme.styleTypes[props.type ? props.type : 'primary'].contrast, marginLeft: 15, padding: [4, 10], fontSize: theme.uiFontSize, @@ -113,9 +116,7 @@ class InfoboxComponent extends Component { }; dismiss() { - const { - onDismiss, - } = this.props; + const { onDismiss } = this.props; this.setState({ isDismissing: true, @@ -129,7 +130,7 @@ class InfoboxComponent extends Component { this.setState({ dismissed: true, }); - }, 3000); + }, 3000); } componentWillUnmount(): void { @@ -144,26 +145,24 @@ class InfoboxComponent extends Component { icon, type, ctaLabel, - ctaLoading, ctaOnClick, dismissable, className, } = this.props; - const { - isDismissing, - dismissed, - } = this.state; + const { isDismissing, dismissed } = this.state; if (dismissed) { return null; } return ( -
+
{ })} data-type="franz-infobox" > - {icon && ( - - )} -
- {children} -
+ {icon && } +
{children}
{ctaLabel && ( - )} diff --git a/packages/ui/src/loader/index.tsx b/packages/ui/src/loader/index.tsx index e2701a8e9..244aa9dc9 100644 --- a/packages/ui/src/loader/index.tsx +++ b/packages/ui/src/loader/index.tsx @@ -3,7 +3,6 @@ import React, { Component } from 'react'; import injectStyle, { withTheme } from 'react-jss'; import ReactLoader from 'react-loader'; -import { Theme } from '../../../theme'; import { IWithStyle } from '../typings/generic'; interface IProps extends IWithStyle { @@ -11,7 +10,7 @@ interface IProps extends IWithStyle { color?: string; } -const styles = (theme: Theme) => ({ +const styles = () => ({ container: { position: 'relative', height: 60, @@ -20,12 +19,7 @@ const styles = (theme: Theme) => ({ class LoaderComponent extends Component { render() { - const { - classes, - className, - color, - theme, - } = this.props; + const { classes, className, color, theme } = this.props; return (
{ }; actions[actionName] = action; action.listeners = []; - action.listen = listener => action.listeners.push(listener); + action.listen = (listener) => action.listeners.push(listener); action.off = (listener) => { const { listeners } = action; listeners.splice(listeners.indexOf(listener), 1); }; - action.notify = params => action.listeners.forEach(listener => listener(params)); + action.notify = (params) => action.listeners.forEach((listener) => listener(params)); }); return actions; }; diff --git a/src/api/server/LocalApi.js b/src/api/server/LocalApi.js index 4b1f03f22..2d5bd8b80 100644 --- a/src/api/server/LocalApi.js +++ b/src/api/server/LocalApi.js @@ -2,7 +2,7 @@ import { ipcRenderer } from 'electron'; import { session } from '@electron/remote'; import du from 'du'; -import { getServicePartitionsDirectory } from '../../helpers/service-helpers.js'; +import { getServicePartitionsDirectory } from '../../helpers/service-helpers'; const debug = require('debug')('Ferdi:LocalApi'); @@ -41,11 +41,23 @@ export default class LocalApi { } async clearCache(serviceId = null) { - const s = serviceId ? session.fromPartition(`persist:service-${serviceId}`) : session.defaultSession; + const s = serviceId + ? session.fromPartition(`persist:service-${serviceId}`) + : session.defaultSession; - debug('LocalApi::clearCache resolves', (serviceId || 'clearAppCache')); + debug('LocalApi::clearCache resolves', serviceId || 'clearAppCache'); await s.clearStorageData({ - storages: ['appcache', 'cookies', 'filesystem', 'indexdb', 'localstorage', 'shadercache', 'websql', 'serviceworkers', 'cachestorage'], + storages: [ + 'appcache', + 'cookies', + 'filesystem', + 'indexdb', + 'localstorage', + 'shadercache', + 'websql', + 'serviceworkers', + 'cachestorage', + ], quotas: ['temporary', 'persistent', 'syncable'], }); return s.clearCache(); diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js index 78a98e544..bc1d665b1 100644 --- a/src/api/server/ServerApi.js +++ b/src/api/server/ServerApi.js @@ -23,16 +23,11 @@ import { loadRecipeConfig, } from '../../helpers/recipe-helpers'; -import { - removeServicePartitionDirectory, -} from '../../helpers/service-helpers.js'; +import { removeServicePartitionDirectory } from '../../helpers/service-helpers'; const debug = require('debug')('Ferdi:ServerApi'); -module.paths.unshift( - getDevRecipeDirectory(), - getRecipeDirectory(), -); +module.paths.unshift(getDevRecipeDirectory(), getRecipeDirectory()); const { default: fetch } = remoteRequire('electron-fetch'); @@ -43,12 +38,16 @@ export default class ServerApi { // User async login(email, passwordHash) { - const request = await sendAuthRequest(`${apiBase()}/auth/login`, { - method: 'POST', - headers: { - Authorization: `Basic ${window.btoa(`${email}:${passwordHash}`)}`, + const request = await sendAuthRequest( + `${apiBase()}/auth/login`, + { + method: 'POST', + headers: { + Authorization: `Basic ${window.btoa(`${email}:${passwordHash}`)}`, + }, }, - }, false); + false, + ); if (!request.ok) { throw request; } @@ -59,10 +58,14 @@ export default class ServerApi { } async signup(data) { - const request = await sendAuthRequest(`${apiBase()}/auth/signup`, { - method: 'POST', - body: JSON.stringify(data), - }, false); + const request = await sendAuthRequest( + `${apiBase()}/auth/signup`, + { + method: 'POST', + body: JSON.stringify(data), + }, + false, + ); if (!request.ok) { throw request; } @@ -86,12 +89,16 @@ export default class ServerApi { } async retrievePassword(email) { - const request = await sendAuthRequest(`${apiBase()}/auth/password`, { - method: 'POST', - body: JSON.stringify({ - email, - }), - }, false); + const request = await sendAuthRequest( + `${apiBase()}/auth/password`, + { + method: 'POST', + body: JSON.stringify({ + email, + }), + }, + false, + ); if (!request.ok) { throw request; } @@ -128,7 +135,9 @@ export default class ServerApi { } const updatedData = await request.json(); - const user = Object.assign(updatedData, { data: new UserModel(updatedData.data) }); + const user = Object.assign(updatedData, { + data: new UserModel(updatedData.data), + }); debug('ServerApi::updateUserInfo resolves', user); return user; } @@ -159,7 +168,7 @@ export default class ServerApi { const data = await request.json(); let services = await this._mapServiceModels(data); - services = services.filter(service => service !== null); + services = services.filter((service) => service !== null); debug('ServerApi::getServices resolves', services); return services; } @@ -175,12 +184,17 @@ export default class ServerApi { const serviceData = await request.json(); if (data.iconFile) { - const iconData = await this.uploadServiceIcon(serviceData.data.id, data.iconFile); + const iconData = await this.uploadServiceIcon( + serviceData.data.id, + data.iconFile, + ); serviceData.data = iconData; } - const service = Object.assign(serviceData, { data: await this._prepareServiceModel(serviceData.data) }); + const service = Object.assign(serviceData, { + data: await this._prepareServiceModel(serviceData.data), + }); debug('ServerApi::createService resolves', service); return service; @@ -204,7 +218,9 @@ export default class ServerApi { const serviceData = await request.json(); - const service = Object.assign(serviceData, { data: await this._prepareServiceModel(serviceData.data) }); + const service = Object.assign(serviceData, { + data: await this._prepareServiceModel(serviceData.data), + }); debug('ServerApi::updateService resolves', service); return service; @@ -221,7 +237,10 @@ export default class ServerApi { delete requestData.headers['Content-Type']; - const request = await window.fetch(`${apiBase()}/service/${serviceId}`, requestData); + const request = await window.fetch( + `${apiBase()}/service/${serviceId}`, + requestData, + ); if (!request.ok) { throw request; @@ -292,18 +311,21 @@ export default class ServerApi { // Recipes async getInstalledRecipes() { const recipesDirectory = getRecipeDirectory(); - const paths = fs.readdirSync(recipesDirectory) - .filter(file => ( - fs.statSync(path.join(recipesDirectory, file)).isDirectory() - && file !== 'temp' - && file !== 'dev' - )); - - this.recipes = paths.map((id) => { - // eslint-disable-next-line - const Recipe = require(id)(RecipeModel); - return new Recipe(loadRecipeConfig(id)); - }).filter(recipe => recipe.id); + const paths = fs + .readdirSync(recipesDirectory) + .filter( + (file) => fs.statSync(path.join(recipesDirectory, file)).isDirectory() + && file !== 'temp' + && file !== 'dev', + ); + + this.recipes = paths + .map((id) => { + // eslint-disable-next-line + const Recipe = require(id)(RecipeModel); + return new Recipe(loadRecipeConfig(id)); + }) + .filter((recipe) => recipe.id); this.recipes = this.recipes.concat(this._getDevRecipes()); @@ -373,7 +395,9 @@ export default class ServerApi { console.log('[ServerApi::getRecipePackage] Using internal recipe file'); archivePath = internalRecipeFile; } else { - console.log('[ServerApi::getRecipePackage] Downloading recipe from server'); + console.log( + '[ServerApi::getRecipePackage] Downloading recipe from server', + ); archivePath = tempArchivePath; const packageUrl = `${apiBase()}/recipes/download/${recipeId}`; @@ -393,12 +417,14 @@ export default class ServerApi { preservePaths: true, unlink: true, preserveOwner: false, - onwarn: x => console.log('warn', recipeId, x), + onwarn: (x) => console.log('warn', recipeId, x), }); await sleep(10); - const { id } = fs.readJsonSync(path.join(recipeTempDirectory, 'package.json')); + const { id } = fs.readJsonSync( + path.join(recipeTempDirectory, 'package.json'), + ); const recipeDirectory = path.join(recipesDirectory, id); fs.copySync(recipeTempDirectory, recipeDirectory); fs.remove(recipeTempDirectory); @@ -414,7 +440,9 @@ export default class ServerApi { // News async getLatestNews() { - const url = `${apiBase(true)}/news?platform=${osPlatform}&arch=${osArch}&version=${app.getVersion()}`; + const url = `${apiBase( + true, + )}/news?platform=${osPlatform}&arch=${osArch}&version=${app.getVersion()}`; const request = await sendAuthRequest(url); if (!request.ok) throw request; const data = await request.json(); @@ -435,9 +463,13 @@ export default class ServerApi { throw new Error('Server not loaded'); } - const request = await sendAuthRequest(`${apiBase(false)}/health`, { - method: 'GET', - }, false); + const request = await sendAuthRequest( + `${apiBase(false)}/health`, + { + method: 'GET', + }, + false, + ); if (!request.ok) { throw request; } @@ -445,23 +477,31 @@ export default class ServerApi { } async getLegacyServices() { - const file = path.join(app.getPath('userData'), 'settings', 'services.json'); + const file = path.join( + app.getPath('userData'), + 'settings', + 'services.json', + ); try { const config = fs.readJsonSync(file); if (Object.prototype.hasOwnProperty.call(config, 'services')) { - const services = await Promise.all(config.services.map(async (s) => { - const service = s; - const request = await sendAuthRequest(`${apiBase()}/recipes/${s.service}`); - - if (request.status === 200) { - const data = await request.json(); - service.recipe = new RecipePreviewModel(data); - } - - return service; - })); + const services = await Promise.all( + config.services.map(async (s) => { + const service = s; + const request = await sendAuthRequest( + `${apiBase()}/recipes/${s.service}`, + ); + + if (request.status === 200) { + const data = await request.json(); + service.recipe = new RecipePreviewModel(data); + } + + return service; + }), + ); debug('ServerApi::getLegacyServices resolves', services); return services; @@ -475,17 +515,19 @@ export default class ServerApi { // Helper async _mapServiceModels(services) { - const recipes = services.map(s => s.recipeId); + const recipes = services.map((s) => s.recipeId); await this._bulkRecipeCheck(recipes); /* eslint-disable no-return-await */ - return Promise.all(services.map(async service => await this._prepareServiceModel(service))); + return Promise.all( + services.map(async (service) => await this._prepareServiceModel(service)), + ); /* eslint-enable no-return-await */ } async _prepareServiceModel(service) { let recipe; try { - recipe = this.recipes.find(r => r.id === service.recipeId); + recipe = this.recipes.find((r) => r.id === service.recipeId); if (!recipe) { console.warn(`Recipe ${service.recipeId} not loaded`); @@ -501,21 +543,25 @@ export default class ServerApi { async _bulkRecipeCheck(unfilteredRecipes) { // Filter recipe duplicates as we don't need to download 3 Slack recipes - const recipes = unfilteredRecipes.filter((elem, pos, arr) => arr.indexOf(elem) === pos); + const recipes = unfilteredRecipes.filter( + (elem, pos, arr) => arr.indexOf(elem) === pos, + ); - return Promise.all(recipes - .map(async (recipeId) => { - let recipe = this.recipes.find(r => r.id === recipeId); + return Promise.all( + recipes.map(async (recipeId) => { + let recipe = this.recipes.find((r) => r.id === recipeId); if (!recipe) { - console.warn(`Recipe '${recipeId}' not installed, trying to fetch from server`); + console.warn( + `Recipe '${recipeId}' not installed, trying to fetch from server`, + ); await this.getRecipePackage(recipeId); debug('Rerun ServerAPI::getInstalledRecipes'); await this.getInstalledRecipes(); - recipe = this.recipes.find(r => r.id === recipeId); + recipe = this.recipes.find((r) => r.id === recipeId); if (!recipe) { console.warn(`Could not load recipe ${recipeId}`); @@ -524,69 +570,83 @@ export default class ServerApi { } return recipe; - })).catch(err => console.error('Can\'t load recipe', err)); + }), + ).catch((err) => console.error("Can't load recipe", err)); } _mapRecipePreviewModel(recipes) { - return recipes.map((recipe) => { - try { - return new RecipePreviewModel(recipe); - } catch (e) { - console.error(e); - return null; - } - }).filter(recipe => recipe !== null); + return recipes + .map((recipe) => { + try { + return new RecipePreviewModel(recipe); + } catch (e) { + console.error(e); + return null; + } + }) + .filter((recipe) => recipe !== null); } _mapNewsModels(news) { - return news.map((newsItem) => { - try { - return new NewsModel(newsItem); - } catch (e) { - console.error(e); - return null; - } - }).filter(newsItem => newsItem !== null); + return news + .map((newsItem) => { + try { + return new NewsModel(newsItem); + } catch (e) { + console.error(e); + return null; + } + }) + .filter((newsItem) => newsItem !== null); } _mapOrderModels(orders) { - return orders.map((orderItem) => { - try { - return new OrderModel(orderItem); - } catch (e) { - console.error(e); - return null; - } - }).filter(orderItem => orderItem !== null); + return orders + .map((orderItem) => { + try { + return new OrderModel(orderItem); + } catch (e) { + console.error(e); + return null; + } + }) + .filter((orderItem) => orderItem !== null); } _getDevRecipes() { const recipesDirectory = getDevRecipeDirectory(); try { - const paths = fs.readdirSync(recipesDirectory) - .filter(file => fs.statSync(path.join(recipesDirectory, file)).isDirectory() && file !== 'temp'); - - const recipes = paths.map((id) => { - let Recipe; - try { - // eslint-disable-next-line - Recipe = require(id)(RecipeModel); - return new Recipe(loadRecipeConfig(id)); - } catch (err) { - console.error(err); - } + const paths = fs + .readdirSync(recipesDirectory) + .filter( + (file) => fs.statSync(path.join(recipesDirectory, file)).isDirectory() + && file !== 'temp', + ); + + const recipes = paths + .map((id) => { + let Recipe; + try { + // eslint-disable-next-line + Recipe = require(id)(RecipeModel); + return new Recipe(loadRecipeConfig(id)); + } catch (err) { + console.error(err); + } - return false; - }).filter(recipe => recipe.id).map((data) => { - const recipe = data; + return false; + }) + .filter((recipe) => recipe.id) + .map((data) => { + const recipe = data; - recipe.icons = { - svg: `${recipe.path}/icon.svg`, - }; - recipe.local = true; + recipe.icons = { + svg: `${recipe.path}/icon.svg`, + }; + recipe.local = true; - return data; - }); + return data; + }); return recipes; } catch (err) { diff --git a/src/app.js b/src/app.js index aab1729d7..a2d60dbe4 100644 --- a/src/app.js +++ b/src/app.js @@ -59,7 +59,7 @@ window.addEventListener('load', () => { }); // Prevent drag and drop into window from redirecting -window.addEventListener('dragover', event => event.preventDefault()); -window.addEventListener('drop', event => event.preventDefault()); -window.addEventListener('dragover', event => event.stopPropagation()); -window.addEventListener('drop', event => event.stopPropagation()); +window.addEventListener('dragover', (event) => event.preventDefault()); +window.addEventListener('drop', (event) => event.preventDefault()); +window.addEventListener('dragover', (event) => event.stopPropagation()); +window.addEventListener('drop', (event) => event.stopPropagation()); diff --git a/src/components/auth/AuthLayout.js b/src/components/auth/AuthLayout.js index 6fa3adf92..c0e6b982b 100644 --- a/src/components/auth/AuthLayout.js +++ b/src/components/auth/AuthLayout.js @@ -7,14 +7,19 @@ import { TitleBar } from 'electron-react-titlebar'; import Link from '../ui/Link'; import InfoBar from '../ui/InfoBar'; -import { oneOrManyChildElements, globalError as globalErrorPropType } from '../../prop-types'; +import { + oneOrManyChildElements, + globalError as globalErrorPropType, +} from '../../prop-types'; import globalMessages from '../../i18n/globalMessages'; import { isWindows } from '../../environment'; import AppUpdateInfoBar from '../AppUpdateInfoBar'; import { GITHUB_FERDI_URL } from '../../config'; -export default @observer class AuthLayout extends Component { +export default +@observer +class AuthLayout extends Component { static propTypes = { children: oneOrManyChildElements.isRequired, error: globalErrorPropType.isRequired, @@ -30,7 +35,7 @@ export default @observer class AuthLayout extends Component { state = { shouldShowAppUpdateInfoBar: true, - } + }; static defaultProps = { nextAppReleaseVersion: null, @@ -57,12 +62,15 @@ export default @observer class AuthLayout extends Component { return ( <> - {isWindows && !isFullScreen && } + {isWindows && !isFullScreen && ( + + )}
{!isOnline && ( - + {intl.formatMessage(globalMessages.notConnectedToTheInternet)} @@ -95,7 +103,11 @@ export default @observer class AuthLayout extends Component { })}
{/*
*/} - +
diff --git a/src/components/auth/ChangeServer.js b/src/components/auth/ChangeServer.js index 0dedd825a..7bab80280 100644 --- a/src/components/auth/ChangeServer.js +++ b/src/components/auth/ChangeServer.js @@ -100,7 +100,7 @@ export default @observer class ChangeServer extends Component { const { intl } = this.context; return (
-
this.submit(e)}> + this.submit(e)}>

{intl.formatMessage(messages.headline)}

{form.$('server').value === this.franzServer && ( @@ -113,7 +113,7 @@ export default @observer class ChangeServer extends Component { && ( this.submit(e)} + onChange={(e) => this.submit(e)} field={form.$('customServer')} /> )} diff --git a/src/components/auth/Locked.js b/src/components/auth/Locked.js index 1fdbea595..2ad8a2409 100644 --- a/src/components/auth/Locked.js +++ b/src/components/auth/Locked.js @@ -102,7 +102,7 @@ export default @observer class Locked extends Component { return (
- this.submit(e)}> + this.submit(e)}> - this.submit(e)}> + this.submit(e)}> - this.submit(e)}> + this.submit(e)}> ({ +const styles = (theme) => ({ root: { width: '500px !important', textAlign: 'center', @@ -161,7 +161,7 @@ class SetupAssistant extends Component { const sanitizedWorkspace = slackWorkspace.trim().replace(/^https?:\/\//, ''); if (sanitizedWorkspace) { - const index = services.findIndex(s => s.id === SLACK_ID); + const index = services.findIndex((s) => s.id === SLACK_ID); if (index === -1) { const newServices = services; @@ -215,11 +215,11 @@ class SetupAssistant extends Component {