diff --git a/client/src/javascript/components/sidebar/NotificationsButton.tsx b/client/src/javascript/components/sidebar/NotificationsButton.tsx index 7823d809..5eb98c46 100644 --- a/client/src/javascript/components/sidebar/NotificationsButton.tsx +++ b/client/src/javascript/components/sidebar/NotificationsButton.tsx @@ -1,8 +1,7 @@ import classnames from 'classnames'; -import {Component, createRef} from 'react'; -import {defineMessages, injectIntl, WrappedComponentProps} from 'react-intl'; +import {defineMessages, useIntl} from 'react-intl'; +import {FC, useEffect, useRef, useState} from 'react'; import {observer} from 'mobx-react'; -import {reaction} from 'mobx'; import type {Notification} from '@shared/types/Notification'; @@ -14,13 +13,14 @@ import NotificationIcon from '../icons/NotificationIcon'; import NotificationStore from '../../stores/NotificationStore'; import Tooltip from '../general/Tooltip'; -interface NotificationsButtonStates { - isLoading: boolean; - paginationStart: number; - prevHeight: number; -} +const NOTIFICATIONS_PER_PAGE = 10; -const loadingIndicatorIcon = ; +const fetchNotifications = (paginationStart: number) => + FloodActions.fetchNotifications({ + id: 'notification-tooltip', + limit: NOTIFICATIONS_PER_PAGE, + start: paginationStart, + }); const MESSAGES = defineMessages({ 'notification.torrent.finished.heading': { @@ -41,271 +41,248 @@ const MESSAGES = defineMessages({ 'notification.feed.torrent.added.body': { id: 'notification.feed.torrent.added.body', }, - noNotification: { - id: 'notification.no.notification', - }, - clearAll: { - id: 'notification.clear.all', - }, - showing: { - id: 'notification.showing', - }, - at: { - id: 'general.at', - }, - to: { - id: 'general.to', - }, - of: { - id: 'general.of', - }, }); -const NOTIFICATIONS_PER_PAGE = 10; - -@observer -class NotificationsButton extends Component { - tooltipRef: Tooltip | null = null; - notificationsListRef = createRef(); - - constructor(props: WrappedComponentProps) { - super(props); - - reaction( - () => NotificationStore.notificationCount, - (count) => { - if (count.total > 0 && this.tooltipRef?.isOpen()) this.fetchNotifications(); - }, - ); - - this.state = { - isLoading: false, - paginationStart: 0, - prevHeight: 0, - }; - } - - getBottomToolbar = () => { - const {notificationCount} = NotificationStore; - - if (notificationCount.total > 0) { - const newerButtonClass = classnames( - 'toolbar__item toolbar__item--button', - 'tooltip__content--padding-surrogate', - { - 'is-disabled': this.state.paginationStart === 0, - }, - ); - const olderButtonClass = classnames( - 'toolbar__item toolbar__item--button', - 'tooltip__content--padding-surrogate', - { - 'is-disabled': this.state.paginationStart + NOTIFICATIONS_PER_PAGE >= notificationCount.total, - }, - ); - - const olderFrom = this.state.paginationStart + NOTIFICATIONS_PER_PAGE + 1; - let olderTo = this.state.paginationStart + NOTIFICATIONS_PER_PAGE * 2; - let newerFrom = this.state.paginationStart - NOTIFICATIONS_PER_PAGE; - const newerTo = this.state.paginationStart; - - if (olderTo > notificationCount.total) { - olderTo = notificationCount.total; - } - - if (newerFrom < 0) { - newerFrom = 0; - } - - return ( -
    -
  • - - {`${newerFrom + 1} - ${newerTo}`} -
  • -
  • - {this.props.intl.formatMessage(MESSAGES.clearAll)} -
  • -
  • - {`${olderFrom} - ${olderTo}`} - -
  • -
- ); - } - - return null; - }; - - getNotification = (notification: Notification, index: number) => { - const {intl} = this.props; - const date = intl.formatDate(notification.ts, { - year: 'numeric', - month: 'long', - day: '2-digit', - }); - const time = intl.formatTime(notification.ts); - - return ( -
  • -
    - - {intl.formatMessage( - MESSAGES[`${notification.id}.heading` as keyof typeof MESSAGES] || {id: 'general.error.unknown'}, - )} - - {' — '} - {`${date} ${intl.formatMessage(MESSAGES.at)} ${time}`} -
    -
    - {intl.formatMessage( - MESSAGES[`${notification.id}.body` as keyof typeof MESSAGES] || { - id: 'general.error.unknown', - }, - notification.data, - )} -
    -
  • - ); - }; - - getTopToolbar() { - const {intl} = this.props; - const {paginationStart} = this.state; - - const {notificationCount} = NotificationStore; - - if (notificationCount.total > NOTIFICATIONS_PER_PAGE) { - let countStart = paginationStart + 1; - let countEnd = paginationStart + NOTIFICATIONS_PER_PAGE; - - if (countStart > notificationCount.total) { - countStart = notificationCount.total; - } - - if (countEnd > notificationCount.total) { - countEnd = notificationCount.total; - } - - return ( -
    - - {`${intl.formatMessage(MESSAGES.showing)} `} - - {countStart} - {` ${intl.formatMessage(MESSAGES.to)} `} - {countEnd} - - {` ${intl.formatMessage(MESSAGES.of)} `} - {notificationCount.total} - -
    - ); - } - - return null; - } - - fetchNotifications = () => { - this.setState({isLoading: true}); - - FloodActions.fetchNotifications({ - id: 'notification-tooltip', - limit: NOTIFICATIONS_PER_PAGE, - start: this.state.paginationStart, - }).then(() => { - this.setState({isLoading: false}); - }); - }; - - handleClearNotificationsClick = () => { - this.setState({ - paginationStart: 0, - prevHeight: 0, - }); - - if (this.tooltipRef != null) { - this.tooltipRef.dismissTooltip(); - } - - FloodActions.clearNotifications(); - }; - - handleNewerNotificationsClick = () => { - if (this.state.paginationStart - NOTIFICATIONS_PER_PAGE >= 0) { - this.setState((state) => { - const paginationStart = state.paginationStart - NOTIFICATIONS_PER_PAGE; - return { - paginationStart, - prevHeight: this.notificationsListRef.current?.clientHeight || 0, - }; - }, this.fetchNotifications); - } - }; - - handleOlderNotificationsClick = () => { - const {notificationCount} = NotificationStore; - - if (notificationCount.total > this.state.paginationStart + NOTIFICATIONS_PER_PAGE) { - this.setState((state) => { - const paginationStart = state.paginationStart + NOTIFICATIONS_PER_PAGE; - return { - paginationStart, - prevHeight: this.notificationsListRef.current?.clientHeight || 0, - }; - }, this.fetchNotifications); - } - }; - - render() { - const {intl} = this.props; - const {isLoading, prevHeight} = this.state; - const {hasNotification, notifications, notificationCount} = NotificationStore; - - return ( - - {this.getTopToolbar()} -
    {loadingIndicatorIcon}
    -
      - {notifications.map(this.getNotification)} -
    - {this.getBottomToolbar()} - - ) : ( -
    - {intl.formatMessage(MESSAGES.noNotification)} -
    - ) - } - interactive - onOpen={() => this.fetchNotifications()} - ref={(ref) => { - this.tooltipRef = ref; - }} - width={340} - position="bottom" - wrapperClassName="sidebar__action sidebar__icon-button - tooltip__wrapper"> - - {hasNotification ? {notificationCount.total} : null} -
    - ); - } +interface NotificationTopToolbarProps { + paginationStart: number; + notificationTotal: number; } -export default injectIntl(NotificationsButton); +const NotificationTopToolbar: FC = ({ + paginationStart, + notificationTotal, +}: NotificationTopToolbarProps) => { + const intl = useIntl(); + + if (notificationTotal > NOTIFICATIONS_PER_PAGE) { + let countStart = paginationStart + 1; + let countEnd = paginationStart + NOTIFICATIONS_PER_PAGE; + + if (countStart > notificationTotal) { + countStart = notificationTotal; + } + + if (countEnd > notificationTotal) { + countEnd = notificationTotal; + } + + return ( +
    + + {`${intl.formatMessage({ + id: 'notification.showing', + })} `} + + {countStart} + {` ${intl.formatMessage({ + id: 'general.to', + })} `} + {countEnd} + + {` ${intl.formatMessage({ + id: 'general.of', + })} `} + {notificationTotal} + +
    + ); + } + + return null; +}; + +interface NotificationItemProps { + index: number; + notification: Notification; +} + +const NotificationItem: FC = ({index, notification}: NotificationItemProps) => { + const intl = useIntl(); + const date = intl.formatDate(notification.ts, { + year: 'numeric', + month: 'long', + day: '2-digit', + }); + const time = intl.formatTime(notification.ts); + + return ( +
  • +
    + + {intl.formatMessage( + MESSAGES[`${notification.id}.heading` as keyof typeof MESSAGES] || {id: 'general.error.unknown'}, + )} + + {' — '} + {`${date} ${intl.formatMessage({ + id: 'general.at', + })} ${time}`} +
    +
    + {intl.formatMessage( + MESSAGES[`${notification.id}.body` as keyof typeof MESSAGES] || { + id: 'general.error.unknown', + }, + notification.data, + )} +
    +
  • + ); +}; + +interface NotificationBottomToolbarProps { + paginationStart: number; + notificationTotal: number; + onPrevClick: () => void; + onNextClick: () => void; + onClearClick: () => void; +} + +const NotificationBottomToolbar: FC = ({ + paginationStart, + notificationTotal, + onPrevClick, + onClearClick, + onNextClick, +}: NotificationBottomToolbarProps) => { + const intl = useIntl(); + + if (notificationTotal > 0) { + const newerButtonClass = classnames('toolbar__item toolbar__item--button', 'tooltip__content--padding-surrogate', { + 'is-disabled': paginationStart === 0, + }); + const olderButtonClass = classnames('toolbar__item toolbar__item--button', 'tooltip__content--padding-surrogate', { + 'is-disabled': paginationStart + NOTIFICATIONS_PER_PAGE >= notificationTotal, + }); + + const olderFrom = paginationStart + NOTIFICATIONS_PER_PAGE + 1; + let olderTo = paginationStart + NOTIFICATIONS_PER_PAGE * 2; + let newerFrom = paginationStart - NOTIFICATIONS_PER_PAGE; + const newerTo = paginationStart; + + if (olderTo > notificationTotal) { + olderTo = notificationTotal; + } + + if (newerFrom < 0) { + newerFrom = 0; + } + + return ( +
      +
    • + + {`${newerFrom + 1} - ${newerTo}`} +
    • +
    • + {intl.formatMessage({ + id: 'notification.clear.all', + })} +
    • +
    • + {`${olderFrom} - ${olderTo}`} + +
    • +
    + ); + } + + return null; +}; + +const NotificationsButton: FC = observer(() => { + const intl = useIntl(); + + const tooltipRef = useRef(null); + const notificationsListRef = useRef(null); + + const [isLoading, setIsLoading] = useState(false); + const [paginationStart, setPaginationStart] = useState(0); + const [prevHeight, setPrevHeight] = useState(0); + + const {hasNotification, notifications, notificationCount} = NotificationStore; + + useEffect(() => { + if (notificationCount.total > 0 && tooltipRef.current?.isOpen()) { + setIsLoading(true); + fetchNotifications(paginationStart).finally(() => setIsLoading(false)); + } + }, [notificationCount, paginationStart]); + + return ( + + +
    + +
    +
      + {notifications.map((notification, index) => ( + + ))} +
    + { + const newPaginationStart = paginationStart - NOTIFICATIONS_PER_PAGE; + if (newPaginationStart >= 0) { + setPrevHeight(notificationsListRef.current?.clientHeight || 0); + setPaginationStart(newPaginationStart); + } + }} + onClearClick={() => { + if (tooltipRef.current != null) { + tooltipRef.current.dismissTooltip(); + } + + FloodActions.clearNotifications(); + + setPrevHeight(0); + setPaginationStart(0); + }} + onNextClick={() => { + const newPaginationStart = paginationStart + NOTIFICATIONS_PER_PAGE; + if (notificationCount.total > newPaginationStart) { + setPrevHeight(notificationsListRef.current?.clientHeight || 0); + setPaginationStart(newPaginationStart); + } + }} + /> + + ) : ( +
    + {intl.formatMessage({ + id: 'notification.no.notification', + })} +
    + ) + } + interactive + onOpen={() => fetchNotifications(paginationStart)} + ref={tooltipRef} + width={340} + position="bottom" + wrapperClassName="sidebar__action sidebar__icon-button + tooltip__wrapper"> + + {hasNotification ? {notificationCount.total} : null} +
    + ); +}); + +export default NotificationsButton;