From 354ffea92be3a1030c730b6bb8ece7f680e01b12 Mon Sep 17 00:00:00 2001 From: Balaji Vijayakumar Date: Wed, 26 Oct 2022 09:47:11 +0530 Subject: refactor: convert TabBar to typescript --- src/@types/stores.types.ts | 1 + src/components/layout/Sidebar.tsx | 68 ++-- src/components/services/tabs/TabBarSortableList.js | 77 ---- .../services/tabs/TabBarSortableList.tsx | 79 ++++ src/components/services/tabs/TabItem.js | 419 --------------------- src/components/services/tabs/TabItem.tsx | 411 ++++++++++++++++++++ src/components/services/tabs/Tabbar.js | 123 ------ src/components/services/tabs/Tabbar.tsx | 128 +++++++ 8 files changed, 664 insertions(+), 642 deletions(-) delete mode 100644 src/components/services/tabs/TabBarSortableList.js create mode 100644 src/components/services/tabs/TabBarSortableList.tsx delete mode 100644 src/components/services/tabs/TabItem.js create mode 100644 src/components/services/tabs/TabItem.tsx delete mode 100644 src/components/services/tabs/Tabbar.js create mode 100644 src/components/services/tabs/Tabbar.tsx (limited to 'src') diff --git a/src/@types/stores.types.ts b/src/@types/stores.types.ts index 30898a8a7..bf2dc8bd2 100644 --- a/src/@types/stores.types.ts +++ b/src/@types/stores.types.ts @@ -125,6 +125,7 @@ interface AppStore extends TypedStore { universalDarkMode: boolean; cacheSize: () => void; debugInfo: () => void; + enableLongPressServiceHint: boolean; } interface CommunityRecipesStore extends TypedStore { diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index a81229dca..c37447357 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -3,28 +3,28 @@ import ReactTooltip from 'react-tooltip'; import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; import { inject, observer } from 'mobx-react'; import { + mdiBell, + mdiBellOff, mdiCheckAll, - mdiViewGrid, - mdiPlusBox, + mdiChevronDown, + mdiChevronRight, mdiCog, - mdiBellOff, - mdiBell, mdiLock, mdiMenu, - mdiChevronDown, - mdiChevronRight, + mdiPlusBox, + mdiViewGrid, mdiViewSplitVertical, } from '@mdi/js'; import Tabbar from '../services/tabs/Tabbar'; import { - settingsShortcutKey, + addNewServiceShortcutKey, lockFerdiumShortcutKey, + muteFerdiumShortcutKey, + settingsShortcutKey, + splitModeToggleShortcutKey, todosToggleShortcutKey, workspaceToggleShortcutKey, - addNewServiceShortcutKey, - splitModeToggleShortcutKey, - muteFerdiumShortcutKey, } from '../../environment'; import { todosStore } from '../../features/todos'; import { todoActions } from '../../features/todos/actions'; @@ -86,21 +86,25 @@ interface IProps extends WrappedComponentProps { actions?: Actions; stores?: RealStores; - deleteService: () => void; - updateService: () => void; - hibernateService: () => void; - wakeUpService: () => void; toggleMuteApp: () => void; toggleCollapseMenu: () => void; toggleWorkspaceDrawer: () => void; - openSettings: (arg: { path: string }) => void; + openSettings: (args: { path: string }) => void; closeSettings: () => void; - setActive: () => void; - reorder: () => void; - reload: () => void; - toggleNotifications: () => void; - toggleAudio: () => void; - toggleDarkMode: () => void; + setActive: (args: { serviceId: string }) => void; + reorder: (args: { oldIndex: number; newIndex: number }) => void; + reload: (args: { serviceId: string }) => void; + toggleNotifications: (args: { serviceId: string }) => void; + toggleAudio: (args: { serviceId: string }) => void; + toggleDarkMode: (args: { serviceId: string }) => void; + deleteService: (args: { serviceId: string }) => void; + hibernateService: (args: { serviceId: string }) => void; + wakeUpService: (args: { serviceId: string }) => void; + updateService: (args: { + serviceId: string; + serviceData: { isEnabled: boolean; isMediaPlaying: boolean }; + redirect: boolean; + }) => void; } interface IState { @@ -180,10 +184,28 @@ class Sidebar extends Component { return (
this.enableToolTip()} disableToolTip={() => this.disableToolTip()} - useHorizontalStyle={stores!.settings.all.app.useHorizontalStyle} + reorder={this.props.reorder} + reload={this.props.reload} + toggleNotifications={this.props.toggleNotifications} + toggleAudio={this.props.toggleAudio} + toggleDarkMode={this.props.toggleDarkMode} + deleteService={this.props.deleteService} + updateService={this.props.updateService} + hibernateService={this.props.hibernateService} + wakeUpService={this.props.wakeUpService} /> <> {numberActiveButtons <= 1 || hideCollapseButton ? null : ( diff --git a/src/components/services/tabs/TabBarSortableList.js b/src/components/services/tabs/TabBarSortableList.js deleted file mode 100644 index e01461e5c..000000000 --- a/src/components/services/tabs/TabBarSortableList.js +++ /dev/null @@ -1,77 +0,0 @@ -import { Component } from 'react'; -import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; -import PropTypes from 'prop-types'; -import { SortableContainer } from 'react-sortable-hoc'; - -import TabItem from './TabItem'; - -class TabBarSortableList extends Component { - static propTypes = { - services: MobxPropTypes.arrayOrObservableArray.isRequired, - setActive: PropTypes.func.isRequired, - openSettings: PropTypes.func.isRequired, - reload: PropTypes.func.isRequired, - toggleNotifications: PropTypes.func.isRequired, - toggleAudio: PropTypes.func.isRequired, - toggleDarkMode: PropTypes.func.isRequired, - deleteService: PropTypes.func.isRequired, - disableService: PropTypes.func.isRequired, - enableService: PropTypes.func.isRequired, - hibernateService: PropTypes.func.isRequired, - wakeUpService: PropTypes.func.isRequired, - showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, - showServiceNameSetting: PropTypes.bool.isRequired, - showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, - }; - - render() { - const { - services, - setActive, - reload, - toggleNotifications, - toggleAudio, - toggleDarkMode, - deleteService, - disableService, - enableService, - hibernateService, - wakeUpService, - openSettings, - showMessageBadgeWhenMutedSetting, - showServiceNameSetting, - showMessageBadgesEvenWhenMuted, - } = this.props; - - return ( -
    - {services.map((service, index) => ( - setActive({ serviceId: service.id })} - service={service} - index={index} - shortcutIndex={index + 1} - reload={() => reload({ serviceId: service.id })} - toggleNotifications={() => - toggleNotifications({ serviceId: service.id }) - } - toggleAudio={() => toggleAudio({ serviceId: service.id })} - toggleDarkMode={() => toggleDarkMode({ serviceId: service.id })} - 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} - showServiceNameSetting={showServiceNameSetting} - /> - ))} -
- ); - } -} - -export default SortableContainer(observer(TabBarSortableList)); diff --git a/src/components/services/tabs/TabBarSortableList.tsx b/src/components/services/tabs/TabBarSortableList.tsx new file mode 100644 index 000000000..e04f6268d --- /dev/null +++ b/src/components/services/tabs/TabBarSortableList.tsx @@ -0,0 +1,79 @@ +import { Component } from 'react'; +import { observer } from 'mobx-react'; +import { SortableContainer } from 'react-sortable-hoc'; + +import TabItem from './TabItem'; +import Service from '../../../models/Service'; + +interface IProps { + showMessageBadgeWhenMutedSetting: boolean; + showServiceNameSetting: boolean; + showMessageBadgesEvenWhenMuted: boolean; + services: Service[]; + + setActive: (args: { serviceId: string }) => void; + openSettings: (args: { path: string }) => void; + reload: (args: { serviceId: string }) => void; + toggleNotifications: (args: { serviceId: string }) => void; + toggleAudio: (args: { serviceId: string }) => void; + toggleDarkMode: (args: { serviceId: string }) => void; + deleteService: (args: { serviceId: string }) => void; + disableService: (args: { serviceId: string }) => void; + enableService: (args: { serviceId: string }) => void; + hibernateService: (args: { serviceId: string }) => void; + wakeUpService: (args: { serviceId: string }) => void; +} + +@observer +class TabBarSortableList extends Component { + render() { + const { + services, + setActive, + reload, + toggleNotifications, + toggleAudio, + toggleDarkMode, + deleteService, + disableService, + enableService, + hibernateService, + wakeUpService, + openSettings, + showMessageBadgeWhenMutedSetting, + showServiceNameSetting, + showMessageBadgesEvenWhenMuted, + } = this.props; + + return ( +
    + {services.map((service, index) => ( + setActive({ serviceId: service.id })} + service={service} + index={index} + shortcutIndex={index + 1} + reload={() => reload({ serviceId: service.id })} + toggleNotifications={() => + toggleNotifications({ serviceId: service.id }) + } + toggleAudio={() => toggleAudio({ serviceId: service.id })} + toggleDarkMode={() => toggleDarkMode({ serviceId: service.id })} + 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} + showServiceNameSetting={showServiceNameSetting} + /> + ))} +
+ ); + } +} + +export default SortableContainer(TabBarSortableList); diff --git a/src/components/services/tabs/TabItem.js b/src/components/services/tabs/TabItem.js deleted file mode 100644 index 0aab82a6f..000000000 --- a/src/components/services/tabs/TabItem.js +++ /dev/null @@ -1,419 +0,0 @@ -import { Menu, dialog, app } from '@electron/remote'; -import { Component } from 'react'; -import { defineMessages, injectIntl } from 'react-intl'; -import PropTypes from 'prop-types'; -import { inject, observer } from 'mobx-react'; -import classnames from 'classnames'; -import { SortableElement } from 'react-sortable-hoc'; -import injectSheet from 'react-jss'; -import ms from 'ms'; - -import { observable, autorun, reaction, makeObservable } from 'mobx'; -import { mdiExclamation, mdiVolumeSource } from '@mdi/js'; -import ServiceModel from '../../../models/Service'; -import { cmdOrCtrlShortcutKey, shiftKey, altKey } from '../../../environment'; -import globalMessages from '../../../i18n/globalMessages'; -import SettingsStore from '../../../stores/SettingsStore'; -import Icon from '../../ui/icon'; - -const IS_SERVICE_DEBUGGING_ENABLED = ( - localStorage.getItem('debug') || '' -).includes('Ferdium:Service'); - -const messages = defineMessages({ - reload: { - id: 'tabs.item.reload', - defaultMessage: 'Reload', - }, - disableNotifications: { - id: 'tabs.item.disableNotifications', - defaultMessage: 'Disable notifications', - }, - enableNotifications: { - id: 'tabs.item.enableNotification', - defaultMessage: 'Enable notifications', - }, - disableAudio: { - id: 'tabs.item.disableAudio', - defaultMessage: 'Disable audio', - }, - enableAudio: { - id: 'tabs.item.enableAudio', - defaultMessage: 'Enable audio', - }, - enableDarkMode: { - id: 'tabs.item.enableDarkMode', - defaultMessage: 'Enable Dark mode', - }, - disableDarkMode: { - id: 'tabs.item.disableDarkMode', - defaultMessage: 'Disable Dark mode', - }, - disableService: { - id: 'tabs.item.disableService', - defaultMessage: 'Disable service', - }, - enableService: { - 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', - }, - confirmDeleteService: { - id: 'tabs.item.confirmDeleteService', - defaultMessage: 'Do you really want to delete the {serviceName} service?', - }, -}); - -let pollIndicatorTransition = 'none'; -let polledTransition = 'none'; -let pollAnsweredTransition = 'none'; - -if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { - pollIndicatorTransition = 'background 0.5s'; - polledTransition = 'background 0.1s'; - pollAnsweredTransition = 'background 0.1s'; -} - -const styles = { - pollIndicator: { - position: 'absolute', - bottom: 2, - width: 10, - height: 10, - borderRadius: 5, - background: 'gray', - transition: pollIndicatorTransition, - }, - pollIndicatorPoll: { - left: 2, - }, - pollIndicatorAnswer: { - left: 14, - }, - polled: { - background: 'yellow !important', - transition: polledTransition, - }, - pollAnswered: { - background: 'green !important', - transition: pollAnsweredTransition, - }, - stale: { - background: 'red !important', - }, -}; - -class TabItem extends Component { - static propTypes = { - classes: PropTypes.object.isRequired, - service: PropTypes.instanceOf(ServiceModel).isRequired, - clickHandler: PropTypes.func.isRequired, - shortcutIndex: PropTypes.number.isRequired, - reload: PropTypes.func.isRequired, - toggleNotifications: PropTypes.func.isRequired, - toggleAudio: PropTypes.func.isRequired, - toggleDarkMode: PropTypes.func.isRequired, - openSettings: PropTypes.func.isRequired, - deleteService: PropTypes.func.isRequired, - disableService: PropTypes.func.isRequired, - enableService: PropTypes.func.isRequired, - hibernateService: PropTypes.func.isRequired, - wakeUpService: PropTypes.func.isRequired, - showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, - showServiceNameSetting: PropTypes.bool.isRequired, - showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, - stores: PropTypes.shape({ - settings: PropTypes.instanceOf(SettingsStore).isRequired, - }).isRequired, - }; - - @observable isPolled = false; - - @observable isPollAnswered = false; - - constructor(props) { - super(props); - - makeObservable(this); - - this.state = { - showShortcutIndex: false, - }; - - reaction( - () => this.props.stores.settings.app.enableLongPressServiceHint, - () => { - this.checkForLongPress( - this.props.stores.settings.app.enableLongPressServiceHint, - ); - }, - ); - } - - handleShortcutIndex = (event, showShortcutIndex = true) => { - if (event.key === 'Shift') { - this.setState({ showShortcutIndex }); - } - }; - - checkForLongPress = enableLongPressServiceHint => { - if (enableLongPressServiceHint) { - document.addEventListener('keydown', e => { - this.handleShortcutIndex(e); - }); - - document.addEventListener('keyup', e => { - this.handleShortcutIndex(e, false); - }); - } - }; - - componentDidMount() { - const { service, stores } = this.props; - - if (IS_SERVICE_DEBUGGING_ENABLED) { - autorun(() => { - if (Date.now() - service.lastPoll < ms('0.2s')) { - this.isPolled = true; - - setTimeout(() => { - this.isPolled = false; - }, ms('1s')); - } - - if (Date.now() - service.lastPollAnswer < ms('0.2s')) { - this.isPollAnswered = true; - - setTimeout(() => { - this.isPollAnswered = false; - }, ms('1s')); - } - }); - } - - this.checkForLongPress(stores.settings.app.enableLongPressServiceHint); - } - - render() { - const { - classes, - service, - clickHandler, - shortcutIndex, - reload, - toggleNotifications, - toggleAudio, - toggleDarkMode, - deleteService, - disableService, - enableService, - hibernateService, - wakeUpService, - openSettings, - showMessageBadgeWhenMutedSetting, - showServiceNameSetting, - showMessageBadgesEvenWhenMuted, - } = this.props; - const { intl } = this.props; - - const menuTemplate = [ - { - label: service.name || service.recipe.name, - enabled: false, - }, - { - type: 'separator', - }, - { - label: intl.formatMessage(messages.reload), - click: reload, - accelerator: `${cmdOrCtrlShortcutKey()}+R`, - enabled: service.isEnabled, - }, - { - label: intl.formatMessage(globalMessages.edit), - click: () => - openSettings({ - path: `services/edit/${service.id}`, - }), - }, - { - type: 'separator', - }, - { - label: service.isNotificationEnabled - ? intl.formatMessage(messages.disableNotifications) - : intl.formatMessage(messages.enableNotifications), - click: () => toggleNotifications(), - accelerator: `${cmdOrCtrlShortcutKey()}+${altKey()}+N`, - enabled: service.isEnabled, - }, - { - label: service.isMuted - ? intl.formatMessage(messages.enableAudio) - : intl.formatMessage(messages.disableAudio), - click: () => toggleAudio(), - accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+A`, - enabled: service.isEnabled, - }, - { - label: service.isDarkModeEnabled - ? intl.formatMessage(messages.disableDarkMode) - : intl.formatMessage(messages.enableDarkMode), - click: () => toggleDarkMode(), - accelerator: `${shiftKey()}+${altKey()}+D`, - enabled: service.isEnabled, - }, - { - label: intl.formatMessage( - service.isEnabled ? messages.disableService : messages.enableService, - ), - click: () => (service.isEnabled ? disableService() : enableService()), - accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+S`, - }, - { - label: intl.formatMessage( - service.isHibernating - ? messages.wakeUpService - : messages.hibernateService, - ), - // eslint-disable-next-line no-confusing-arrow - click: () => - service.isHibernating ? wakeUpService() : hibernateService(), - enabled: service.isEnabled && service.canHibernate, - }, - { - type: 'separator', - }, - { - label: intl.formatMessage(messages.deleteService), - click: () => { - const selection = dialog.showMessageBoxSync(app.mainWindow, { - type: 'question', - message: intl.formatMessage(messages.deleteService), - detail: intl.formatMessage(messages.confirmDeleteService, { - serviceName: service.name || service.recipe.name, - }), - buttons: [ - intl.formatMessage(globalMessages.yes), - intl.formatMessage(globalMessages.no), - ], - }); - if (selection === 0) { - deleteService(); - } - }, - }, - ]; - const menu = Menu.buildFromTemplate(menuTemplate); - - let notificationBadge = null; - if ( - (showMessageBadgeWhenMutedSetting || service.isNotificationEnabled) && - showMessageBadgesEvenWhenMuted && - service.isBadgeEnabled - ) { - notificationBadge = ( - <> - {service.unreadDirectMessageCount > 0 && ( - - {service.unreadDirectMessageCount} - - )} - {service.unreadIndirectMessageCount > 0 && - service.unreadDirectMessageCount === 0 && - service.isIndirectMessageBadgeEnabled && ( - - )} - {service.isHibernating && ( - - )} - - ); - } - - let errorBadge = null; - if (service.isError) { - errorBadge = ( - - ); - } - - const showMediaBadge = - service.isMediaBadgeEnabled && - service.isMediaPlaying && - service.isEnabled; - const mediaBadge = ( - - ); - - return ( -
  • menu.popup()} - data-tip={`${service.name} ${ - shortcutIndex <= 9 - ? `(${cmdOrCtrlShortcutKey(false)}+${shortcutIndex})` - : '' - }`} - > - - {showServiceNameSetting && ( - {service.name} - )} - {notificationBadge} - {errorBadge} - {showMediaBadge && mediaBadge} - {IS_SERVICE_DEBUGGING_ENABLED && ( - <> -
    -
    - - )} - {shortcutIndex <= 9 && this.state.showShortcutIndex && ( - {shortcutIndex} - )} -
  • - ); - } -} - -export default injectIntl( - SortableElement( - injectSheet(styles, { injectTheme: true })( - inject('stores')(observer(TabItem)), - ), - ), -); diff --git a/src/components/services/tabs/TabItem.tsx b/src/components/services/tabs/TabItem.tsx new file mode 100644 index 000000000..fae788764 --- /dev/null +++ b/src/components/services/tabs/TabItem.tsx @@ -0,0 +1,411 @@ +import { app, dialog, Menu } from '@electron/remote'; +import { Component } from 'react'; +import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; +import { inject, observer } from 'mobx-react'; +import classnames from 'classnames'; +import { SortableElement } from 'react-sortable-hoc'; +import injectSheet, { WithStylesProps } from 'react-jss'; +import ms from 'ms'; + +import { autorun, makeObservable, observable, reaction } from 'mobx'; +import { mdiExclamation, mdiVolumeSource } from '@mdi/js'; +import Service from '../../../models/Service'; +import { altKey, cmdOrCtrlShortcutKey, shiftKey } from '../../../environment'; +import globalMessages from '../../../i18n/globalMessages'; +import Icon from '../../ui/icon'; +import { Stores } from '../../../@types/stores.types'; +import MenuItemConstructorOptions = Electron.MenuItemConstructorOptions; + +const IS_SERVICE_DEBUGGING_ENABLED = ( + localStorage.getItem('debug') || '' +).includes('Ferdium:Service'); + +const messages = defineMessages({ + reload: { + id: 'tabs.item.reload', + defaultMessage: 'Reload', + }, + disableNotifications: { + id: 'tabs.item.disableNotifications', + defaultMessage: 'Disable notifications', + }, + enableNotifications: { + id: 'tabs.item.enableNotification', + defaultMessage: 'Enable notifications', + }, + disableAudio: { + id: 'tabs.item.disableAudio', + defaultMessage: 'Disable audio', + }, + enableAudio: { + id: 'tabs.item.enableAudio', + defaultMessage: 'Enable audio', + }, + enableDarkMode: { + id: 'tabs.item.enableDarkMode', + defaultMessage: 'Enable Dark mode', + }, + disableDarkMode: { + id: 'tabs.item.disableDarkMode', + defaultMessage: 'Disable Dark mode', + }, + disableService: { + id: 'tabs.item.disableService', + defaultMessage: 'Disable service', + }, + enableService: { + 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', + }, + confirmDeleteService: { + id: 'tabs.item.confirmDeleteService', + defaultMessage: 'Do you really want to delete the {serviceName} service?', + }, +}); + +let pollIndicatorTransition = 'none'; +let polledTransition = 'none'; +let pollAnsweredTransition = 'none'; + +if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { + pollIndicatorTransition = 'background 0.5s'; + polledTransition = 'background 0.1s'; + pollAnsweredTransition = 'background 0.1s'; +} + +const styles = { + pollIndicator: { + position: 'absolute', + bottom: 2, + width: 10, + height: 10, + borderRadius: 5, + background: 'gray', + transition: pollIndicatorTransition, + }, + pollIndicatorPoll: { + left: 2, + }, + pollIndicatorAnswer: { + left: 14, + }, + polled: { + background: 'yellow !important', + transition: polledTransition, + }, + pollAnswered: { + background: 'green !important', + transition: pollAnsweredTransition, + }, + stale: { + background: 'red !important', + }, +}; + +interface IProps extends WrappedComponentProps, WithStylesProps { + showMessageBadgeWhenMutedSetting: boolean; + showServiceNameSetting: boolean; + showMessageBadgesEvenWhenMuted: boolean; + service: Service; + shortcutIndex: number; + stores?: Stores; + reload: () => void; + + clickHandler: () => void; + toggleNotifications: () => void; + toggleAudio: () => void; + toggleDarkMode: () => void; + openSettings: (args: { path: string }) => void; + deleteService: () => void; + disableService: () => void; + enableService: () => void; + hibernateService: () => void; + wakeUpService: () => void; +} + +interface IState { + showShortcutIndex: boolean; +} + +@inject('stores') +@observer +class TabItem extends Component { + @observable isPolled = false; + + @observable isPollAnswered = false; + + constructor(props) { + super(props); + + makeObservable(this); + + this.state = { + showShortcutIndex: false, + }; + + reaction( + () => this.props.stores!.settings.app.enableLongPressServiceHint, + () => { + this.checkForLongPress( + this.props.stores!.settings.app.enableLongPressServiceHint, + ); + }, + ); + } + + handleShortcutIndex = (event, showShortcutIndex = true) => { + if (event.key === 'Shift') { + this.setState({ showShortcutIndex }); + } + }; + + checkForLongPress = enableLongPressServiceHint => { + if (enableLongPressServiceHint) { + document.addEventListener('keydown', e => { + this.handleShortcutIndex(e); + }); + + document.addEventListener('keyup', e => { + this.handleShortcutIndex(e, false); + }); + } + }; + + componentDidMount() { + const { service, stores } = this.props; + + if (IS_SERVICE_DEBUGGING_ENABLED) { + autorun(() => { + if (Date.now() - service.lastPoll < ms('0.2s')) { + this.isPolled = true; + + setTimeout(() => { + this.isPolled = false; + }, ms('1s')); + } + + if (Date.now() - service.lastPollAnswer < ms('0.2s')) { + this.isPollAnswered = true; + + setTimeout(() => { + this.isPollAnswered = false; + }, ms('1s')); + } + }); + } + + this.checkForLongPress(stores!.settings.app.enableLongPressServiceHint); + } + + render() { + const { + classes, + service, + clickHandler, + shortcutIndex, + reload, + toggleNotifications, + toggleAudio, + toggleDarkMode, + deleteService, + disableService, + enableService, + hibernateService, + wakeUpService, + openSettings, + showMessageBadgeWhenMutedSetting, + showServiceNameSetting, + showMessageBadgesEvenWhenMuted, + } = this.props; + const { intl } = this.props; + + const menuTemplate: Array = [ + { + label: service.name || service.recipe.name, + enabled: false, + }, + { + type: 'separator', + }, + { + label: intl.formatMessage(messages.reload), + click: reload, + accelerator: `${cmdOrCtrlShortcutKey()}+R`, + enabled: service.isEnabled, + }, + { + label: intl.formatMessage(globalMessages.edit), + click: () => + openSettings({ + path: `services/edit/${service.id}`, + }), + }, + { + type: 'separator', + }, + { + label: service.isNotificationEnabled + ? intl.formatMessage(messages.disableNotifications) + : intl.formatMessage(messages.enableNotifications), + click: () => toggleNotifications(), + accelerator: `${cmdOrCtrlShortcutKey()}+${altKey()}+N`, + enabled: service.isEnabled, + }, + { + label: service.isMuted + ? intl.formatMessage(messages.enableAudio) + : intl.formatMessage(messages.disableAudio), + click: () => toggleAudio(), + accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+A`, + enabled: service.isEnabled, + }, + { + label: service.isDarkModeEnabled + ? intl.formatMessage(messages.disableDarkMode) + : intl.formatMessage(messages.enableDarkMode), + click: () => toggleDarkMode(), + accelerator: `${shiftKey()}+${altKey()}+D`, + enabled: service.isEnabled, + }, + { + label: intl.formatMessage( + service.isEnabled ? messages.disableService : messages.enableService, + ), + click: () => (service.isEnabled ? disableService() : enableService()), + accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+S`, + }, + { + label: intl.formatMessage( + service.isHibernating + ? messages.wakeUpService + : messages.hibernateService, + ), + // eslint-disable-next-line no-confusing-arrow + click: () => + service.isHibernating ? wakeUpService() : hibernateService(), + enabled: service.isEnabled && service.canHibernate, + }, + { + type: 'separator', + }, + { + label: intl.formatMessage(messages.deleteService), + click: () => { + // @ts-ignore + const selection = dialog.showMessageBoxSync(app.mainWindow, { + type: 'question', + message: intl.formatMessage(messages.deleteService), + detail: intl.formatMessage(messages.confirmDeleteService, { + serviceName: service.name || service.recipe.name, + }), + buttons: [ + intl.formatMessage(globalMessages.yes), + intl.formatMessage(globalMessages.no), + ], + }); + if (selection === 0) { + deleteService(); + } + }, + }, + ]; + const menu = Menu.buildFromTemplate(menuTemplate); + + const showNotificationBadge = + (showMessageBadgeWhenMutedSetting || service.isNotificationEnabled) && + showMessageBadgesEvenWhenMuted && + service.isBadgeEnabled; + + const showMediaBadge = + service.isMediaBadgeEnabled && + service.isMediaPlaying && + service.isEnabled; + const mediaBadge = ( + + ); + + return ( +
  • menu.popup()} + data-tip={`${service.name} ${ + shortcutIndex <= 9 + ? `(${cmdOrCtrlShortcutKey(false)}+${shortcutIndex})` + : '' + }`} + > + + {showServiceNameSetting && ( + {service.name} + )} + {showNotificationBadge && ( + <> + {service.unreadDirectMessageCount > 0 && ( + + {service.unreadDirectMessageCount} + + )} + {service.unreadIndirectMessageCount > 0 && + service.unreadDirectMessageCount === 0 && + service.isIndirectMessageBadgeEnabled && ( + + )} + {service.isHibernating && ( + + )} + + )} + {service.isError && ( + + )} + {showMediaBadge && mediaBadge} + {IS_SERVICE_DEBUGGING_ENABLED && ( + <> +
    +
    + + )} + {shortcutIndex <= 9 && this.state.showShortcutIndex && ( + {shortcutIndex} + )} +
  • + ); + } +} + +export default injectIntl( + SortableElement(injectSheet(styles, { injectTheme: true })(TabItem)), +); diff --git a/src/components/services/tabs/Tabbar.js b/src/components/services/tabs/Tabbar.js deleted file mode 100644 index 0eaf641e3..000000000 --- a/src/components/services/tabs/Tabbar.js +++ /dev/null @@ -1,123 +0,0 @@ -import { Component } from 'react'; -import PropTypes from 'prop-types'; -import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; - -import TabBarSortableList from './TabBarSortableList'; - -class TabBar extends Component { - static propTypes = { - services: MobxPropTypes.arrayOrObservableArray.isRequired, - setActive: PropTypes.func.isRequired, - openSettings: PropTypes.func.isRequired, - enableToolTip: PropTypes.func.isRequired, - disableToolTip: PropTypes.func.isRequired, - reorder: PropTypes.func.isRequired, - reload: PropTypes.func.isRequired, - toggleNotifications: PropTypes.func.isRequired, - toggleAudio: PropTypes.func.isRequired, - toggleDarkMode: PropTypes.func.isRequired, - deleteService: PropTypes.func.isRequired, - updateService: PropTypes.func.isRequired, - hibernateService: PropTypes.func.isRequired, - wakeUpService: PropTypes.func.isRequired, - useHorizontalStyle: PropTypes.bool.isRequired, - showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, - showServiceNameSetting: PropTypes.bool.isRequired, - showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, - }; - - onSortEnd = ({ oldIndex, newIndex }) => { - const { enableToolTip, reorder } = this.props; - - enableToolTip(); - reorder({ oldIndex, newIndex }); - }; - - shouldPreventSorting = event => event.target.tagName !== 'LI'; - - toggleService = ({ serviceId, isEnabled }) => { - const { updateService } = this.props; - - if (serviceId) { - updateService({ - serviceId, - serviceData: { - isEnabled, - isMediaPlaying: false, - }, - redirect: false, - }); - } - }; - - disableService({ serviceId }) { - this.toggleService({ serviceId, isEnabled: false }); - } - - enableService({ serviceId }) { - this.toggleService({ serviceId, isEnabled: true }); - } - - hibernateService({ serviceId }) { - if (serviceId) { - this.props.hibernateService({ serviceId }); - } - } - - wakeUpService({ serviceId }) { - if (serviceId) { - this.props.wakeUpService({ serviceId }); - } - } - - render() { - const { - services, - setActive, - openSettings, - disableToolTip, - reload, - toggleNotifications, - toggleAudio, - toggleDarkMode, - deleteService, - useHorizontalStyle, - showMessageBadgeWhenMutedSetting, - showServiceNameSetting, - showMessageBadgesEvenWhenMuted, - } = this.props; - - const axis = useHorizontalStyle ? 'x' : 'y'; - - return ( -
    - this.disableService(args)} - enableService={args => this.enableService(args)} - hibernateService={args => this.hibernateService(args)} - wakeUpService={args => this.wakeUpService(args)} - openSettings={openSettings} - distance={20} - axis={axis} - lockAxis={axis} - helperClass="is-reordering" - showMessageBadgeWhenMutedSetting={showMessageBadgeWhenMutedSetting} - showServiceNameSetting={showServiceNameSetting} - showMessageBadgesEvenWhenMuted={showMessageBadgesEvenWhenMuted} - /> -
    - ); - } -} - -export default observer(TabBar); diff --git a/src/components/services/tabs/Tabbar.tsx b/src/components/services/tabs/Tabbar.tsx new file mode 100644 index 000000000..64541cc8f --- /dev/null +++ b/src/components/services/tabs/Tabbar.tsx @@ -0,0 +1,128 @@ +import { Component } from 'react'; +import { observer } from 'mobx-react'; + +import TabBarSortableList from './TabBarSortableList'; +import Service from '../../../models/Service'; + +interface IProps { + useHorizontalStyle: boolean; + showMessageBadgeWhenMutedSetting: boolean; + showServiceNameSetting: boolean; + showMessageBadgesEvenWhenMuted: boolean; + services: Service[]; + setActive: (args: { serviceId: string }) => void; + openSettings: (args: { path: string }) => void; + enableToolTip: () => void; + disableToolTip: () => void; + reorder: (args: { oldIndex: number; newIndex: number }) => void; + reload: (args: { serviceId: string }) => void; + toggleNotifications: (args: { serviceId: string }) => void; + toggleAudio: (args: { serviceId: string }) => void; + toggleDarkMode: (args: { serviceId: string }) => void; + deleteService: (args: { serviceId: string }) => void; + hibernateService: (args: { serviceId: string }) => void; + wakeUpService: (args: { serviceId: string }) => void; + updateService: (args: { + serviceId: string; + serviceData: { isEnabled: boolean; isMediaPlaying: boolean }; + redirect: boolean; + }) => void; +} + +@observer +class TabBar extends Component { + onSortEnd = ({ oldIndex, newIndex }) => { + const { enableToolTip, reorder } = this.props; + + enableToolTip(); + reorder({ oldIndex, newIndex }); + }; + + shouldPreventSorting = event => event.target.tagName !== 'LI'; + + toggleService = (args: { serviceId: string; isEnabled: boolean }) => { + const { updateService } = this.props; + + if (args.serviceId) { + updateService({ + serviceId: args.serviceId, + serviceData: { + isEnabled: args.isEnabled, + isMediaPlaying: false, + }, + redirect: false, + }); + } + }; + + disableService({ serviceId }) { + this.toggleService({ serviceId, isEnabled: false }); + } + + enableService({ serviceId }) { + this.toggleService({ serviceId, isEnabled: true }); + } + + hibernateService({ serviceId }) { + if (serviceId) { + this.props.hibernateService({ serviceId }); + } + } + + wakeUpService({ serviceId }) { + if (serviceId) { + this.props.wakeUpService({ serviceId }); + } + } + + render() { + const { + services, + setActive, + openSettings, + disableToolTip, + reload, + toggleNotifications, + toggleAudio, + toggleDarkMode, + deleteService, + useHorizontalStyle, + showMessageBadgeWhenMutedSetting, + showServiceNameSetting, + showMessageBadgesEvenWhenMuted, + } = this.props; + + const axis = useHorizontalStyle ? 'x' : 'y'; + + return ( +
    + this.disableService(args)} + enableService={args => this.enableService(args)} + hibernateService={args => this.hibernateService(args)} + wakeUpService={args => this.wakeUpService(args)} + openSettings={openSettings} + distance={20} + axis={axis} + lockAxis={axis} + helperClass="is-reordering" + showMessageBadgeWhenMutedSetting={showMessageBadgeWhenMutedSetting} + showServiceNameSetting={showServiceNameSetting} + showMessageBadgesEvenWhenMuted={showMessageBadgesEvenWhenMuted} + /> +
    + ); + } +} + +export default TabBar; -- cgit v1.2.3-70-g09d2