From e6181ef2c9a578f9811ad957bef5a2eb25d6daf7 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Fri, 13 Nov 2020 21:40:31 +0800 Subject: [PATCH] client: convert more components to Functional Component --- client/scripts/build.js | 10 +- client/src/javascript/app.tsx | 137 ++- .../src/javascript/components/AppWrapper.tsx | 12 +- .../javascript/components/alerts/Alert.tsx | 4 +- .../javascript/components/alerts/Alerts.tsx | 8 +- .../javascript/components/general/Badge.tsx | 6 +- .../general/ClientConnectionInterruption.tsx | 16 +- .../components/general/Duration.tsx | 6 +- .../components/general/ListViewport.tsx | 87 +- .../components/general/LoadingIndicator.tsx | 4 +- .../components/general/LoadingOverlay.tsx | 4 +- .../javascript/components/general/Size.tsx | 4 +- .../components/general/SortableListItem.tsx | 8 +- .../components/general/WindowTitle.tsx | 8 +- .../general/filesystem/DirectoryTree.tsx | 10 +- .../components/icons/CountryFlagIcon.tsx | 4 +- .../components/layout/ApplicationContent.tsx | 6 +- .../components/layout/ApplicationPanel.tsx | 6 +- .../components/layout/ApplicationView.tsx | 6 +- .../javascript/components/modals/Modal.tsx | 12 +- .../components/modals/ModalActions.tsx | 4 +- .../modals/ModalFormSectionHeader.tsx | 6 +- .../components/modals/ModalTabs.tsx | 10 +- .../javascript/components/modals/Modals.tsx | 63 +- .../modals/confirm-modal/ConfirmModal.tsx | 8 +- .../move-torrents-modal/MoveTorrentsModal.tsx | 186 ++-- .../modals/set-tags-modal/SetTagsModal.tsx | 145 ++-- .../set-trackers-modal/SetTrackersModal.tsx | 195 ++--- .../modals/settings-modal/DiskUsageTab.tsx | 4 +- .../modals/settings-modal/SettingsModal.tsx | 4 +- .../TorrentDetailsModal.tsx | 8 +- .../TorrentGeneralInfo.tsx | 12 +- .../torrent-details-modal/TorrentHeading.tsx | 195 ++--- .../torrent-details-modal/TorrentPeers.tsx | 139 ++- .../torrent-details-modal/TorrentTrackers.tsx | 81 +- .../components/sidebar/DiskUsage.tsx | 14 +- .../components/sidebar/FeedsButton.tsx | 6 +- .../components/sidebar/LogoutButton.tsx | 7 +- .../components/sidebar/SettingsButton.tsx | 6 +- .../javascript/components/sidebar/Sidebar.tsx | 3 +- .../components/sidebar/SidebarActions.tsx | 6 +- .../components/sidebar/SidebarFilter.tsx | 4 +- .../components/sidebar/SidebarItem.tsx | 6 +- .../components/sidebar/StatusFilters.tsx | 8 +- .../components/sidebar/TagFilters.tsx | 8 +- .../components/sidebar/TrackerFilters.tsx | 8 +- .../sidebar/TransferRateDetails.tsx | 40 +- .../components/torrent-list/Action.tsx | 8 +- .../components/torrent-list/ActionBar.tsx | 183 ++-- .../components/torrent-list/SortDropdown.tsx | 8 +- .../components/torrent-list/TorrentList.tsx | 50 +- .../torrent-list/TorrentListCell.tsx | 40 +- .../torrent-list/TorrentListRow.tsx | 48 +- .../torrent-list/TorrentListRowCondensed.tsx | 118 +-- .../torrent-list/TorrentListRowExpanded.tsx | 192 ++--- .../src/javascript/components/views/Login.tsx | 4 +- .../javascript/components/views/Register.tsx | 4 +- .../views/TorrentClientOverview.tsx | 40 +- client/src/javascript/stores/UIStore.ts | 7 +- .../src/javascript/ui/components/Checkbox.tsx | 4 +- .../javascript/ui/components/Container.tsx | 6 +- .../src/javascript/ui/components/FadeIn.tsx | 6 +- .../ui/components/FormElementAddon.tsx | 8 +- .../src/javascript/ui/components/FormRow.tsx | 6 +- .../src/javascript/ui/components/Overlay.tsx | 8 +- client/src/javascript/ui/components/Radio.tsx | 4 +- .../src/javascript/ui/icons/LoadingRing.tsx | 4 +- package-lock.json | 797 +++++++++++++----- package.json | 4 +- 69 files changed, 1631 insertions(+), 1452 deletions(-) diff --git a/client/scripts/build.js b/client/scripts/build.js index 5f2b39d5..1b32237b 100644 --- a/client/scripts/build.js +++ b/client/scripts/build.js @@ -68,7 +68,15 @@ measureFileSizesBeforeBuild(paths.appBuild) }) .then( ({stats, previousFileSizes}) => { - if (stats.compilation.warnings.length !== 0) { + if (stats.hasErrors()) { + stats.compilation.errors.forEach((err) => { + console.error(err); + }); + + process.exit(1); + } + + if (stats.hasWarnings()) { console.log(chalk.yellow('Compiled with warnings.\n')); stats.compilation.warnings.forEach((warning) => { diff --git a/client/src/javascript/app.tsx b/client/src/javascript/app.tsx index b5ac08b3..07ac0fb1 100644 --- a/client/src/javascript/app.tsx +++ b/client/src/javascript/app.tsx @@ -1,8 +1,8 @@ +import {FC, lazy, Suspense, useEffect} from 'react'; import {observer} from 'mobx-react'; import {Router} from 'react-router-dom'; import {Route, Switch} from 'react-router'; import ReactDOM from 'react-dom'; -import * as React from 'react'; import AsyncIntlProvider from './i18n/languages'; import AppWrapper from './components/AppWrapper'; @@ -14,84 +14,73 @@ import UIStore from './stores/UIStore'; import '../sass/style.scss'; -const Login = React.lazy(() => import(/* webpackPrefetch: true */ './components/views/Login')); -const Register = React.lazy(() => import(/* webpackPrefetch: true */ './components/views/Register')); -const TorrentClientOverview = React.lazy( - () => import(/* webpackPreload: true */ './components/views/TorrentClientOverview'), -); +const Login = lazy(() => import(/* webpackPrefetch: true */ './components/views/Login')); +const Register = lazy(() => import(/* webpackPrefetch: true */ './components/views/Register')); +const TorrentClientOverview = lazy(() => import(/* webpackPreload: true */ './components/views/TorrentClientOverview')); -const initialize = async (): Promise => { - UIStore.registerDependency([ - { - id: 'notifications', - message: {id: 'dependency.loading.notifications'}, - }, - ]); +const FloodApp: FC = observer(() => { + useEffect(() => { + UIStore.registerDependency([ + { + id: 'notifications', + message: {id: 'dependency.loading.notifications'}, + }, + ]); - UIStore.registerDependency([ - { - id: 'torrent-taxonomy', - message: {id: 'dependency.loading.torrent.taxonomy'}, - }, - ]); + UIStore.registerDependency([ + { + id: 'torrent-taxonomy', + message: {id: 'dependency.loading.torrent.taxonomy'}, + }, + ]); - UIStore.registerDependency([ - { - id: 'transfer-data', - message: {id: 'dependency.loading.transfer.rate.details'}, - }, - { - id: 'transfer-history', - message: {id: 'dependency.loading.transfer.history'}, - }, - ]); + UIStore.registerDependency([ + { + id: 'transfer-data', + message: {id: 'dependency.loading.transfer.rate.details'}, + }, + { + id: 'transfer-history', + message: {id: 'dependency.loading.transfer.history'}, + }, + ]); - UIStore.registerDependency([ - { - id: 'torrent-list', - message: {id: 'dependency.loading.torrent.list'}, - }, - ]); + UIStore.registerDependency([ + { + id: 'torrent-list', + message: {id: 'dependency.loading.torrent.list'}, + }, + ]); - AuthActions.verify().then( - ({initialUser}: {initialUser?: boolean}): void => { - if (initialUser) { - history.replace('register'); - } else { - history.replace('overview'); - } - }, - (): void => { - history.replace('login'); - }, - ); -}; - -const appRoutes = ( - - - - - - - - - -); - -@observer -class FloodApp extends React.Component { - componentDidMount(): void { - initialize(); - } - - render(): React.ReactNode { - return ( - }> - {appRoutes} - + AuthActions.verify().then( + ({initialUser}: {initialUser?: boolean}): void => { + if (initialUser) { + history.replace('register'); + } else { + history.replace('overview'); + } + }, + (): void => { + history.replace('login'); + }, ); - } -} + }, []); + + return ( + }> + + + + + + + + + + + + + ); +}); ReactDOM.render(, document.getElementById('app')); diff --git a/client/src/javascript/components/AppWrapper.tsx b/client/src/javascript/components/AppWrapper.tsx index 38da887a..ce566993 100644 --- a/client/src/javascript/components/AppWrapper.tsx +++ b/client/src/javascript/components/AppWrapper.tsx @@ -1,6 +1,6 @@ import {CSSTransition, TransitionGroup} from 'react-transition-group'; +import {FC, ReactNode} from 'react'; import {observer} from 'mobx-react'; -import * as React from 'react'; import AuthStore from '../stores/AuthStore'; import ConfigStore from '../stores/ConfigStore'; @@ -11,13 +11,13 @@ import WindowTitle from './general/WindowTitle'; import LoadingOverlay from './general/LoadingOverlay'; interface AppWrapperProps { - children: React.ReactNode; + children: ReactNode; } -const AppWrapper: React.FC = (props: AppWrapperProps) => { +const AppWrapper: FC = observer((props: AppWrapperProps) => { const {children} = props; - let overlay: React.ReactNode = null; + let overlay: ReactNode = null; if (!AuthStore.isAuthenticating || (AuthStore.isAuthenticated && !UIStore.haveUIDependenciesResolved)) { overlay = ; } @@ -45,6 +45,6 @@ const AppWrapper: React.FC = (props: AppWrapperProps) => { {children} ); -}; +}); -export default observer(AppWrapper); +export default AppWrapper; diff --git a/client/src/javascript/components/alerts/Alert.tsx b/client/src/javascript/components/alerts/Alert.tsx index 609c5cc2..79fdcf95 100644 --- a/client/src/javascript/components/alerts/Alert.tsx +++ b/client/src/javascript/components/alerts/Alert.tsx @@ -1,6 +1,6 @@ +import {FC} from 'react'; import {FormattedMessage} from 'react-intl'; import classnames from 'classnames'; -import * as React from 'react'; import CircleCheckmarkIcon from '../icons/CircleCheckmarkIcon'; import CircleExclamationIcon from '../icons/CircleExclamationIcon'; @@ -11,7 +11,7 @@ interface AlertProps { type?: 'success' | 'error'; } -const Alert: React.FC = (props: AlertProps) => { +const Alert: FC = (props: AlertProps) => { const {id, count, type} = props; const alertClasses = classnames('alert', { diff --git a/client/src/javascript/components/alerts/Alerts.tsx b/client/src/javascript/components/alerts/Alerts.tsx index f8f8fc4f..4933ce5b 100644 --- a/client/src/javascript/components/alerts/Alerts.tsx +++ b/client/src/javascript/components/alerts/Alerts.tsx @@ -1,11 +1,11 @@ import {CSSTransition, TransitionGroup} from 'react-transition-group'; +import {FC} from 'react'; import {observer} from 'mobx-react'; -import * as React from 'react'; import Alert from './Alert'; import AlertStore from '../../stores/AlertStore'; -const Alerts: React.FC = () => { +const Alerts: FC = observer(() => { const {alerts, accumulation} = AlertStore; const sortedAlerts = Object.keys(alerts) @@ -33,6 +33,6 @@ const Alerts: React.FC = () => { ) : null} ); -}; +}); -export default observer(Alerts); +export default Alerts; diff --git a/client/src/javascript/components/general/Badge.tsx b/client/src/javascript/components/general/Badge.tsx index c78875fe..2abf6acd 100644 --- a/client/src/javascript/components/general/Badge.tsx +++ b/client/src/javascript/components/general/Badge.tsx @@ -1,10 +1,10 @@ -import * as React from 'react'; +import {FC, ReactNode} from 'react'; interface BadgeProps { - children: React.ReactNode; + children: ReactNode; } -const Badge: React.FC = ({children}: BadgeProps) => { +const Badge: FC = ({children}: BadgeProps) => { return
{children}
; }; diff --git a/client/src/javascript/components/general/ClientConnectionInterruption.tsx b/client/src/javascript/components/general/ClientConnectionInterruption.tsx index 01d2eeb2..86c0e2cc 100644 --- a/client/src/javascript/components/general/ClientConnectionInterruption.tsx +++ b/client/src/javascript/components/general/ClientConnectionInterruption.tsx @@ -1,6 +1,6 @@ +import {FC, ReactText, useRef, useState} from 'react'; import {FormattedMessage} from 'react-intl'; import {observer} from 'mobx-react'; -import * as React from 'react'; import { Button, @@ -22,11 +22,11 @@ import FloodActions from '../../actions/FloodActions'; import type {ClientConnectionSettingsFormType} from './connection-settings/ClientConnectionSettingsForm'; -const ClientConnectionInterruption: React.FC = () => { - const [error, setError] = React.useState(null); - const [isSubmitting, setSubmitting] = React.useState(false); - const [selection, setSelection] = React.useState('retry'); - const settingsFormRef = React.useRef(null); +const ClientConnectionInterruption: FC = observer(() => { + const [error, setError] = useState(null); + const [isSubmitting, setSubmitting] = useState(false); + const [selection, setSelection] = useState('retry'); + const settingsFormRef = useRef(null); return ( @@ -117,6 +117,6 @@ const ClientConnectionInterruption: React.FC = () => { ); -}; +}); -export default observer(ClientConnectionInterruption); +export default ClientConnectionInterruption; diff --git a/client/src/javascript/components/general/Duration.tsx b/client/src/javascript/components/general/Duration.tsx index 5791b720..54108c05 100644 --- a/client/src/javascript/components/general/Duration.tsx +++ b/client/src/javascript/components/general/Duration.tsx @@ -1,14 +1,14 @@ +import {FC, ReactNode} from 'react'; import {FormattedMessage} from 'react-intl'; -import * as React from 'react'; import formatUtil from '@shared/util/formatUtil'; interface DurationProps { - suffix?: React.ReactNode; + suffix?: ReactNode; value: number; } -const Duration: React.FC = (props: DurationProps) => { +const Duration: FC = (props: DurationProps) => { const {suffix, value} = props; if (value == null) { diff --git a/client/src/javascript/components/general/ListViewport.tsx b/client/src/javascript/components/general/ListViewport.tsx index a140958b..3945e7fd 100644 --- a/client/src/javascript/components/general/ListViewport.tsx +++ b/client/src/javascript/components/general/ListViewport.tsx @@ -1,66 +1,63 @@ +import {ComponentProps, FC, forwardRef, RefCallback, UIEvent, useEffect, useRef} from 'react'; import {FixedSizeList} from 'react-window'; import {OverlayScrollbarsComponent} from 'overlayscrollbars-react'; -import {useMediaQuery} from '@react-hook/media-query'; -import {useWindowSize} from '@react-hook/window-size'; -import * as React from 'react'; +import {useMedia, useWindowSize} from 'react-use'; import type {ListChildComponentProps} from 'react-window'; -const Overflow = React.forwardRef>( - (props: React.ComponentProps<'div'>, ref) => { - const {children, className, onScroll} = props; - const osRef = React.useRef(null); +const Overflow = forwardRef>((props: ComponentProps<'div'>, ref) => { + const {children, className, onScroll} = props; + const osRef = useRef(null); - React.useEffect(() => { - const scrollbarRef = osRef.current; - - if (scrollbarRef == null) { - return () => { - // do nothing. - }; - } - - const viewport = scrollbarRef.osInstance()?.getElements().viewport as HTMLDivElement; - - const refCallback = ref as React.RefCallback; - refCallback(viewport); - - if (onScroll) { - viewport.addEventListener('scroll', (e) => onScroll((e as unknown) as React.UIEvent), { - passive: true, - }); - } + useEffect(() => { + const scrollbarRef = osRef.current; + if (scrollbarRef == null) { return () => { - if (onScroll) { - viewport.removeEventListener('scroll', (e) => onScroll((e as unknown) as React.UIEvent)); - } + // do nothing. }; - }, [onScroll]); + } - return ( - - {children} - - ); - }, -); + const viewport = scrollbarRef.osInstance()?.getElements().viewport as HTMLDivElement; + + const refCallback = ref as RefCallback; + refCallback(viewport); + + if (onScroll) { + viewport.addEventListener('scroll', (e) => onScroll((e as unknown) as UIEvent), { + passive: true, + }); + } + + return () => { + if (onScroll) { + viewport.removeEventListener('scroll', (e) => onScroll((e as unknown) as UIEvent)); + } + }; + }, [onScroll]); + + return ( + + {children} + + ); +}); interface ListViewportProps { className: string; - itemRenderer: React.FC; + itemRenderer: FC; itemSize: number; listLength: number; - outerRef?: React.RefCallback; + outerRef?: RefCallback; } -const ListViewport = React.forwardRef((props: ListViewportProps, ref) => { +const ListViewport = forwardRef((props: ListViewportProps, ref) => { const {className, itemRenderer, itemSize, listLength, outerRef} = props; - const [windowWidth, windowHeight] = useWindowSize(); - const isDarkTheme = useMediaQuery('(prefers-color-scheme: dark)'); + const {height: windowHeight, width: windowWidth} = useWindowSize(); + const isDarkTheme = useMedia('(prefers-color-scheme: dark)'); return ( = (props: LoadingIndicatorProps) => { +const LoadingIndicator: FC = (props: LoadingIndicatorProps) => { const {inverse} = props; const classes = classnames('loading-indicator', { diff --git a/client/src/javascript/components/general/LoadingOverlay.tsx b/client/src/javascript/components/general/LoadingOverlay.tsx index a3d5e6ba..1a56dcae 100644 --- a/client/src/javascript/components/general/LoadingOverlay.tsx +++ b/client/src/javascript/components/general/LoadingOverlay.tsx @@ -1,6 +1,6 @@ import classnames from 'classnames'; +import {FC} from 'react'; import {useIntl} from 'react-intl'; -import * as React from 'react'; import Checkmark from '../icons/Checkmark'; import LoadingIndicator from './LoadingIndicator'; @@ -15,7 +15,7 @@ interface LoadingOverlayProps { dependencies?: Dependencies; } -const LoadingOverlay: React.FC = (props: LoadingOverlayProps) => { +const LoadingOverlay: FC = (props: LoadingOverlayProps) => { const {dependencies} = props; return ( diff --git a/client/src/javascript/components/general/Size.tsx b/client/src/javascript/components/general/Size.tsx index 38c8ea3f..80927a72 100644 --- a/client/src/javascript/components/general/Size.tsx +++ b/client/src/javascript/components/general/Size.tsx @@ -1,5 +1,5 @@ +import {FC} from 'react'; import {FormattedNumber, useIntl} from 'react-intl'; -import * as React from 'react'; import {compute, getTranslationString} from '../../util/size'; @@ -18,7 +18,7 @@ interface SizeProps { className?: string; } -const Size: React.FC = ({value, isSpeed, className, precision}: SizeProps) => { +const Size: FC = ({value, isSpeed, className, precision}: SizeProps) => { const computed = compute(value, precision); const intl = useIntl(); diff --git a/client/src/javascript/components/general/SortableListItem.tsx b/client/src/javascript/components/general/SortableListItem.tsx index b10720dc..a0f05ea0 100644 --- a/client/src/javascript/components/general/SortableListItem.tsx +++ b/client/src/javascript/components/general/SortableListItem.tsx @@ -1,13 +1,13 @@ import classnames from 'classnames'; import {DragElementWrapper, DragPreviewOptions, DragSource, DragSourceOptions, DropTarget} from 'react-dnd'; +import {FC, ReactNode, useEffect} from 'react'; import flow from 'lodash/flow'; import {getEmptyImage} from 'react-dnd-html5-backend'; -import * as React from 'react'; import LockIcon from '../icons/LockIcon'; interface SortableListItemProps { - children?: React.ReactNode; + children?: ReactNode; list: string; id: string; index: number; @@ -22,10 +22,10 @@ interface SortableListItemProps { connectDropTarget: DragElementWrapper; } -const SortableListItem: React.FC = (props: SortableListItemProps) => { +const SortableListItem: FC = (props: SortableListItemProps) => { const {children, isDragging, isLocked, connectDragPreview, connectDragSource, connectDropTarget} = props; - React.useEffect(() => { + useEffect(() => { connectDragPreview(getEmptyImage(), { captureDraggingState: true, }); diff --git a/client/src/javascript/components/general/WindowTitle.tsx b/client/src/javascript/components/general/WindowTitle.tsx index 8f71dc64..420ec28c 100644 --- a/client/src/javascript/components/general/WindowTitle.tsx +++ b/client/src/javascript/components/general/WindowTitle.tsx @@ -1,11 +1,11 @@ +import {FC} from 'react'; import {useIntl} from 'react-intl'; import {observer} from 'mobx-react'; -import * as React from 'react'; import {compute, getTranslationString} from '../../util/size'; import TransferDataStore from '../../stores/TransferDataStore'; -const WindowTitle: React.FC = () => { +const WindowTitle: FC = observer(() => { const {transferSummary: summary} = TransferDataStore; const intl = useIntl(); @@ -41,6 +41,6 @@ const WindowTitle: React.FC = () => { document.title = title; return null; -}; +}); -export default observer(WindowTitle); +export default WindowTitle; diff --git a/client/src/javascript/components/general/filesystem/DirectoryTree.tsx b/client/src/javascript/components/general/filesystem/DirectoryTree.tsx index 70a89fd2..a240479b 100644 --- a/client/src/javascript/components/general/filesystem/DirectoryTree.tsx +++ b/client/src/javascript/components/general/filesystem/DirectoryTree.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import {FC, ReactNode} from 'react'; import type {TorrentContentSelection, TorrentContentSelectionTree} from '@shared/types/TorrentContent'; import type {TorrentProperties} from '@shared/types/Torrent'; @@ -16,17 +16,17 @@ interface DirectoryTreeProps { onItemSelect: (selection: TorrentContentSelection) => void; } -const DirectoryTree: React.FC = (props: DirectoryTreeProps) => { +const DirectoryTree: FC = (props: DirectoryTreeProps) => { const {depth = 0, itemsTree, hash, path, onItemSelect} = props; const {files, directories} = itemsTree; const childDepth = depth + 1; - const directoryNodes: Array = + const directoryNodes: Array = directories != null ? Object.keys(directories) .sort((a, b) => a.localeCompare(b)) .map( - (directoryName, index): React.ReactNode => { + (directoryName, index): ReactNode => { const subSelectedItems = itemsTree.directories && itemsTree.directories[directoryName]; const id = `${index}${childDepth}${directoryName}`; @@ -53,7 +53,7 @@ const DirectoryTree: React.FC = (props: DirectoryTreeProps) ) : []; - const fileList: React.ReactNode = + const fileList: ReactNode = files != null && Object.keys(files).length > 0 ? ( = {}; @@ -36,7 +36,7 @@ interface CountryFlagIconProps { countryCode: string; } -const CountryFlagIcon: React.FC = ({countryCode}: CountryFlagIconProps) => { +const CountryFlagIcon: FC = ({countryCode}: CountryFlagIconProps) => { const flag = getFlag(countryCode); if (flag == null) { return null; diff --git a/client/src/javascript/components/layout/ApplicationContent.tsx b/client/src/javascript/components/layout/ApplicationContent.tsx index 9ccdf4f0..0405f962 100644 --- a/client/src/javascript/components/layout/ApplicationContent.tsx +++ b/client/src/javascript/components/layout/ApplicationContent.tsx @@ -1,10 +1,10 @@ -import * as React from 'react'; +import {FC, ReactNode} from 'react'; interface ApplicationContentProps { - children: React.ReactNode; + children: ReactNode; } -const ApplicationContent: React.FC = ({children}: ApplicationContentProps) => { +const ApplicationContent: FC = ({children}: ApplicationContentProps) => { return
{children}
; }; diff --git a/client/src/javascript/components/layout/ApplicationPanel.tsx b/client/src/javascript/components/layout/ApplicationPanel.tsx index 09d5225c..247b649e 100644 --- a/client/src/javascript/components/layout/ApplicationPanel.tsx +++ b/client/src/javascript/components/layout/ApplicationPanel.tsx @@ -1,14 +1,14 @@ import classnames from 'classnames'; -import * as React from 'react'; +import {FC, ReactNode} from 'react'; interface ApplicationContentProps { - children: React.ReactNode; + children: ReactNode; baseClassName?: string; className: string; modifier: string; } -const ApplicationContent: React.FC = (props: ApplicationContentProps) => { +const ApplicationContent: FC = (props: ApplicationContentProps) => { const {children, baseClassName, className, modifier} = props; const classes = classnames(baseClassName, { diff --git a/client/src/javascript/components/layout/ApplicationView.tsx b/client/src/javascript/components/layout/ApplicationView.tsx index 7f3e2d51..22d96abf 100644 --- a/client/src/javascript/components/layout/ApplicationView.tsx +++ b/client/src/javascript/components/layout/ApplicationView.tsx @@ -1,12 +1,12 @@ import classnames from 'classnames'; -import * as React from 'react'; +import {FC, ReactNode} from 'react'; interface ApplicationViewProps { - children: React.ReactNode; + children: ReactNode; modifier?: string; } -const ApplicationView: React.FC = (props: ApplicationViewProps) => { +const ApplicationView: FC = (props: ApplicationViewProps) => { const {children, modifier} = props; const classes = classnames('application__view', { diff --git a/client/src/javascript/components/modals/Modal.tsx b/client/src/javascript/components/modals/Modal.tsx index 7eb2e421..381cff50 100644 --- a/client/src/javascript/components/modals/Modal.tsx +++ b/client/src/javascript/components/modals/Modal.tsx @@ -1,5 +1,5 @@ import classnames from 'classnames'; -import * as React from 'react'; +import {FC, ReactNode, useState} from 'react'; import ModalActions from './ModalActions'; import ModalTabs from './ModalTabs'; @@ -8,8 +8,8 @@ import type {ModalAction} from '../../stores/UIStore'; import type {Tab} from './ModalTabs'; interface ModalProps { - heading: React.ReactNode; - content?: React.ReactNode; + heading: ReactNode; + content?: ReactNode; className?: string | null; alignment?: 'left' | 'center'; size?: 'medium' | 'large'; @@ -20,7 +20,7 @@ interface ModalProps { tabs?: Record; } -const Modal: React.FC = (props: ModalProps) => { +const Modal: FC = (props: ModalProps) => { const {alignment, size, orientation, tabsInBody, inverse, className, content, heading, tabs, actions} = props; const contentWrapperClasses = classnames( @@ -47,12 +47,12 @@ const Modal: React.FC = (props: ModalProps) => { let headerTabs; if (tabs) { - const [activeTabId, setActiveTabId] = React.useState(Object.keys(tabs)[0]); + const [activeTabId, setActiveTabId] = useState(Object.keys(tabs)[0]); const activeTab = tabs[activeTabId]; const contentClasses = classnames('modal__content', activeTab.modalContentClasses); - const ModalContentComponent = activeTab.content as React.FunctionComponent; + const ModalContentComponent = activeTab.content as FC; const modalContentData = activeTab.props; const modalTabs = ( diff --git a/client/src/javascript/components/modals/ModalActions.tsx b/client/src/javascript/components/modals/ModalActions.tsx index 2edb407f..b74f2da3 100644 --- a/client/src/javascript/components/modals/ModalActions.tsx +++ b/client/src/javascript/components/modals/ModalActions.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import {FC} from 'react'; import {Button, Checkbox} from '../../ui'; import UIActions from '../../actions/UIActions'; @@ -9,7 +9,7 @@ interface ModalActionsProps { actions: Array; } -const ModalActions: React.FC = (props: ModalActionsProps) => { +const ModalActions: FC = (props: ModalActionsProps) => { const {actions} = props; const buttons = actions.map((action, index) => { diff --git a/client/src/javascript/components/modals/ModalFormSectionHeader.tsx b/client/src/javascript/components/modals/ModalFormSectionHeader.tsx index 75208eed..d3bb11b8 100644 --- a/client/src/javascript/components/modals/ModalFormSectionHeader.tsx +++ b/client/src/javascript/components/modals/ModalFormSectionHeader.tsx @@ -1,10 +1,10 @@ -import * as React from 'react'; +import {FC, ReactNode} from 'react'; interface ModalFormSectionHeaderProps { - children: React.ReactNode; + children: ReactNode; } -const ModalFormSectionHeader: React.FC = ({children}: ModalFormSectionHeaderProps) => { +const ModalFormSectionHeader: FC = ({children}: ModalFormSectionHeaderProps) => { return

{children}

; }; diff --git a/client/src/javascript/components/modals/ModalTabs.tsx b/client/src/javascript/components/modals/ModalTabs.tsx index c6860d6c..310d8987 100644 --- a/client/src/javascript/components/modals/ModalTabs.tsx +++ b/client/src/javascript/components/modals/ModalTabs.tsx @@ -1,10 +1,10 @@ import classnames from 'classnames'; -import * as React from 'react'; +import {FC, ReactNode, ReactNodeArray} from 'react'; export interface Tab { id?: string; - label: React.ReactNode; - content: React.ReactNode | React.FunctionComponent; + label: ReactNode; + content: ReactNode | FC; props?: Record; modalContentClasses?: string; } @@ -15,10 +15,10 @@ interface ModalTabsProps { onTabChange: (tab: Tab) => void; } -const ModalTabs: React.FC = (props: ModalTabsProps) => { +const ModalTabs: FC = (props: ModalTabsProps) => { const {activeTabId, tabs = {}, onTabChange} = props; - const tabNodes: React.ReactNodeArray = Object.keys(tabs).map((tabId) => { + const tabNodes: ReactNodeArray = Object.keys(tabs).map((tabId) => { const currentTab = tabs[tabId]; currentTab.id = tabId; diff --git a/client/src/javascript/components/modals/Modals.tsx b/client/src/javascript/components/modals/Modals.tsx index 62816a4e..7a1d62df 100644 --- a/client/src/javascript/components/modals/Modals.tsx +++ b/client/src/javascript/components/modals/Modals.tsx @@ -1,6 +1,7 @@ import {CSSTransition, TransitionGroup} from 'react-transition-group'; +import {FC} from 'react'; import {observer} from 'mobx-react'; -import * as React from 'react'; +import {useKeyPressEvent} from 'react-use'; import AddTorrentsModal from './add-torrents-modal/AddTorrentsModal'; import ConfirmModal from './confirm-modal/ConfirmModal'; @@ -41,47 +42,29 @@ const createModal = (id: Modal['id']): React.ReactNode => { } }; -const dismissModal = () => { - UIActions.dismissModal(); -}; +const Modals: FC = observer(() => { + const {id} = UIStore.activeModal || {}; -@observer -class Modals extends React.Component { - componentDidMount() { - window.addEventListener('keydown', this.handleKeyPress); + useKeyPressEvent('Escape', () => UIActions.dismissModal()); + + let modal; + if (id != null) { + modal = ( + +
+
{ + UIActions.dismissModal(); + }} + /> + {createModal(id)} +
+ + ); } - componentWillUnmount() { - window.removeEventListener('keydown', this.handleKeyPress); - } - - handleKeyPress = (event: KeyboardEvent) => { - if (UIStore.activeModal != null && event.key === 'Escape') { - dismissModal(); - } - }; - - handleOverlayClick = () => { - dismissModal(); - }; - - render() { - const id = UIStore.activeModal?.id; - - let modal; - if (id != null) { - modal = ( - -
-
- {createModal(id)} -
- - ); - } - - return {modal}; - } -} + return {modal}; +}); export default Modals; diff --git a/client/src/javascript/components/modals/confirm-modal/ConfirmModal.tsx b/client/src/javascript/components/modals/confirm-modal/ConfirmModal.tsx index ddbd378f..852ae9b9 100644 --- a/client/src/javascript/components/modals/confirm-modal/ConfirmModal.tsx +++ b/client/src/javascript/components/modals/confirm-modal/ConfirmModal.tsx @@ -1,10 +1,10 @@ +import {FC} from 'react'; import {observer} from 'mobx-react'; -import * as React from 'react'; import Modal from '../Modal'; import UIStore from '../../../stores/UIStore'; -const ConfirmModal: React.FC = () => { +const ConfirmModal: FC = observer(() => { if (UIStore.activeModal?.id !== 'confirm') { return null; } @@ -19,6 +19,6 @@ const ConfirmModal: React.FC = () => { heading={heading} /> ); -}; +}); -export default observer(ConfirmModal); +export default ConfirmModal; diff --git a/client/src/javascript/components/modals/move-torrents-modal/MoveTorrentsModal.tsx b/client/src/javascript/components/modals/move-torrents-modal/MoveTorrentsModal.tsx index c3800f7b..25ea2fce 100644 --- a/client/src/javascript/components/modals/move-torrents-modal/MoveTorrentsModal.tsx +++ b/client/src/javascript/components/modals/move-torrents-modal/MoveTorrentsModal.tsx @@ -1,23 +1,13 @@ -import {injectIntl, WrappedComponentProps} from 'react-intl'; -import * as React from 'react'; - -import type {MoveTorrentsOptions} from '@shared/types/api/torrents'; +import {FC, useState} from 'react'; +import {useIntl} from 'react-intl'; import FilesystemBrowserTextbox from '../../general/filesystem/FilesystemBrowserTextbox'; import {Form} from '../../../ui'; import Modal from '../Modal'; -import ModalActions from '../ModalActions'; import TorrentActions from '../../../actions/TorrentActions'; import TorrentStore from '../../../stores/TorrentStore'; import UIStore from '../../../stores/UIStore'; -import type {ModalAction} from '../../../stores/UIStore'; - -interface MoveTorrentsStates { - isSettingDownloadPath: boolean; - originalSource?: string; -} - const getSuggestedPath = (sources: Array): string | undefined => { const commonPath = sources[0]; @@ -38,101 +28,81 @@ const getSuggestedPath = (sources: Array): string | undefined => { return undefined; }; -class MoveTorrents extends React.Component { - constructor(props: WrappedComponentProps) { - super(props); +const MoveTorrents: FC = () => { + const intl = useIntl(); + const [isSettingDownloadPath, setIsSettingDownloadPath] = useState(false); - this.state = { - isSettingDownloadPath: false, - originalSource: getSuggestedPath( - TorrentStore.selectedTorrents.map((hash: string) => TorrentStore.torrents[hash].directory), - ), - }; - } + return ( + +
{ + const hashes = TorrentStore.selectedTorrents; + if (hashes.length > 0) { + setIsSettingDownloadPath(true); + TorrentActions.moveTorrents({ + hashes, + destination: formData.destination as string, + isBasePath: formData.isBasePath as boolean, + moveFiles: formData.moveFiles as boolean, + isCheckHash: formData.isCheckHash as boolean, + }).then(() => { + UIStore.dismissModal(); + setIsSettingDownloadPath(false); + }); + } + }}> + TorrentStore.torrents[hash].directory), + )} + showBasePathToggle + /> + +
+ } + actions={[ + { + checked: true, + content: intl.formatMessage({ + id: 'torrents.move.data.label', + }), + id: 'moveFiles', + type: 'checkbox', + }, + { + checked: false, + content: intl.formatMessage({ + id: 'torrents.move.check_hash.label', + }), + id: 'isCheckHash', + type: 'checkbox', + }, + { + content: intl.formatMessage({ + id: 'button.cancel', + }), + triggerDismiss: true, + type: 'tertiary', + }, + { + content: intl.formatMessage({ + id: 'torrents.move.button.set.location', + }), + isLoading: isSettingDownloadPath, + submit: true, + type: 'primary', + }, + ]} + /> + ); +}; - getActions(): Array { - return [ - { - checked: true, - content: this.props.intl.formatMessage({ - id: 'torrents.move.data.label', - }), - id: 'moveFiles', - type: 'checkbox', - }, - { - checked: false, - content: this.props.intl.formatMessage({ - id: 'torrents.move.check_hash.label', - }), - id: 'isCheckHash', - type: 'checkbox', - }, - { - content: this.props.intl.formatMessage({ - id: 'button.cancel', - }), - triggerDismiss: true, - type: 'tertiary', - }, - { - content: this.props.intl.formatMessage({ - id: 'torrents.move.button.set.location', - }), - isLoading: this.state.isSettingDownloadPath, - submit: true, - type: 'primary', - }, - ]; - } - - getContent(): React.ReactNode { - return ( -
-
{ - return this.handleFormSubmit((formData as unknown) as MoveTorrentsOptions); - }}> - - - -
- ); - } - - handleFormSubmit = (formData: MoveTorrentsOptions) => { - const hashes = TorrentStore.selectedTorrents; - if (hashes.length > 0) { - this.setState({isSettingDownloadPath: true}); - TorrentActions.moveTorrents({ - hashes, - destination: formData.destination, - isBasePath: formData.isBasePath, - moveFiles: formData.moveFiles, - isCheckHash: formData.isCheckHash, - }).then(() => { - UIStore.dismissModal(); - this.setState({isSettingDownloadPath: false}); - }); - } - }; - - render() { - return ( - - ); - } -} - -export default injectIntl(MoveTorrents); +export default MoveTorrents; diff --git a/client/src/javascript/components/modals/set-tags-modal/SetTagsModal.tsx b/client/src/javascript/components/modals/set-tags-modal/SetTagsModal.tsx index 69478720..687c1c5f 100644 --- a/client/src/javascript/components/modals/set-tags-modal/SetTagsModal.tsx +++ b/client/src/javascript/components/modals/set-tags-modal/SetTagsModal.tsx @@ -1,5 +1,5 @@ -import {Component} from 'react'; -import {injectIntl, WrappedComponentProps} from 'react-intl'; +import {FC, useRef, useState} from 'react'; +import {useIntl} from 'react-intl'; import {Form, FormRow} from '../../../ui'; import Modal from '../Modal'; @@ -7,91 +7,64 @@ import TagSelect from '../../general/form-elements/TagSelect'; import TorrentActions from '../../../actions/TorrentActions'; import TorrentStore from '../../../stores/TorrentStore'; -import type {ModalAction} from '../../../stores/UIStore'; +const SetTagsModal: FC = () => { + const formRef = useRef
(null); + const intl = useIntl(); + const [isSettingTags, setIsSettingTags] = useState(false); -interface SetTagsModalStates { - isSettingTags: boolean; -} + return ( + + + + TorrentStore.torrents[hash].tags)[0] + .slice()} + id="tags" + placeholder={intl.formatMessage({ + id: 'torrents.set.tags.enter.tags', + })} + /> + + +
+ } + actions={[ + { + content: intl.formatMessage({ + id: 'button.cancel', + }), + clickHandler: null, + triggerDismiss: true, + type: 'tertiary', + }, + { + content: intl.formatMessage({ + id: 'torrents.set.tags.button.set', + }), + clickHandler: () => { + if (formRef.current == null) { + return; + } -class SetTagsModal extends Component { - formRef: Form | null = null; + const formData = formRef.current.getFormData() as {tags: string}; + const tags = formData.tags ? formData.tags.split(',') : []; - constructor(props: WrappedComponentProps) { - super(props); - this.state = { - isSettingTags: false, - }; - } + setIsSettingTags(true); + TorrentActions.setTags({hashes: TorrentStore.selectedTorrents, tags}); + }, + isLoading: isSettingTags, + triggerDismiss: false, + type: 'primary', + }, + ]} + /> + ); +}; - getActions(): Array { - const primaryButtonText = this.props.intl.formatMessage({ - id: 'torrents.set.tags.button.set', - }); - - return [ - { - clickHandler: null, - content: this.props.intl.formatMessage({ - id: 'button.cancel', - }), - triggerDismiss: true, - type: 'tertiary', - }, - { - clickHandler: this.handleSetTagsClick, - content: primaryButtonText, - isLoading: this.state.isSettingTags, - triggerDismiss: false, - type: 'primary', - }, - ]; - } - - getContent() { - return ( -
-
{ - this.formRef = ref; - }}> - - TorrentStore.torrents[hash].tags)[0] - .slice()} - id="tags" - placeholder={this.props.intl.formatMessage({ - id: 'torrents.set.tags.enter.tags', - })} - /> - -
-
- ); - } - - handleSetTagsClick = () => { - if (this.formRef == null) { - return; - } - - const formData = this.formRef.getFormData() as {tags: string}; - const tags = formData.tags ? formData.tags.split(',') : []; - - this.setState({isSettingTags: true}, () => TorrentActions.setTags({hashes: TorrentStore.selectedTorrents, tags})); - }; - - render() { - return ( - - ); - } -} - -export default injectIntl(SetTagsModal); +export default SetTagsModal; diff --git a/client/src/javascript/components/modals/set-trackers-modal/SetTrackersModal.tsx b/client/src/javascript/components/modals/set-trackers-modal/SetTrackersModal.tsx index 12cbbe52..d164cc4e 100644 --- a/client/src/javascript/components/modals/set-trackers-modal/SetTrackersModal.tsx +++ b/client/src/javascript/components/modals/set-trackers-modal/SetTrackersModal.tsx @@ -1,7 +1,5 @@ -import {Component} from 'react'; -import {injectIntl, WrappedComponentProps} from 'react-intl'; -import {observable, runInAction} from 'mobx'; -import {observer} from 'mobx-react'; +import {FC, useEffect, useRef, useState} from 'react'; +import {useIntl} from 'react-intl'; import {TorrentTrackerType} from '@shared/types/TorrentTracker'; @@ -12,119 +10,96 @@ import TorrentActions from '../../../actions/TorrentActions'; import TorrentStore from '../../../stores/TorrentStore'; import UIStore from '../../../stores/UIStore'; -import type {ModalAction} from '../../../stores/UIStore'; - -interface SetTrackersModalStates { - isLoadingTrackers: boolean; - isSettingTrackers: boolean; -} - -@observer -class SetTrackersModal extends Component { - trackerURLs = observable.array([]); - formRef: Form | null = null; - - constructor(props: WrappedComponentProps) { - super(props); +const SetTrackersModal: FC = () => { + const formRef = useRef
(null); + const intl = useIntl(); + const [isSettingTrackers, setIsSettingTrackers] = useState(false); + const [trackerState, setTrackerState] = useState<{isLoadingTrackers: boolean; trackerURLs: Array}>({ + isLoadingTrackers: false, + trackerURLs: [], + }); + useEffect(() => { TorrentActions.fetchTorrentTrackers(TorrentStore.selectedTorrents[0]).then((trackers) => { if (trackers != null) { - runInAction(() => { - this.trackerURLs.replace( - trackers.filter((tracker) => tracker.type !== TorrentTrackerType.DHT).map((tracker) => tracker.url), - ); - this.setState({isLoadingTrackers: false}); + setTrackerState({ + isLoadingTrackers: false, + trackerURLs: trackers + .filter((tracker) => tracker.type !== TorrentTrackerType.DHT) + .map((tracker) => tracker.url), }); } }); + }, []); - this.state = { - isSettingTrackers: false, - isLoadingTrackers: true, - }; - } - - getActions(): Array { - const primaryButtonText = this.props.intl.formatMessage({ - id: 'torrents.set.trackers.button.set', - }); - - return [ - { - clickHandler: null, - content: this.props.intl.formatMessage({ - id: 'button.cancel', - }), - triggerDismiss: true, - type: 'tertiary', - }, - { - clickHandler: this.handleSetTrackersClick, - content: primaryButtonText, - isLoading: this.state.isSettingTrackers || this.state.isLoadingTrackers, - triggerDismiss: false, - type: 'primary', - }, - ]; - } - - handleSetTrackersClick = (): void => { - if (this.formRef == null || this.state.isSettingTrackers || this.state.isLoadingTrackers) { - return; - } - - this.setState({isSettingTrackers: true}); - - const formData = this.formRef.getFormData() as Record; - const trackers = getTextArray(formData, 'trackers').filter((tracker) => tracker !== ''); - - TorrentActions.setTrackers({hashes: TorrentStore.selectedTorrents, trackers}).then(() => { - this.setState({isSettingTrackers: false}); - UIStore.dismissModal(); - }); - }; - - render() { - return ( - - { - this.formRef = ref; - }}> - {this.state.isLoadingTrackers ? ( - - - - ) : ( - + + {trackerState.isLoadingTrackers ? ( + + ({id: index, value: url})) - } /> - )} - - - } - heading={this.props.intl.formatMessage({ - id: 'torrents.set.trackers.heading', - })} - /> - ); - } -} + + ) : ( + ({id: index, value: url})) + } + /> + )} + + + } + actions={[ + { + clickHandler: null, + content: intl.formatMessage({ + id: 'button.cancel', + }), + triggerDismiss: true, + type: 'tertiary', + }, + { + clickHandler: () => { + if (formRef.current == null || isSettingTrackers || trackerState.isLoadingTrackers) { + return; + } -export default injectIntl(SetTrackersModal); + setIsSettingTrackers(true); + + const formData = formRef.current.getFormData() as Record; + const trackers = getTextArray(formData, 'trackers').filter((tracker) => tracker !== ''); + + TorrentActions.setTrackers({hashes: TorrentStore.selectedTorrents, trackers}).then(() => { + setIsSettingTrackers(false); + UIStore.dismissModal(); + }); + }, + content: intl.formatMessage({ + id: 'torrents.set.trackers.button.set', + }), + isLoading: isSettingTrackers || trackerState.isLoadingTrackers, + triggerDismiss: false, + type: 'primary', + }, + ]} + /> + ); +}; + +export default SetTrackersModal; diff --git a/client/src/javascript/components/modals/settings-modal/DiskUsageTab.tsx b/client/src/javascript/components/modals/settings-modal/DiskUsageTab.tsx index 7de15fc1..86df535e 100644 --- a/client/src/javascript/components/modals/settings-modal/DiskUsageTab.tsx +++ b/client/src/javascript/components/modals/settings-modal/DiskUsageTab.tsx @@ -1,5 +1,5 @@ +import {FC} from 'react'; import {FormattedMessage} from 'react-intl'; -import * as React from 'react'; import type {FloodSettings} from '@shared/types/FloodSettings'; @@ -11,7 +11,7 @@ interface DiskUsageTabProps { onSettingsChange: (changedSettings: Partial) => void; } -const DiskUsageTab: React.FC = (props: DiskUsageTabProps) => { +const DiskUsageTab: FC = (props: DiskUsageTabProps) => { return (
diff --git a/client/src/javascript/components/modals/settings-modal/SettingsModal.tsx b/client/src/javascript/components/modals/settings-modal/SettingsModal.tsx index d01e1b7f..91bb1276 100644 --- a/client/src/javascript/components/modals/settings-modal/SettingsModal.tsx +++ b/client/src/javascript/components/modals/settings-modal/SettingsModal.tsx @@ -1,6 +1,6 @@ import {FC, useState} from 'react'; import {useIntl} from 'react-intl'; -import {useMediaQuery} from '@react-hook/media-query'; +import {useMedia} from 'react-use'; import type {ClientSettings} from '@shared/types/ClientSettings'; import type {FloodSettings} from '@shared/types/FloodSettings'; @@ -20,7 +20,7 @@ import UIStore from '../../../stores/UIStore'; const SettingsModal: FC = () => { const intl = useIntl(); - const isSmallScreen = useMediaQuery('(max-width: 720px)'); + const isSmallScreen = useMedia('(max-width: 720px)'); const [changedClientSettings, setChangedClientSettings] = useState>({}); const [changedFloodSettings, setChangedFloodSettings] = useState>({}); diff --git a/client/src/javascript/components/modals/torrent-details-modal/TorrentDetailsModal.tsx b/client/src/javascript/components/modals/torrent-details-modal/TorrentDetailsModal.tsx index 72a9e1fb..93512b53 100644 --- a/client/src/javascript/components/modals/torrent-details-modal/TorrentDetailsModal.tsx +++ b/client/src/javascript/components/modals/torrent-details-modal/TorrentDetailsModal.tsx @@ -1,6 +1,6 @@ +import {FC} from 'react'; import {useIntl} from 'react-intl'; -import {useMediaQuery} from '@react-hook/media-query'; -import * as React from 'react'; +import {useMedia} from 'react-use'; import Modal from '../Modal'; import TorrentMediainfo from './TorrentMediainfo'; @@ -10,9 +10,9 @@ import TorrentHeading from './TorrentHeading'; import TorrentPeers from './TorrentPeers'; import TorrentTrackers from './TorrentTrackers'; -const TorrentDetailsModal: React.FC = () => { +const TorrentDetailsModal: FC = () => { const intl = useIntl(); - const isSmallScreen = useMediaQuery('(max-width: 720px)'); + const isSmallScreen = useMedia('(max-width: 720px)'); const tabs = { 'torrent-details': { diff --git a/client/src/javascript/components/modals/torrent-details-modal/TorrentGeneralInfo.tsx b/client/src/javascript/components/modals/torrent-details-modal/TorrentGeneralInfo.tsx index 699cad9d..1be9df6a 100644 --- a/client/src/javascript/components/modals/torrent-details-modal/TorrentGeneralInfo.tsx +++ b/client/src/javascript/components/modals/torrent-details-modal/TorrentGeneralInfo.tsx @@ -1,6 +1,6 @@ +import {FC} from 'react'; import {FormattedMessage, FormattedNumber, useIntl} from 'react-intl'; import {observer} from 'mobx-react'; -import * as React from 'react'; import type {TorrentProperties} from '@shared/types/Torrent'; @@ -16,7 +16,9 @@ const getTags = (tags: TorrentProperties['tags']) => { )); }; -const TorrentGeneralInfo: React.FC = () => { +const TorrentGeneralInfo: FC = observer(() => { + const intl = useIntl(); + if (UIStore.activeModal?.id !== 'torrent-details') { return null; } @@ -26,8 +28,6 @@ const TorrentGeneralInfo: React.FC = () => { return null; } - const intl = useIntl(); - let dateAdded = null; if (torrent.dateAdded) { dateAdded = new Date(torrent.dateAdded * 1000); @@ -189,6 +189,6 @@ const TorrentGeneralInfo: React.FC = () => { ); -}; +}); -export default observer(TorrentGeneralInfo); +export default TorrentGeneralInfo; diff --git a/client/src/javascript/components/modals/torrent-details-modal/TorrentHeading.tsx b/client/src/javascript/components/modals/torrent-details-modal/TorrentHeading.tsx index bd7d2391..7de91acc 100644 --- a/client/src/javascript/components/modals/torrent-details-modal/TorrentHeading.tsx +++ b/client/src/javascript/components/modals/torrent-details-modal/TorrentHeading.tsx @@ -1,11 +1,8 @@ -import {FormattedMessage, FormattedNumber} from 'react-intl'; import classnames from 'classnames'; -import {Component} from 'react'; -import {observable} from 'mobx'; +import {FC, useEffect, useState} from 'react'; +import {FormattedMessage, FormattedNumber} from 'react-intl'; import {observer} from 'mobx-react'; -import type {TorrentProperties} from '@shared/types/Torrent'; - import ClockIcon from '../../icons/ClockIcon'; import DownloadThickIcon from '../../icons/DownloadThickIcon'; import Duration from '../../general/Duration'; @@ -22,106 +19,100 @@ import TorrentStore from '../../../stores/TorrentStore'; import UploadThickIcon from '../../icons/UploadThickIcon'; import UIStore from '../../../stores/UIStore'; -const getCurrentStatus = (statuses: TorrentProperties['status']) => { - if (statuses.includes('stopped')) { - return 'stop'; +const TorrentHeading: FC = observer(() => { + const torrent = + UIStore.activeModal?.id === 'torrent-details' ? TorrentStore.torrents[UIStore.activeModal.hash] : undefined; + const [torrentStatus, setTorrentStatus] = useState<'start' | 'stop'>('stop'); + + useEffect(() => { + if (torrent?.status.includes('stopped')) { + setTorrentStatus('stop'); + } else { + setTorrentStatus('start'); + } + }, []); + + if (torrent == null) { + return null; } - return 'start'; -}; -@observer -class TorrentHeading extends Component { - @observable torrentStatus: 'start' | 'stop' = 'stop'; + const torrentClasses = torrentStatusClasses( + {status: torrent.status, upRate: torrent.upRate, downRate: torrent.downRate}, + 'torrent-details__header', + ); + const torrentStatusIcon = torrentStatusIcons(torrent.status); - render() { - if (UIStore.activeModal?.id !== 'torrent-details') { - return null; - } - - const torrent = TorrentStore.torrents[UIStore?.activeModal?.hash]; - if (torrent == null) { - return null; - } - - const torrentClasses = torrentStatusClasses( - {status: torrent.status, upRate: torrent.upRate, downRate: torrent.downRate}, - 'torrent-details__header', - ); - const torrentStatusIcon = torrentStatusIcons(torrent.status); - this.torrentStatus = getCurrentStatus(torrent.status); - - return ( -
-

{torrent.name}

-
-
    -
  • - - -  —  - -
  • -
  • - - -  —  - -
  • -
  • - - -
  • -
  • - - -
  • -
-
    -
  • - { - TorrentActions.setPriority({hashes: [`${hash}`], priority: level}); - }} - /> -
  • -
  • { - this.torrentStatus = 'start'; - TorrentActions.startTorrents({ - hashes: [torrent.hash], - }); - }}> - - -
  • -
  • { - this.torrentStatus = 'stop'; - TorrentActions.stopTorrents({ - hashes: [torrent.hash], - }); - }}> - - -
  • -
-
- + return ( +
+

{torrent.name}

+
+
    +
  • + + +  —  + +
  • +
  • + + +  —  + +
  • +
  • + + +
  • +
  • + + +
  • +
+
    +
  • + { + TorrentActions.setPriority({hashes: [`${hash}`], priority: level}); + }} + /> +
  • +
  • { + setTorrentStatus('start'); + TorrentActions.startTorrents({ + hashes: [torrent.hash], + }); + }}> + + +
  • +
  • { + setTorrentStatus('stop'); + TorrentActions.stopTorrents({ + hashes: [torrent.hash], + }); + }}> + + +
  • +
- ); - } -} + +
+ ); +}); export default TorrentHeading; diff --git a/client/src/javascript/components/modals/torrent-details-modal/TorrentPeers.tsx b/client/src/javascript/components/modals/torrent-details-modal/TorrentPeers.tsx index 30efc001..72e3f436 100644 --- a/client/src/javascript/components/modals/torrent-details-modal/TorrentPeers.tsx +++ b/client/src/javascript/components/modals/torrent-details-modal/TorrentPeers.tsx @@ -1,7 +1,6 @@ +import {FC, Suspense, useEffect, useState} from 'react'; import {FormattedMessage} from 'react-intl'; -import {observable, runInAction} from 'mobx'; -import {observer} from 'mobx-react'; -import {Component, Suspense} from 'react'; +import {useInterval} from 'react-use'; import type {TorrentPeer} from '@shared/types/TorrentPeer'; @@ -15,86 +14,76 @@ import SpinnerIcon from '../../icons/SpinnerIcon'; import TorrentActions from '../../../actions/TorrentActions'; import UIStore from '../../../stores/UIStore'; -@observer -class TorrentPeers extends Component { - peers = observable.array([]); - polling = setInterval(() => this.fetchPeers(), ConfigStore.pollInterval); +const TorrentPeers: FC = () => { + const [peers, setPeers] = useState>([]); + const [pollingDelay, setPollingDelay] = useState(null); - constructor(props: unknown) { - super(props); - - this.fetchPeers(); - } - - componentWillUnmount() { - clearInterval(this.polling); - } - - fetchPeers = () => { + const fetchPeers = () => { + setPollingDelay(null); if (UIStore.activeModal?.id === 'torrent-details') { - TorrentActions.fetchTorrentPeers(UIStore.activeModal?.hash).then((peers) => { - if (peers != null) { - runInAction(() => { - this.peers.replace(peers); - }); + TorrentActions.fetchTorrentPeers(UIStore.activeModal?.hash).then((data) => { + if (data != null) { + setPeers(data); } }); } + setPollingDelay(ConfigStore.pollInterval); }; - render() { - const peerList = this.peers.map((peer) => { - const {country: countryCode} = peer; - const encryptedIcon = peer.isEncrypted ? : null; - const incomingIcon = peer.isIncoming ? : null; + useEffect(() => fetchPeers(), []); + useInterval(() => fetchPeers(), pollingDelay); - return ( - - - - }> - - - {countryCode} - - {peer.address} - - - - - - - - {`${peer.completedPercent}%`} - {peer.clientVersion} - {encryptedIcon} - {incomingIcon} - - ); - }); + return ( +
+ + + + + + + + + + + + + + {peers.map((peer) => { + const {country: countryCode} = peer; + const encryptedIcon = peer.isEncrypted ? : null; + const incomingIcon = peer.isIncoming ? : null; - return ( -
-
+ + {peers.length} + DLUL%ClientEncIn
- - - - - - - - - - - - {peerList} -
- - {this.peers.length} - DLUL%ClientEncIn
-
- ); - } -} + return ( + + + + }> + + + {countryCode} + + {peer.address} + + + + + + + + {`${peer.completedPercent}%`} + {peer.clientVersion} + {encryptedIcon} + {incomingIcon} + + ); + })} + + +
+ ); +}; export default TorrentPeers; diff --git a/client/src/javascript/components/modals/torrent-details-modal/TorrentTrackers.tsx b/client/src/javascript/components/modals/torrent-details-modal/TorrentTrackers.tsx index a271abaa..fd60bca3 100644 --- a/client/src/javascript/components/modals/torrent-details-modal/TorrentTrackers.tsx +++ b/client/src/javascript/components/modals/torrent-details-modal/TorrentTrackers.tsx @@ -1,7 +1,5 @@ -import {Component} from 'react'; +import {FC, useEffect, useState} from 'react'; import {FormattedMessage} from 'react-intl'; -import {observable, runInAction} from 'mobx'; -import {observer} from 'mobx-react'; import type {TorrentTracker} from '@shared/types/TorrentTracker'; @@ -9,54 +7,47 @@ import Badge from '../../general/Badge'; import TorrentActions from '../../../actions/TorrentActions'; import UIStore from '../../../stores/UIStore'; -@observer -class TorrentTrackers extends Component { - trackers = observable.array([]); +const TorrentTrackers: FC = () => { + const [trackers, setTrackers] = useState>([]); - constructor(props: unknown) { - super(props); + const trackerCount = trackers.length; + const trackerTypes = ['http', 'udp', 'dht']; + const trackerDetails = trackers.map((tracker) => ( + + {tracker.url} + {trackerTypes[tracker.type - 1]} + + )); + + useEffect(() => { if (UIStore.activeModal?.id === 'torrent-details') { - TorrentActions.fetchTorrentTrackers(UIStore.activeModal?.hash).then((trackers) => { - if (trackers != null) { - runInAction(() => { - this.trackers.replace(trackers); - }); + TorrentActions.fetchTorrentTrackers(UIStore.activeModal?.hash).then((data) => { + if (data != null) { + setTrackers(data); } }); } - } + }, []); - render() { - const trackerCount = this.trackers.length; - const trackerTypes = ['http', 'udp', 'dht']; - - const trackerDetails = this.trackers.map((tracker) => ( - - {tracker.url} - {trackerTypes[tracker.type - 1]} - - )); - - return ( -
- - - - - - - - {trackerDetails} -
- - {trackerCount} - - -
-
- ); - } -} + return ( +
+ + + + + + + + {trackerDetails} +
+ + {trackerCount} + + +
+
+ ); +}; export default TorrentTrackers; diff --git a/client/src/javascript/components/sidebar/DiskUsage.tsx b/client/src/javascript/components/sidebar/DiskUsage.tsx index 8164dd44..517b658f 100644 --- a/client/src/javascript/components/sidebar/DiskUsage.tsx +++ b/client/src/javascript/components/sidebar/DiskUsage.tsx @@ -1,6 +1,6 @@ +import {FC, ReactNode, ReactNodeArray} from 'react'; import {FormattedMessage} from 'react-intl'; import {observer} from 'mobx-react'; -import * as React from 'react'; import type {Disk} from '@shared/types/DiskUsage'; @@ -11,11 +11,11 @@ import ProgressBar from '../general/ProgressBar'; import SettingStore from '../../stores/SettingStore'; interface DiskUsageTooltipItemProps { - label: React.ReactNode; + label: ReactNode; value: number; } -const DiskUsageTooltipItem: React.FC = ({label, value}: DiskUsageTooltipItemProps) => { +const DiskUsageTooltipItem: FC = ({label, value}: DiskUsageTooltipItemProps) => { return (
  • @@ -24,7 +24,7 @@ const DiskUsageTooltipItem: React.FC = ({label, value ); }; -const DiskUsage: React.FC = () => { +const DiskUsage: FC = observer(() => { const {disks} = DiskUsageStore; const {mountPoints} = SettingStore.floodSettings; @@ -39,7 +39,7 @@ const DiskUsage: React.FC = () => { }; }, {}); - const diskNodes: React.ReactNodeArray = mountPoints + const diskNodes: ReactNodeArray = mountPoints .filter((target) => target in diskMap) .map((target) => diskMap[target]) .map((d) => { @@ -77,6 +77,6 @@ const DiskUsage: React.FC = () => { {diskNodes} ); -}; +}); -export default observer(DiskUsage); +export default DiskUsage; diff --git a/client/src/javascript/components/sidebar/FeedsButton.tsx b/client/src/javascript/components/sidebar/FeedsButton.tsx index d357117a..365983e5 100644 --- a/client/src/javascript/components/sidebar/FeedsButton.tsx +++ b/client/src/javascript/components/sidebar/FeedsButton.tsx @@ -1,5 +1,5 @@ import {defineMessages, useIntl} from 'react-intl'; -import * as React from 'react'; +import {FC, useRef} from 'react'; import FeedIcon from '../icons/FeedIcon'; import Tooltip from '../general/Tooltip'; @@ -11,10 +11,10 @@ const MESSAGES = defineMessages({ }, }); -const FeedsButton: React.FC = () => { +const FeedsButton: FC = () => { const intl = useIntl(); const label = intl.formatMessage(MESSAGES.feeds); - const tooltipRef = React.useRef(null); + const tooltipRef = useRef(null); return ( { +const LogoutButton: FC = () => { + const intl = useIntl(); + if (ConfigStore.authMethod === 'none') { return null; } - const intl = useIntl(); - return ( { +const SettingsButton: FC = () => { const intl = useIntl(); const label = intl.formatMessage(MESSAGES.settings); - const tooltipRef = React.useRef(null); + const tooltipRef = useRef(null); return ( { +const Sidebar: FC = () => { return ( = ({children}: SidebarActionsProps) => { +const SidebarActions: FC = ({children}: SidebarActionsProps) => { return
    {children}
    ; }; diff --git a/client/src/javascript/components/sidebar/SidebarFilter.tsx b/client/src/javascript/components/sidebar/SidebarFilter.tsx index a7a69e1d..efe5945d 100644 --- a/client/src/javascript/components/sidebar/SidebarFilter.tsx +++ b/client/src/javascript/components/sidebar/SidebarFilter.tsx @@ -1,6 +1,6 @@ import classnames from 'classnames'; +import {FC} from 'react'; import {useIntl} from 'react-intl'; -import * as React from 'react'; import Badge from '../general/Badge'; @@ -13,7 +13,7 @@ interface SidebarFilterProps { handleClick: (slug: string) => void; } -const SidebarFilter: React.FC = (props: SidebarFilterProps) => { +const SidebarFilter: FC = (props: SidebarFilterProps) => { const {isActive, count, slug, icon, handleClick} = props; const intl = useIntl(); diff --git a/client/src/javascript/components/sidebar/SidebarItem.tsx b/client/src/javascript/components/sidebar/SidebarItem.tsx index e77e09c9..35af9379 100644 --- a/client/src/javascript/components/sidebar/SidebarItem.tsx +++ b/client/src/javascript/components/sidebar/SidebarItem.tsx @@ -1,13 +1,13 @@ import classnames from 'classnames'; -import * as React from 'react'; +import {FC, ReactNode} from 'react'; interface SidebarItemProps { - children: React.ReactNode; + children: ReactNode; baseClassName?: string; modifier: string; } -const SidebarItem: React.FC = ({children, baseClassName, modifier}: SidebarItemProps) => { +const SidebarItem: FC = ({children, baseClassName, modifier}: SidebarItemProps) => { const classes = classnames(baseClassName, { [`${baseClassName}--${modifier}`]: modifier, }); diff --git a/client/src/javascript/components/sidebar/StatusFilters.tsx b/client/src/javascript/components/sidebar/StatusFilters.tsx index 7865ec72..3eaf10ce 100644 --- a/client/src/javascript/components/sidebar/StatusFilters.tsx +++ b/client/src/javascript/components/sidebar/StatusFilters.tsx @@ -1,6 +1,6 @@ +import {FC} from 'react'; import {FormattedMessage, useIntl} from 'react-intl'; import {observer} from 'mobx-react'; -import * as React from 'react'; import type {TorrentStatus} from '@shared/constants/torrentStatusMap'; @@ -17,7 +17,7 @@ import TorrentFilterStore from '../../stores/TorrentFilterStore'; import UIActions from '../../actions/UIActions'; import UploadSmall from '../icons/UploadSmall'; -const StatusFilters: React.FC = () => { +const StatusFilters: FC = observer(() => { const intl = useIntl(); const filters: Array<{ @@ -110,6 +110,6 @@ const StatusFilters: React.FC = () => { {filterElements} ); -}; +}); -export default observer(StatusFilters); +export default StatusFilters; diff --git a/client/src/javascript/components/sidebar/TagFilters.tsx b/client/src/javascript/components/sidebar/TagFilters.tsx index b8bd94e9..abd53da6 100644 --- a/client/src/javascript/components/sidebar/TagFilters.tsx +++ b/client/src/javascript/components/sidebar/TagFilters.tsx @@ -1,12 +1,12 @@ +import {FC} from 'react'; import {FormattedMessage} from 'react-intl'; import {observer} from 'mobx-react'; -import * as React from 'react'; import SidebarFilter from './SidebarFilter'; import TorrentFilterStore from '../../stores/TorrentFilterStore'; import UIActions from '../../actions/UIActions'; -const TagFilters: React.FC = () => { +const TagFilters: FC = observer(() => { const tags = Object.keys(TorrentFilterStore.taxonomy.tagCounts); if ((tags.length === 1 && tags[0] === '') || (tags.length === 2 && tags[1] === 'untagged')) { @@ -44,6 +44,6 @@ const TagFilters: React.FC = () => { {filterElements} ); -}; +}); -export default observer(TagFilters); +export default TagFilters; diff --git a/client/src/javascript/components/sidebar/TrackerFilters.tsx b/client/src/javascript/components/sidebar/TrackerFilters.tsx index 8095ff9e..302a9469 100644 --- a/client/src/javascript/components/sidebar/TrackerFilters.tsx +++ b/client/src/javascript/components/sidebar/TrackerFilters.tsx @@ -1,12 +1,12 @@ +import {FC} from 'react'; import {FormattedMessage} from 'react-intl'; import {observer} from 'mobx-react'; -import * as React from 'react'; import SidebarFilter from './SidebarFilter'; import TorrentFilterStore from '../../stores/TorrentFilterStore'; import UIActions from '../../actions/UIActions'; -const TrackerFilters: React.FC = () => { +const TrackerFilters: FC = observer(() => { const trackers = Object.keys(TorrentFilterStore.taxonomy.trackerCounts); if (trackers.length === 1 && trackers[0] === '') { @@ -43,6 +43,6 @@ const TrackerFilters: React.FC = () => { {filterElements} ); -}; +}); -export default observer(TrackerFilters); +export default TrackerFilters; diff --git a/client/src/javascript/components/sidebar/TransferRateDetails.tsx b/client/src/javascript/components/sidebar/TransferRateDetails.tsx index 300fd5ca..a0389a07 100644 --- a/client/src/javascript/components/sidebar/TransferRateDetails.tsx +++ b/client/src/javascript/components/sidebar/TransferRateDetails.tsx @@ -1,6 +1,6 @@ import classnames from 'classnames'; -import {Component} from 'react'; -import {defineMessages, injectIntl, WrappedComponentProps} from 'react-intl'; +import {defineMessages, useIntl} from 'react-intl'; +import {FC} from 'react'; import {observer} from 'mobx-react'; import type {TransferDirection} from '@shared/types/TransferData'; @@ -16,10 +16,6 @@ import Upload from '../icons/Upload'; import type {TransferRateGraphInspectorPoint} from './TransferRateGraph'; -interface TransferRateDetailsProps extends WrappedComponentProps { - inspectorPoint: TransferRateGraphInspectorPoint | null; -} - const messages = defineMessages({ ago: { id: 'general.ago', @@ -32,10 +28,14 @@ const icons = { upload: , }; -@observer -class TransferRateDetails extends Component { - getCurrentTransferRate(direction: TransferDirection, options: {showHoverDuration?: boolean} = {}) { - const {inspectorPoint, intl} = this.props; +interface TransferRateDetailsProps { + inspectorPoint: TransferRateGraphInspectorPoint | null; +} + +const TransferRateDetails: FC = observer(({inspectorPoint}: TransferRateDetailsProps) => { + const intl = useIntl(); + + const getCurrentTransferRate = (direction: TransferDirection, options: {showHoverDuration?: boolean} = {}) => { const {throttleGlobalDownSpeed = 0, throttleGlobalUpSpeed = 0} = SettingStore.clientSettings || {}; const {transferSummary} = TransferDataStore; @@ -106,16 +106,14 @@ class TransferRateDetails extends Component { ); - } + }; - render() { - return ( -
    - {this.getCurrentTransferRate('download', {showHoverDuration: true})} - {this.getCurrentTransferRate('upload')} -
    - ); - } -} + return ( +
    + {getCurrentTransferRate('download', {showHoverDuration: true})} + {getCurrentTransferRate('upload')} +
    + ); +}); -export default injectIntl(TransferRateDetails); +export default TransferRateDetails; diff --git a/client/src/javascript/components/torrent-list/Action.tsx b/client/src/javascript/components/torrent-list/Action.tsx index f8e20c60..a28f5f1f 100644 --- a/client/src/javascript/components/torrent-list/Action.tsx +++ b/client/src/javascript/components/torrent-list/Action.tsx @@ -1,17 +1,17 @@ import classnames from 'classnames'; -import * as React from 'react'; +import {FC, ReactNode} from 'react'; import Tooltip from '../general/Tooltip'; interface ActionProps { clickHandler: () => void; - icon: React.ReactNode; - label: React.ReactNode; + icon: ReactNode; + label: ReactNode; slug: string; noTip?: boolean; } -const Action: React.FC = (props: ActionProps) => { +const Action: FC = (props: ActionProps) => { const {clickHandler, icon, label, slug, noTip} = props; const classes = classnames('action tooltip__wrapper', { [`action--${slug}`]: slug != null, diff --git a/client/src/javascript/components/torrent-list/ActionBar.tsx b/client/src/javascript/components/torrent-list/ActionBar.tsx index 9a4e7cda..3f0ac8cf 100644 --- a/client/src/javascript/components/torrent-list/ActionBar.tsx +++ b/client/src/javascript/components/torrent-list/ActionBar.tsx @@ -1,9 +1,7 @@ import classnames from 'classnames'; -import {injectIntl, WrappedComponentProps} from 'react-intl'; +import {FC} from 'react'; import {observer} from 'mobx-react'; -import {Component} from 'react'; - -import type {FloodSettings} from '@shared/types/FloodSettings'; +import {useIntl} from 'react-intl'; import Action from './Action'; import Add from '../icons/Add'; @@ -18,108 +16,91 @@ import TorrentActions from '../../actions/TorrentActions'; import TorrentStore from '../../stores/TorrentStore'; import UIActions from '../../actions/UIActions'; -@observer -class ActionBar extends Component { - static handleAddTorrents() { - UIActions.displayModal({id: 'add-torrents'}); - } +const ActionBar: FC = observer(() => { + const intl = useIntl(); + const {sortTorrents: sortBy, torrentListViewSize} = SettingStore.floodSettings; - static handleRemoveTorrents() { - UIActions.displayModal({ - id: 'remove-torrents', - }); - } + const classes = classnames('action-bar', { + 'action-bar--is-condensed': torrentListViewSize === 'condensed', + }); - static handleSortChange(sortBy: FloodSettings['sortTorrents']) { - SettingActions.saveSetting('sortTorrents', sortBy); - } - - static handleStart() { - TorrentActions.startTorrents({ - hashes: TorrentStore.selectedTorrents, - }); - } - - static handleStop() { - TorrentActions.stopTorrents({ - hashes: TorrentStore.selectedTorrents, - }); - } - - static handleSidebarChange() { - const view = document.getElementsByClassName('application__view')[0]; - if (view != null) { - view.classList.toggle('application__view--sidebar-alternative-state'); - } - } - - render() { - const {intl} = this.props; - const {sortTorrents: sortBy, torrentListViewSize} = SettingStore.floodSettings; - - const classes = classnames('action-bar', { - 'action-bar--is-condensed': torrentListViewSize === 'condensed', - }); - - return ( - + ); +}); -export default injectIntl(ActionBar); +export default ActionBar; diff --git a/client/src/javascript/components/torrent-list/SortDropdown.tsx b/client/src/javascript/components/torrent-list/SortDropdown.tsx index 29d78cca..edc8890d 100644 --- a/client/src/javascript/components/torrent-list/SortDropdown.tsx +++ b/client/src/javascript/components/torrent-list/SortDropdown.tsx @@ -1,5 +1,5 @@ +import {FC} from 'react'; import {FormattedMessage, useIntl} from 'react-intl'; -import * as React from 'react'; import type {FloodSettings} from '@shared/types/FloodSettings'; @@ -27,7 +27,7 @@ interface SortDropdownProps { onSortChange: (sortBy: FloodSettings['sortTorrents']) => void; } -const SortDropdown: React.FC = (props: SortDropdownProps) => { +const SortDropdown: FC = (props: SortDropdownProps) => { const {direction, selectedProperty, onSortChange} = props; const intl = useIntl(); @@ -75,11 +75,9 @@ const SortDropdown: React.FC = (props: SortDropdownProps) => return; } - let nextDirection = direction; + let nextDirection: 'asc' | 'desc' = 'asc'; if (selectedProperty === property) { nextDirection = direction === 'asc' ? 'desc' : 'asc'; - } else { - nextDirection = 'asc'; } onSortChange({direction: nextDirection, property}); diff --git a/client/src/javascript/components/torrent-list/TorrentList.tsx b/client/src/javascript/components/torrent-list/TorrentList.tsx index 8d60ae2a..c8d41453 100644 --- a/client/src/javascript/components/torrent-list/TorrentList.tsx +++ b/client/src/javascript/components/torrent-list/TorrentList.tsx @@ -1,10 +1,10 @@ +import {Component, createRef, FC, MouseEvent, ReactNode, TouchEvent} from 'react'; import {FormattedMessage, injectIntl, WrappedComponentProps} from 'react-intl'; import {observer} from 'mobx-react'; import {observable, reaction} from 'mobx'; import {useDropzone} from 'react-dropzone'; -import * as React from 'react'; -import type {FixedSizeList, ListChildComponentProps} from 'react-window'; +import type {FixedSizeList} from 'react-window'; import defaultFloodSettings from '@shared/constants/defaultFloodSettings'; @@ -27,7 +27,7 @@ import UIActions from '../../actions/UIActions'; import type {TorrentListColumn} from '../../constants/TorrentListColumns'; -const TorrentDropzone: React.FC<{children: React.ReactNode}> = ({children}: {children: React.ReactNode}) => { +const TorrentDropzone: FC<{children: ReactNode}> = ({children}: {children: ReactNode}) => { const handleFileDrop = (files: Array) => { const filesData: Array = []; @@ -66,7 +66,7 @@ const TorrentDropzone: React.FC<{children: React.ReactNode}> = ({children}: {chi ); }; -const getEmptyTorrentListNotification = (): React.ReactNode => { +const getEmptyTorrentListNotification = (): ReactNode => { let clearFilters = null; if (TorrentFilterStore.isFilterActive) { @@ -93,13 +93,13 @@ const getEmptyTorrentListNotification = (): React.ReactNode => { ); }; -const handleClick = (hash: string, event: React.MouseEvent) => UIActions.handleTorrentClick({hash, event}); +const handleClick = (hash: string, event: MouseEvent) => UIActions.handleTorrentClick({hash, event}); const handleDoubleClick = (hash: string) => TorrentListContextMenu.handleDetailsClick(hash); @observer -class TorrentList extends React.Component { +class TorrentList extends Component { listHeaderRef: HTMLDivElement | null = null; - listViewportRef = React.createRef(); + listViewportRef = createRef(); torrentListViewportSize = observable.object<{width: number; height: number}>({ width: window.innerWidth, @@ -121,7 +121,7 @@ class TorrentList extends React.Component { }); }; - handleContextMenuClick = (hash: string, event: React.MouseEvent | React.TouchEvent) => { + handleContextMenuClick = (hash: string, event: MouseEvent | TouchEvent) => { if (event.cancelable === true) { event.preventDefault(); } @@ -166,21 +166,6 @@ class TorrentList extends React.Component { } }; - renderListItem: React.FC = observer(({index, style}) => { - const torrent = TorrentStore.filteredTorrents[index]; - - return ( - - ); - }); - render() { const torrents = TorrentStore.filteredTorrents; const {torrentListViewSize = 'condensed'} = SettingStore.floodSettings; @@ -188,8 +173,8 @@ class TorrentList extends React.Component { const isCondensed = torrentListViewSize === 'condensed'; const isListEmpty = torrents == null || torrents.length === 0; - let content: React.ReactNode = null; - let torrentListHeading: React.ReactNode = null; + let content: ReactNode = null; + let torrentListHeading: ReactNode = null; if (!ClientStatusStore.isConnected) { content = (
    @@ -232,7 +217,20 @@ class TorrentList extends React.Component { content = ( { + const {hash} = TorrentStore.filteredTorrents[index]; + + return ( + + ); + }} itemSize={isCondensed ? 30 : 70} listLength={torrents.length} ref={this.listViewportRef} diff --git a/client/src/javascript/components/torrent-list/TorrentListCell.tsx b/client/src/javascript/components/torrent-list/TorrentListCell.tsx index eee177ee..65c3df0a 100644 --- a/client/src/javascript/components/torrent-list/TorrentListCell.tsx +++ b/client/src/javascript/components/torrent-list/TorrentListCell.tsx @@ -1,6 +1,6 @@ import classnames from 'classnames'; +import {FC, ReactNode} from 'react'; import {observer} from 'mobx-react'; -import * as React from 'react'; import type {TorrentProperties} from '@shared/types/Torrent'; @@ -14,33 +14,29 @@ import type {TorrentListColumn} from '../../constants/TorrentListColumns'; interface TorrentListCellProps { hash: string; column: TorrentListColumn; - content?: (torrent: TorrentProperties, column: TorrentListColumn) => React.ReactNode; + content?: (torrent: TorrentProperties, column: TorrentListColumn) => ReactNode; className?: string; classNameOverride?: boolean; width?: number; showIcon?: boolean; } -const TorrentListCell: React.FC = ({ - hash, - content, - column, - className, - classNameOverride, - width, - showIcon, -}: TorrentListCellProps) => { - const icon = showIcon ? torrentPropertyIcons[column] : null; +const TorrentListCell: FC = observer( + ({hash, content, column, className, classNameOverride, width, showIcon}: TorrentListCellProps) => { + const icon = showIcon ? torrentPropertyIcons[column] : null; - return ( -
    - {icon} - {content?.(TorrentStore.torrents[hash], column) || } -
    - ); -}; + return ( +
    + {icon} + {content?.(TorrentStore.torrents[hash], column) || } +
    + ); + }, +); TorrentListCell.defaultProps = { className: undefined, @@ -50,4 +46,4 @@ TorrentListCell.defaultProps = { showIcon: false, }; -export default observer(TorrentListCell); +export default TorrentListCell; diff --git a/client/src/javascript/components/torrent-list/TorrentListRow.tsx b/client/src/javascript/components/torrent-list/TorrentListRow.tsx index 2a340dcd..1f1b9d2a 100644 --- a/client/src/javascript/components/torrent-list/TorrentListRow.tsx +++ b/client/src/javascript/components/torrent-list/TorrentListRow.tsx @@ -1,7 +1,7 @@ import classnames from 'classnames'; -import {LongPressDetectEvents, useLongPress} from 'use-long-press'; +import {CSSProperties, FC, MouseEvent, TouchEvent, useRef, useState} from 'react'; import {observer} from 'mobx-react'; -import * as React from 'react'; +import {useLongPress} from 'react-use'; import SettingStore from '../../stores/SettingStore'; import torrentStatusClasses from '../../util/torrentStatusClasses'; @@ -11,17 +11,17 @@ import TorrentListRowCondensed from './TorrentListRowCondensed'; import TorrentListRowExpanded from './TorrentListRowExpanded'; interface TorrentListRowProps { - style: React.CSSProperties; + style: CSSProperties; hash: string; - handleClick: (hash: string, event: React.MouseEvent) => void; - handleDoubleClick: (hash: string, event: React.MouseEvent) => void; - handleRightClick: (hash: string, event: React.MouseEvent | React.TouchEvent) => void; + handleClick: (hash: string, event: MouseEvent) => void; + handleDoubleClick: (hash: string, event: MouseEvent) => void; + handleRightClick: (hash: string, event: MouseEvent | TouchEvent) => void; } -const TorrentListRow: React.FC = (props: TorrentListRowProps) => { +const TorrentListRow: FC = observer((props: TorrentListRowProps) => { const {style, hash, handleClick, handleDoubleClick, handleRightClick} = props; - const [rowLocation, setRowLocation] = React.useState(0); - const rowRef = React.createRef(); + const [rowLocation, setRowLocation] = useState(0); + const rowRef = useRef(null); const isCondensed = SettingStore.floodSettings.torrentListViewSize === 'condensed'; @@ -36,22 +36,20 @@ const TorrentListRow: React.FC = (props: TorrentListRowProp 'torrent', ); - const longPressBind = useLongPress( + const {onTouchStart, onTouchEnd} = useLongPress( (e) => { if (e != null && rowRef.current?.getBoundingClientRect().top === rowLocation) { - handleRightClick(hash, e); + handleRightClick(hash, (e as unknown) as TouchEvent); } }, - { - captureEvent: true, - detect: LongPressDetectEvents.TOUCH, - onStart: () => { - setRowLocation(rowRef.current?.getBoundingClientRect().top || 0); - }, - onFinish: (e) => ((e as unknown) as TouchEvent)?.preventDefault(), - }, + {isPreventDefault: true}, ); + const onTouchStartHooked = (e: TouchEvent) => { + setRowLocation(rowRef.current?.getBoundingClientRect().top || 0); + onTouchStart(e); + }; + if (isCondensed) { return ( = (props: TorrentListRowProp handleClick={handleClick} handleDoubleClick={handleDoubleClick} handleRightClick={handleRightClick} - handleTouchStart={longPressBind.onTouchStart} - handleTouchEnd={longPressBind.onTouchEnd} + handleTouchStart={onTouchStartHooked} + handleTouchEnd={onTouchEnd} /> ); } @@ -77,10 +75,10 @@ const TorrentListRow: React.FC = (props: TorrentListRowProp handleClick={handleClick} handleDoubleClick={handleDoubleClick} handleRightClick={handleRightClick} - handleTouchStart={longPressBind.onTouchStart} - handleTouchEnd={longPressBind.onTouchEnd} + handleTouchStart={onTouchStartHooked} + handleTouchEnd={onTouchEnd} /> ); -}; +}); -export default observer(TorrentListRow); +export default TorrentListRow; diff --git a/client/src/javascript/components/torrent-list/TorrentListRowCondensed.tsx b/client/src/javascript/components/torrent-list/TorrentListRowCondensed.tsx index 78253d41..1fc1c6a5 100644 --- a/client/src/javascript/components/torrent-list/TorrentListRowCondensed.tsx +++ b/client/src/javascript/components/torrent-list/TorrentListRowCondensed.tsx @@ -1,5 +1,5 @@ +import {forwardRef} from 'react'; import {observer} from 'mobx-react'; -import * as React from 'react'; import ProgressBar from '../general/ProgressBar'; import SettingStore from '../../stores/SettingStore'; @@ -18,76 +18,78 @@ interface TorrentListRowCondensedProps { handleTouchEnd: (event: React.TouchEvent) => void; } -const TorrentListRowCondensed = React.forwardRef( - ( - { - className, - style, - hash, - handleClick, - handleDoubleClick, - handleRightClick, - handleTouchStart, - handleTouchEnd, - }: TorrentListRowCondensedProps, - ref, - ) => { - const torrentListColumns = SettingStore.floodSettings.torrentListColumns.reduce( - (accumulator: React.ReactNodeArray, {id, visible}) => { - if (TorrentListColumns[id] == null) { - return accumulator; - } +const TorrentListRowCondensed = observer( + forwardRef( + ( + { + className, + style, + hash, + handleClick, + handleDoubleClick, + handleRightClick, + handleTouchStart, + handleTouchEnd, + }: TorrentListRowCondensedProps, + ref, + ) => { + const torrentListColumns = SettingStore.floodSettings.torrentListColumns.reduce( + (accumulator: React.ReactNodeArray, {id, visible}) => { + if (TorrentListColumns[id] == null) { + return accumulator; + } - if (!visible) { - return accumulator; - } + if (!visible) { + return accumulator; + } + + if (id === 'percentComplete') { + accumulator.push( + ( + + )} + width={SettingStore.floodSettings.torrentListColumnWidths[id]} + />, + ); + + return accumulator; + } - if (id === 'percentComplete') { accumulator.push( ( - - )} width={SettingStore.floodSettings.torrentListColumnWidths[id]} />, ); return accumulator; - } + }, + [], + ); - accumulator.push( - , - ); - - return accumulator; - }, - [], - ); - - return ( -
  • handleClick(hash, e)} - onContextMenu={(e) => handleRightClick(hash, e)} - onDoubleClick={(e) => handleDoubleClick(hash, e)} - onTouchStart={handleTouchStart} - onTouchEnd={handleTouchEnd} - ref={ref}> - {torrentListColumns} -
  • - ); - }, + return ( +
  • handleClick(hash, e)} + onContextMenu={(e) => handleRightClick(hash, e)} + onDoubleClick={(e) => handleDoubleClick(hash, e)} + onTouchStart={handleTouchStart} + onTouchEnd={handleTouchEnd} + ref={ref}> + {torrentListColumns} +
  • + ); + }, + ), ); -export default observer(TorrentListRowCondensed); +export default TorrentListRowCondensed; diff --git a/client/src/javascript/components/torrent-list/TorrentListRowExpanded.tsx b/client/src/javascript/components/torrent-list/TorrentListRowExpanded.tsx index 80aa0e8e..3d9d661f 100644 --- a/client/src/javascript/components/torrent-list/TorrentListRowExpanded.tsx +++ b/client/src/javascript/components/torrent-list/TorrentListRowExpanded.tsx @@ -1,6 +1,6 @@ import {FormattedNumber} from 'react-intl'; +import {forwardRef} from 'react'; import {observer} from 'mobx-react'; -import * as React from 'react'; import ProgressBar from '../general/ProgressBar'; import SettingStore from '../../stores/SettingStore'; @@ -20,105 +20,107 @@ interface TorrentListRowExpandedProps { handleTouchEnd: (event: React.TouchEvent) => void; } -const TorrentListRowExpanded = React.forwardRef( - ( - { - className, - style, - hash, - handleClick, - handleDoubleClick, - handleRightClick, - handleTouchStart, - handleTouchEnd, - }: TorrentListRowExpandedProps, - ref, - ) => { - const columns = SettingStore.floodSettings.torrentListColumns; +const TorrentListRowExpanded = observer( + forwardRef( + ( + { + className, + style, + hash, + handleClick, + handleDoubleClick, + handleRightClick, + handleTouchStart, + handleTouchEnd, + }: TorrentListRowExpandedProps, + ref, + ) => { + const columns = SettingStore.floodSettings.torrentListColumns; - const primarySection: React.ReactNodeArray = [ - , - ]; - const secondarySection: React.ReactNodeArray = [ - , - , - , - ]; - const tertiarySection: React.ReactNodeArray = [ - ( - - - % -  —  - - - )} - showIcon - />, - ]; - const quaternarySection: React.ReactNodeArray = [ - ( - - )} - className="torrent__details__section torrent__details__section--quaternary" - classNameOverride - />, - ]; + const primarySection: React.ReactNodeArray = [ + , + ]; + const secondarySection: React.ReactNodeArray = [ + , + , + , + ]; + const tertiarySection: React.ReactNodeArray = [ + ( + + + % +  —  + + + )} + showIcon + />, + ]; + const quaternarySection: React.ReactNodeArray = [ + ( + + )} + className="torrent__details__section torrent__details__section--quaternary" + classNameOverride + />, + ]; - // Using a for loop to maximize performance. - for (let index = 0; index < columns.length; index += 1) { - const {id, visible} = columns[index]; + // Using a for loop to maximize performance. + for (let index = 0; index < columns.length; index += 1) { + const {id, visible} = columns[index]; - if (TorrentListColumns[id] != null && visible) { - switch (id) { - case 'name': - break; - case 'downRate': - case 'upRate': - case 'eta': - break; - case 'downTotal': - case 'percentComplete': - break; - default: - tertiarySection.push(); - break; + if (TorrentListColumns[id] != null && visible) { + switch (id) { + case 'name': + break; + case 'downRate': + case 'upRate': + case 'eta': + break; + case 'downTotal': + case 'percentComplete': + break; + default: + tertiarySection.push(); + break; + } } } - } - return ( -
  • handleClick(hash, e)} - onContextMenu={(e) => handleRightClick(hash, e)} - onDoubleClick={(e) => handleDoubleClick(hash, e)} - onTouchStart={handleTouchStart} - onTouchEnd={handleTouchEnd} - ref={ref}> -
    - {primarySection} -
    {secondarySection}
    -
    -
    {tertiarySection}
    - {quaternarySection} -
  • - ); - }, + return ( +
  • handleClick(hash, e)} + onContextMenu={(e) => handleRightClick(hash, e)} + onDoubleClick={(e) => handleDoubleClick(hash, e)} + onTouchStart={handleTouchStart} + onTouchEnd={handleTouchEnd} + ref={ref}> +
    + {primarySection} +
    {secondarySection}
    +
    +
    {tertiarySection}
    + {quaternarySection} +
  • + ); + }, + ), ); -export default observer(TorrentListRowExpanded); +export default TorrentListRowExpanded; diff --git a/client/src/javascript/components/views/Login.tsx b/client/src/javascript/components/views/Login.tsx index 7a28a576..5e01104e 100644 --- a/client/src/javascript/components/views/Login.tsx +++ b/client/src/javascript/components/views/Login.tsx @@ -1,7 +1,9 @@ +import {FC} from 'react'; + import ApplicationView from '../layout/ApplicationView'; import AuthForm from '../auth/AuthForm'; -const LoginView = () => { +const LoginView: FC = () => { return ( diff --git a/client/src/javascript/components/views/Register.tsx b/client/src/javascript/components/views/Register.tsx index e5bff845..56744af9 100644 --- a/client/src/javascript/components/views/Register.tsx +++ b/client/src/javascript/components/views/Register.tsx @@ -1,7 +1,9 @@ +import {FC} from 'react'; + import ApplicationView from '../layout/ApplicationView'; import AuthForm from '../auth/AuthForm'; -const LoginView = () => { +const LoginView: FC = () => { return ( diff --git a/client/src/javascript/components/views/TorrentClientOverview.tsx b/client/src/javascript/components/views/TorrentClientOverview.tsx index b2121cf7..311e383b 100644 --- a/client/src/javascript/components/views/TorrentClientOverview.tsx +++ b/client/src/javascript/components/views/TorrentClientOverview.tsx @@ -1,4 +1,4 @@ -import {lazy, Component} from 'react'; +import {FC, lazy, useEffect} from 'react'; import ActionBar from '../torrent-list/ActionBar'; import ApplicationContent from '../layout/ApplicationContent'; @@ -13,24 +13,24 @@ import 'overlayscrollbars/css/OverlayScrollbars.css'; const Alerts = lazy(() => import('../alerts/Alerts')); const Modals = lazy(() => import('../modals/Modals')); -export default class TorrentClientOverview extends Component { - async componentDidMount() { +const TorrentClientOverview: FC = () => { + useEffect(() => { FloodActions.startActivityStream(); - } + }, []); - render() { - return ( - - - - - - - - - - - - ); - } -} + return ( + + + + + + + + + + + + ); +}; + +export default TorrentClientOverview; diff --git a/client/src/javascript/stores/UIStore.ts b/client/src/javascript/stores/UIStore.ts index 34313dcc..e7697af7 100644 --- a/client/src/javascript/stores/UIStore.ts +++ b/client/src/javascript/stores/UIStore.ts @@ -1,3 +1,4 @@ +import {FC, MouseEvent} from 'react'; import {makeAutoObservable} from 'mobx'; import type {TorrentContextMenuAction} from '../constants/TorrentContextMenuActions'; @@ -7,9 +8,9 @@ export type ContextMenuItem = type: 'action'; action: TorrentContextMenuAction; label: string; - labelAction?: React.FC; - labelSecondary?: React.FC; - clickHandler(action: TorrentContextMenuAction, event: React.MouseEvent): void; + labelAction?: FC; + labelSecondary?: FC; + clickHandler(action: TorrentContextMenuAction, event: MouseEvent): void; dismissMenu?: boolean; } | { diff --git a/client/src/javascript/ui/components/Checkbox.tsx b/client/src/javascript/ui/components/Checkbox.tsx index c13518b4..eb5a771c 100644 --- a/client/src/javascript/ui/components/Checkbox.tsx +++ b/client/src/javascript/ui/components/Checkbox.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import {FC} from 'react'; import Checkmark from '../icons/Checkmark'; import ToggleInput from './ToggleInput'; @@ -7,7 +7,7 @@ import type {ToggleInputProps} from './ToggleInput'; type CheckboxProps = Omit; -const Checkbox: React.FC = (props: CheckboxProps) => { +const Checkbox: FC = (props: CheckboxProps) => { return } />; }; diff --git a/client/src/javascript/ui/components/Container.tsx b/client/src/javascript/ui/components/Container.tsx index fdae2f4f..bbebde4e 100644 --- a/client/src/javascript/ui/components/Container.tsx +++ b/client/src/javascript/ui/components/Container.tsx @@ -1,11 +1,11 @@ import classnames from 'classnames'; -import * as React from 'react'; +import {FC, ReactNode} from 'react'; interface ContainerProps { - children: React.ReactNode; + children: ReactNode; } -const Container: React.FC = ({children}: ContainerProps) => { +const Container: FC = ({children}: ContainerProps) => { const classes = classnames('container'); return
    {children}
    ; }; diff --git a/client/src/javascript/ui/components/FadeIn.tsx b/client/src/javascript/ui/components/FadeIn.tsx index de8f5146..5599a5a6 100644 --- a/client/src/javascript/ui/components/FadeIn.tsx +++ b/client/src/javascript/ui/components/FadeIn.tsx @@ -1,12 +1,12 @@ import CSSTransition, {CSSTransitionProps} from 'react-transition-group/CSSTransition'; -import * as React from 'react'; +import {FC, ReactNode} from 'react'; interface FadeInProps { - children: React.ReactNode; + children: ReactNode; isIn: CSSTransitionProps['in']; } -const FadeIn: React.FC = ({children, isIn}: FadeInProps) => { +const FadeIn: FC = ({children, isIn}: FadeInProps) => { return ( {children} diff --git a/client/src/javascript/ui/components/FormElementAddon.tsx b/client/src/javascript/ui/components/FormElementAddon.tsx index 4c91fc0d..a1a03f6d 100644 --- a/client/src/javascript/ui/components/FormElementAddon.tsx +++ b/client/src/javascript/ui/components/FormElementAddon.tsx @@ -1,17 +1,17 @@ import classnames from 'classnames'; -import * as React from 'react'; +import {FC, HTMLAttributes, ReactNode} from 'react'; interface FormElementAddonProps { - children: React.ReactNode; + children: ReactNode; addonPlacement?: 'before' | 'after'; addonIndex?: number; className?: string; isInteractive?: boolean; type?: 'icon'; - onClick?: React.HTMLAttributes['onClick']; + onClick?: HTMLAttributes['onClick']; } -const FormElementAddon: React.FC = ({ +const FormElementAddon: FC = ({ children, type, addonPlacement, diff --git a/client/src/javascript/ui/components/FormRow.tsx b/client/src/javascript/ui/components/FormRow.tsx index 1b144727..6b54c93c 100644 --- a/client/src/javascript/ui/components/FormRow.tsx +++ b/client/src/javascript/ui/components/FormRow.tsx @@ -1,14 +1,14 @@ import classnames from 'classnames'; -import * as React from 'react'; +import {FC, ReactNode} from 'react'; interface FormRowProps { - children: React.ReactNode; + children: ReactNode; align?: 'start' | 'center' | 'end'; justify?: 'start' | 'center' | 'end'; wrap?: boolean; } -const FormRow: React.FC = ({children, align, justify, wrap}: FormRowProps) => { +const FormRow: FC = ({children, align, justify, wrap}: FormRowProps) => { const classes = classnames('form__row', { 'form__row--wrap': wrap, [`form__row--justify--${justify}`]: justify, diff --git a/client/src/javascript/ui/components/Overlay.tsx b/client/src/javascript/ui/components/Overlay.tsx index b35e6c8d..bb767ccc 100644 --- a/client/src/javascript/ui/components/Overlay.tsx +++ b/client/src/javascript/ui/components/Overlay.tsx @@ -1,15 +1,15 @@ import classnames from 'classnames'; -import * as React from 'react'; +import {FC, MouseEvent, ReactNode} from 'react'; export interface OverlayProps { - children?: React.ReactNode; + children?: ReactNode; additionalClassNames?: string; isInteractive?: boolean; isTransparent?: boolean; - onClick?: (event: React.MouseEvent) => void; + onClick?: (event: MouseEvent) => void; } -const Overlay: React.FC = ({ +const Overlay: FC = ({ children, additionalClassNames, onClick, diff --git a/client/src/javascript/ui/components/Radio.tsx b/client/src/javascript/ui/components/Radio.tsx index bb86029f..695ba3b1 100644 --- a/client/src/javascript/ui/components/Radio.tsx +++ b/client/src/javascript/ui/components/Radio.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import {FC} from 'react'; import Circle from '../icons/Circle'; import ToggleInput from './ToggleInput'; @@ -10,7 +10,7 @@ type RadioProps = Omit & { groupID: Required; }; -const Radio: React.FC = (props: RadioProps) => { +const Radio: FC = (props: RadioProps) => { const {groupID, id} = props; return } id={groupID} type="radio" value={id} />; }; diff --git a/client/src/javascript/ui/icons/LoadingRing.tsx b/client/src/javascript/ui/icons/LoadingRing.tsx index af43f05b..8424d9f5 100644 --- a/client/src/javascript/ui/icons/LoadingRing.tsx +++ b/client/src/javascript/ui/icons/LoadingRing.tsx @@ -1,11 +1,11 @@ import classnames from 'classnames'; -import * as React from 'react'; +import {FC} from 'react'; interface LoadingRingProps { size?: string; } -const LoadingRing: React.FC = ({size}: LoadingRingProps) => { +const LoadingRing: FC = ({size}: LoadingRingProps) => { const classes = classnames('icon icon--loading icon--loading--ring', { 'icon--small': size === 'small', }); diff --git a/package-lock.json b/package-lock.json index e1a99b40..3823b7c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,8 +22,6 @@ "@babel/preset-react": "^7.12.5", "@babel/preset-typescript": "^7.12.1", "@formatjs/cli": "^2.13.12", - "@react-hook/media-query": "^1.1.1", - "@react-hook/window-size": "^3.0.7", "@types/bencode": "^2.0.0", "@types/body-parser": "^1.19.0", "@types/classnames": "^2.2.11", @@ -141,6 +139,7 @@ "react-router": "^5.2.0", "react-router-dom": "^5.2.0", "react-transition-group": "^4.4.1", + "react-use": "^15.3.4", "react-window": "^1.8.6", "ress": "^3.0.0", "sanitize-filename": "^1.6.3", @@ -158,7 +157,6 @@ "typed-emitter": "^1.3.1", "typescript": "^4.0.5", "url-loader": "^4.1.1", - "use-long-press": "^1.0.4", "webpack": "^5.4.0", "webpack-dev-server": "^3.11.0", "webpackbar": "^5.0.0-3", @@ -2255,83 +2253,6 @@ "integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==", "dev": true }, - "node_modules/@react-hook/debounce": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@react-hook/debounce/-/debounce-3.0.0.tgz", - "integrity": "sha512-ir/kPrSfAzY12Gre0sOHkZ2rkEmM4fS5M5zFxCi4BnCeXh2nvx9Ujd+U4IGpKCuPA+EQD0pg1eK2NGLvfWejag==", - "dev": true, - "dependencies": { - "@react-hook/latest": "^1.0.2" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/@react-hook/event": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@react-hook/event/-/event-1.2.3.tgz", - "integrity": "sha512-WMBwLnYY2rubLeecsi4skl1imfx0oiXTgazV/1ByPT6WkmLvxUao3hC+mxps5D/+JK4Fq3uG9OWU/dn5jMtXyg==", - "dev": true, - "dependencies": { - "@react-hook/passive-layout-effect": "^1.2.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/@react-hook/latest": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@react-hook/latest/-/latest-1.0.3.tgz", - "integrity": "sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==", - "dev": true, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/@react-hook/media-query": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@react-hook/media-query/-/media-query-1.1.1.tgz", - "integrity": "sha512-VM14wDOX5CW5Dn6b2lTiMd79BFMTut9AZj2+vIRT3LCKgMCYmdqruTtzDPSnIVDQdtxdPgtOzvU9oK20LopuOw==", - "dev": true, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/@react-hook/passive-layout-effect": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz", - "integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==", - "dev": true, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/@react-hook/throttle": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@react-hook/throttle/-/throttle-2.2.0.tgz", - "integrity": "sha512-LJ5eg+yMV8lXtqK3lR+OtOZ2WH/EfWvuiEEu0M3bhR7dZRfTyEJKxH1oK9uyBxiXPtWXiQggWbZirMCXam51tg==", - "dev": true, - "dependencies": { - "@react-hook/latest": "^1.0.2" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/@react-hook/window-size": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@react-hook/window-size/-/window-size-3.0.7.tgz", - "integrity": "sha512-bK5ed/jN+cxy0s1jt2CelCnUt7jZRseUvPQ22ZJkUl/QDOsD+7CA/6wcqC3c0QweM/fPBRP6uI56TJ48SnlVww==", - "dev": true, - "dependencies": { - "@react-hook/debounce": "^3.0.0", - "@react-hook/event": "^1.2.1", - "@react-hook/throttle": "^2.2.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, "node_modules/@sinonjs/commons": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", @@ -3017,6 +2938,12 @@ "pretty-format": "^26.0.0" } }, + "node_modules/@types/js-cookie": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.6.tgz", + "integrity": "sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", @@ -3750,6 +3677,12 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@xobotyi/scrollbar-width": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz", + "integrity": "sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==", + "dev": true + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -4802,6 +4735,12 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, + "node_modules/bowser": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz", + "integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==", + "dev": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5983,6 +5922,15 @@ "node": ">=0.10.0" } }, + "node_modules/copy-to-clipboard": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", + "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "dev": true, + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, "node_modules/core-js-compat": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.7.0.tgz", @@ -6289,6 +6237,25 @@ "node": ">=6" } }, + "node_modules/css-in-js-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz", + "integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==", + "dev": true, + "dependencies": { + "hyphenate-style-name": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "node_modules/css-in-js-utils/node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/css-loader": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.0.1.tgz", @@ -6651,12 +6618,12 @@ } }, "node_modules/css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.1.tgz", + "integrity": "sha512-WroX+2MvsYcRGP8QA0p+rxzOniT/zpAoQ/DTKDSJzh5T3IQKUkFHeIIfgIapm2uaP178GWY3Mime1qbk8GO/tA==", "dev": true, "dependencies": { - "mdn-data": "2.0.4", + "mdn-data": "2.0.12", "source-map": "^0.6.1" }, "engines": { @@ -7130,34 +7097,6 @@ "node": ">=8.0.0" } }, - "node_modules/csso/node_modules/css-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.1.tgz", - "integrity": "sha512-WroX+2MvsYcRGP8QA0p+rxzOniT/zpAoQ/DTKDSJzh5T3IQKUkFHeIIfgIapm2uaP178GWY3Mime1qbk8GO/tA==", - "dev": true, - "dependencies": { - "mdn-data": "2.0.12", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.12.tgz", - "integrity": "sha512-ULbAlgzVb8IqZ0Hsxm6hHSlQl3Jckst2YEQS7fODu9ilNWy2LvcoSY7TRFIktABP2mdppBioc66va90T+NUs8Q==", - "dev": true - }, - "node_modules/csso/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/cssom": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", @@ -7183,9 +7122,9 @@ "dev": true }, "node_modules/csstype": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.4.tgz", - "integrity": "sha512-xc8DUsCLmjvCfoD7LTGE0ou2MIWLx0K9RCZwSHMOdynqRsP4MtUcLeqh1HcQ2dInwDTqn+3CE0/FZh1et+p4jA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz", + "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==", "dev": true }, "node_modules/currently-unhandled": { @@ -8137,6 +8076,15 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/error-stack-parser": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "dev": true, + "dependencies": { + "stackframe": "^1.1.1" + } + }, "node_modules/es-abstract": { "version": "1.17.7", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", @@ -9630,12 +9578,24 @@ "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", "dev": true }, + "node_modules/fast-shallow-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", + "integrity": "sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==", + "dev": true + }, "node_modules/fast-sort": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/fast-sort/-/fast-sort-2.2.0.tgz", "integrity": "sha512-W7zqnn2zsYoQA87FKmYtgOsbJohOrh7XrtZrCVHN5XZKqTBTv5UG+rSS3+iWbg/nepRQUOu+wnas8BwtK8kiCg==", "dev": true }, + "node_modules/fastest-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-1.0.1.tgz", + "integrity": "sha1-kSLUBtTJ2YvqZEpraFPVh0uHsCg=", + "dev": true + }, "node_modules/fastparse": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", @@ -11442,6 +11402,12 @@ "node": ">=8.12.0" } }, + "node_modules/hyphenate-style-name": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==", + "dev": true + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -11611,6 +11577,16 @@ "node": "*" } }, + "node_modules/inline-style-prefixer": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-4.0.2.tgz", + "integrity": "sha512-N8nVhwfYga9MiV9jWlwfdj1UDIaZlBFu4cJSJkIr7tZX7sHpHhGR5su1qdpW+7KPL8ISTvCIkcaFi/JdBknvPg==", + "dev": true, + "dependencies": { + "bowser": "^1.7.3", + "css-in-js-utils": "^2.0.0" + } + }, "node_modules/inquirer": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", @@ -13172,6 +13148,12 @@ "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", "dev": true }, + "node_modules/js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==", + "dev": true + }, "node_modules/js-file-download": { "version": "0.4.12", "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz", @@ -13982,9 +13964,9 @@ } }, "node_modules/mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.12.tgz", + "integrity": "sha512-ULbAlgzVb8IqZ0Hsxm6hHSlQl3Jckst2YEQS7fODu9ilNWy2LvcoSY7TRFIktABP2mdppBioc66va90T+NUs8Q==", "dev": true }, "node_modules/mdurl": { @@ -14636,6 +14618,32 @@ "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", "dev": true }, + "node_modules/nano-css": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/nano-css/-/nano-css-5.3.0.tgz", + "integrity": "sha512-uM/9NGK9/E9/sTpbIZ/bQ9xOLOIHZwrrb/CRlbDHBU/GFS7Gshl24v/WJhwsVViWkpOXUmiZ66XO7fSB4Wd92Q==", + "dev": true, + "dependencies": { + "css-tree": "^1.0.0-alpha.28", + "csstype": "^2.5.5", + "fastest-stable-stringify": "^1.0.1", + "inline-style-prefixer": "^4.0.0", + "rtl-css-js": "^1.9.0", + "sourcemap-codec": "^1.4.1", + "stacktrace-js": "^2.0.0", + "stylis": "3.5.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/nano-css/node_modules/csstype": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.14.tgz", + "integrity": "sha512-2mSc+VEpGPblzAxyeR+vZhJKgYg0Og0nnRi7pmRXFYYxSfnOnW8A5wwQb4n4cE2nIOzqKOAzLCaEX6aBmNEv8A==", + "dev": true + }, "node_modules/nanoid": { "version": "3.1.16", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.16.tgz", @@ -20579,6 +20587,42 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-universal-interface": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz", + "integrity": "sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==", + "dev": true, + "peerDependencies": { + "react": "*", + "tslib": "*" + } + }, + "node_modules/react-use": { + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/react-use/-/react-use-15.3.4.tgz", + "integrity": "sha512-cHq1dELW6122oi1+xX7lwNyE/ugZs5L902BuO8eFJCfn2api1KeuPVG1M/GJouVARoUf54S2dYFMKo5nQXdTag==", + "dev": true, + "dependencies": { + "@types/js-cookie": "2.2.6", + "@xobotyi/scrollbar-width": "1.9.5", + "copy-to-clipboard": "^3.2.0", + "fast-deep-equal": "^3.1.3", + "fast-shallow-equal": "^1.0.0", + "js-cookie": "^2.2.1", + "nano-css": "^5.2.1", + "react-universal-interface": "^0.6.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.0.0", + "set-harmonic-interval": "^1.0.1", + "throttle-debounce": "^2.1.0", + "ts-easing": "^0.2.0", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0", + "react-dom": "^16.8.0" + } + }, "node_modules/react-window": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.6.tgz", @@ -21307,6 +21351,15 @@ "node": "6.* || >= 7.*" } }, + "node_modules/rtl-css-js": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.14.0.tgz", + "integrity": "sha512-Dl5xDTeN3e7scU1cWX8c9b6/Nqz3u/HgR4gePc1kWXYiQWVQbKCEyK6+Hxve9LbcJ5EieHy1J9nJCN3grTtGwg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.1.2" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -22050,6 +22103,18 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/screenfull": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.0.2.tgz", + "integrity": "sha512-cCF2b+L/mnEiORLN5xSAz6H3t18i2oHh9BA8+CQlAh5DRw2+NFAGQJOSYbcGw8B2k04g/lVvFcfZ83b3ysH5UQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/scss-tokenizer": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", @@ -22253,6 +22318,15 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "node_modules/set-harmonic-interval": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz", + "integrity": "sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==", + "dev": true, + "engines": { + "node": ">=6.9" + } + }, "node_modules/set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -22871,6 +22945,12 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, "node_modules/spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -22999,6 +23079,15 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", "dev": true }, + "node_modules/stack-generator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.5.tgz", + "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", + "dev": true, + "dependencies": { + "stackframe": "^1.1.1" + } + }, "node_modules/stack-utils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.2.tgz", @@ -23011,6 +23100,42 @@ "node": ">=10" } }, + "node_modules/stackframe": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", + "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==", + "dev": true + }, + "node_modules/stacktrace-gps": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz", + "integrity": "sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg==", + "dev": true, + "dependencies": { + "source-map": "0.5.6", + "stackframe": "^1.1.1" + } + }, + "node_modules/stacktrace-gps/node_modules/source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stacktrace-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", + "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", + "dev": true, + "dependencies": { + "error-stack-parser": "^2.0.6", + "stack-generator": "^2.0.5", + "stacktrace-gps": "^3.0.4" + } + }, "node_modules/static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -23685,6 +23810,12 @@ "node": ">=6" } }, + "node_modules/stylis": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.0.tgz", + "integrity": "sha512-pP7yXN6dwMzAR29Q0mBrabPCe0/mNO1MSr93bhay+hcZondvMMTpeGyd8nbhYJdyperNT2DRxONQuUGcJr5iPw==", + "dev": true + }, "node_modules/superagent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", @@ -23861,6 +23992,19 @@ "nth-check": "^1.0.2" } }, + "node_modules/svgo/node_modules/css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/svgo/node_modules/css-what": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", @@ -23901,6 +24045,12 @@ "node": ">=4" } }, + "node_modules/svgo/node_modules/mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "dev": true + }, "node_modules/svgo/node_modules/mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -23913,6 +24063,15 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/svgo/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/svgo/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -24194,6 +24353,15 @@ "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", "dev": true }, + "node_modules/throttle-debounce": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.3.0.tgz", + "integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -24326,6 +24494,12 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=", + "dev": true + }, "node_modules/toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -24415,6 +24589,12 @@ "utf8-byte-length": "^1.0.1" } }, + "node_modules/ts-easing": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz", + "integrity": "sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==", + "dev": true + }, "node_modules/ts-jest": { "version": "26.4.4", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.4.4.tgz", @@ -25275,19 +25455,6 @@ "node": ">=0.10.0" } }, - "node_modules/use-long-press": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/use-long-press/-/use-long-press-1.0.4.tgz", - "integrity": "sha512-Lw70Sfjut//hEsm/La/ik/5aL042No+4c5AzIb4FhCslLiuVdD8AN2nxRckeJMWBU/88xOwPZ+x+CD5Tjk6sPA==", - "dev": true, - "engines": { - "node": ">=8", - "npm": ">=5" - }, - "peerDependencies": { - "react": "^16.8.0" - } - }, "node_modules/utf8-byte-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", @@ -28596,65 +28763,6 @@ "integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==", "dev": true }, - "@react-hook/debounce": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@react-hook/debounce/-/debounce-3.0.0.tgz", - "integrity": "sha512-ir/kPrSfAzY12Gre0sOHkZ2rkEmM4fS5M5zFxCi4BnCeXh2nvx9Ujd+U4IGpKCuPA+EQD0pg1eK2NGLvfWejag==", - "dev": true, - "requires": { - "@react-hook/latest": "^1.0.2" - } - }, - "@react-hook/event": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@react-hook/event/-/event-1.2.3.tgz", - "integrity": "sha512-WMBwLnYY2rubLeecsi4skl1imfx0oiXTgazV/1ByPT6WkmLvxUao3hC+mxps5D/+JK4Fq3uG9OWU/dn5jMtXyg==", - "dev": true, - "requires": { - "@react-hook/passive-layout-effect": "^1.2.0" - } - }, - "@react-hook/latest": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@react-hook/latest/-/latest-1.0.3.tgz", - "integrity": "sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==", - "dev": true, - "requires": {} - }, - "@react-hook/media-query": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@react-hook/media-query/-/media-query-1.1.1.tgz", - "integrity": "sha512-VM14wDOX5CW5Dn6b2lTiMd79BFMTut9AZj2+vIRT3LCKgMCYmdqruTtzDPSnIVDQdtxdPgtOzvU9oK20LopuOw==", - "dev": true, - "requires": {} - }, - "@react-hook/passive-layout-effect": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz", - "integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==", - "dev": true, - "requires": {} - }, - "@react-hook/throttle": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@react-hook/throttle/-/throttle-2.2.0.tgz", - "integrity": "sha512-LJ5eg+yMV8lXtqK3lR+OtOZ2WH/EfWvuiEEu0M3bhR7dZRfTyEJKxH1oK9uyBxiXPtWXiQggWbZirMCXam51tg==", - "dev": true, - "requires": { - "@react-hook/latest": "^1.0.2" - } - }, - "@react-hook/window-size": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@react-hook/window-size/-/window-size-3.0.7.tgz", - "integrity": "sha512-bK5ed/jN+cxy0s1jt2CelCnUt7jZRseUvPQ22ZJkUl/QDOsD+7CA/6wcqC3c0QweM/fPBRP6uI56TJ48SnlVww==", - "dev": true, - "requires": { - "@react-hook/debounce": "^3.0.0", - "@react-hook/event": "^1.2.1", - "@react-hook/throttle": "^2.2.0" - } - }, "@sinonjs/commons": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", @@ -29318,6 +29426,12 @@ "pretty-format": "^26.0.0" } }, + "@types/js-cookie": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.6.tgz", + "integrity": "sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw==", + "dev": true + }, "@types/json-schema": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", @@ -29963,6 +30077,12 @@ "@xtuc/long": "4.2.2" } }, + "@xobotyi/scrollbar-width": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz", + "integrity": "sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==", + "dev": true + }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -30817,6 +30937,12 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, + "bowser": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz", + "integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -31813,6 +31939,15 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, + "copy-to-clipboard": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", + "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "dev": true, + "requires": { + "toggle-selection": "^1.0.6" + } + }, "core-js-compat": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.7.0.tgz", @@ -32052,6 +32187,24 @@ } } }, + "css-in-js-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz", + "integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==", + "dev": true, + "requires": { + "hyphenate-style-name": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "css-loader": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.0.1.tgz", @@ -32331,12 +32484,12 @@ } }, "css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.1.tgz", + "integrity": "sha512-WroX+2MvsYcRGP8QA0p+rxzOniT/zpAoQ/DTKDSJzh5T3IQKUkFHeIIfgIapm2uaP178GWY3Mime1qbk8GO/tA==", "dev": true, "requires": { - "mdn-data": "2.0.4", + "mdn-data": "2.0.12", "source-map": "^0.6.1" }, "dependencies": { @@ -32702,30 +32855,6 @@ "dev": true, "requires": { "css-tree": "^1.0.0" - }, - "dependencies": { - "css-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.1.tgz", - "integrity": "sha512-WroX+2MvsYcRGP8QA0p+rxzOniT/zpAoQ/DTKDSJzh5T3IQKUkFHeIIfgIapm2uaP178GWY3Mime1qbk8GO/tA==", - "dev": true, - "requires": { - "mdn-data": "2.0.12", - "source-map": "^0.6.1" - } - }, - "mdn-data": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.12.tgz", - "integrity": "sha512-ULbAlgzVb8IqZ0Hsxm6hHSlQl3Jckst2YEQS7fODu9ilNWy2LvcoSY7TRFIktABP2mdppBioc66va90T+NUs8Q==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "cssom": { @@ -32752,9 +32881,9 @@ } }, "csstype": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.4.tgz", - "integrity": "sha512-xc8DUsCLmjvCfoD7LTGE0ou2MIWLx0K9RCZwSHMOdynqRsP4MtUcLeqh1HcQ2dInwDTqn+3CE0/FZh1et+p4jA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz", + "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==", "dev": true }, "currently-unhandled": { @@ -33564,6 +33693,15 @@ "is-arrayish": "^0.2.1" } }, + "error-stack-parser": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "dev": true, + "requires": { + "stackframe": "^1.1.1" + } + }, "es-abstract": { "version": "1.17.7", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", @@ -34686,12 +34824,24 @@ "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", "dev": true }, + "fast-shallow-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", + "integrity": "sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==", + "dev": true + }, "fast-sort": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/fast-sort/-/fast-sort-2.2.0.tgz", "integrity": "sha512-W7zqnn2zsYoQA87FKmYtgOsbJohOrh7XrtZrCVHN5XZKqTBTv5UG+rSS3+iWbg/nepRQUOu+wnas8BwtK8kiCg==", "dev": true }, + "fastest-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-1.0.1.tgz", + "integrity": "sha1-kSLUBtTJ2YvqZEpraFPVh0uHsCg=", + "dev": true + }, "fastparse": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", @@ -36169,6 +36319,12 @@ "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true }, + "hyphenate-style-name": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==", + "dev": true + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -36290,6 +36446,16 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, + "inline-style-prefixer": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-4.0.2.tgz", + "integrity": "sha512-N8nVhwfYga9MiV9jWlwfdj1UDIaZlBFu4cJSJkIr7tZX7sHpHhGR5su1qdpW+7KPL8ISTvCIkcaFi/JdBknvPg==", + "dev": true, + "requires": { + "bowser": "^1.7.3", + "css-in-js-utils": "^2.0.0" + } + }, "inquirer": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", @@ -37511,6 +37677,12 @@ "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", "dev": true }, + "js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==", + "dev": true + }, "js-file-download": { "version": "0.4.12", "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz", @@ -38193,9 +38365,9 @@ } }, "mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.12.tgz", + "integrity": "sha512-ULbAlgzVb8IqZ0Hsxm6hHSlQl3Jckst2YEQS7fODu9ilNWy2LvcoSY7TRFIktABP2mdppBioc66va90T+NUs8Q==", "dev": true }, "mdurl": { @@ -38679,6 +38851,30 @@ "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", "dev": true }, + "nano-css": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/nano-css/-/nano-css-5.3.0.tgz", + "integrity": "sha512-uM/9NGK9/E9/sTpbIZ/bQ9xOLOIHZwrrb/CRlbDHBU/GFS7Gshl24v/WJhwsVViWkpOXUmiZ66XO7fSB4Wd92Q==", + "dev": true, + "requires": { + "css-tree": "^1.0.0-alpha.28", + "csstype": "^2.5.5", + "fastest-stable-stringify": "^1.0.1", + "inline-style-prefixer": "^4.0.0", + "rtl-css-js": "^1.9.0", + "sourcemap-codec": "^1.4.1", + "stacktrace-js": "^2.0.0", + "stylis": "3.5.0" + }, + "dependencies": { + "csstype": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.14.tgz", + "integrity": "sha512-2mSc+VEpGPblzAxyeR+vZhJKgYg0Og0nnRi7pmRXFYYxSfnOnW8A5wwQb4n4cE2nIOzqKOAzLCaEX6aBmNEv8A==", + "dev": true + } + } + }, "nanoid": { "version": "3.1.16", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.16.tgz", @@ -43371,6 +43567,35 @@ "prop-types": "^15.6.2" } }, + "react-universal-interface": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz", + "integrity": "sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==", + "dev": true, + "requires": {} + }, + "react-use": { + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/react-use/-/react-use-15.3.4.tgz", + "integrity": "sha512-cHq1dELW6122oi1+xX7lwNyE/ugZs5L902BuO8eFJCfn2api1KeuPVG1M/GJouVARoUf54S2dYFMKo5nQXdTag==", + "dev": true, + "requires": { + "@types/js-cookie": "2.2.6", + "@xobotyi/scrollbar-width": "1.9.5", + "copy-to-clipboard": "^3.2.0", + "fast-deep-equal": "^3.1.3", + "fast-shallow-equal": "^1.0.0", + "js-cookie": "^2.2.1", + "nano-css": "^5.2.1", + "react-universal-interface": "^0.6.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.0.0", + "set-harmonic-interval": "^1.0.1", + "throttle-debounce": "^2.1.0", + "ts-easing": "^0.2.0", + "tslib": "^2.0.0" + } + }, "react-window": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.6.tgz", @@ -43942,6 +44167,15 @@ "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", "dev": true }, + "rtl-css-js": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.14.0.tgz", + "integrity": "sha512-Dl5xDTeN3e7scU1cWX8c9b6/Nqz3u/HgR4gePc1kWXYiQWVQbKCEyK6+Hxve9LbcJ5EieHy1J9nJCN3grTtGwg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.1.2" + } + }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -44507,6 +44741,12 @@ "ajv-keywords": "^3.5.2" } }, + "screenfull": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.0.2.tgz", + "integrity": "sha512-cCF2b+L/mnEiORLN5xSAz6H3t18i2oHh9BA8+CQlAh5DRw2+NFAGQJOSYbcGw8B2k04g/lVvFcfZ83b3ysH5UQ==", + "dev": true + }, "scss-tokenizer": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", @@ -44697,6 +44937,12 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "set-harmonic-interval": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz", + "integrity": "sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==", + "dev": true + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -45219,6 +45465,12 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -45332,6 +45584,15 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", "dev": true }, + "stack-generator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.5.tgz", + "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", + "dev": true, + "requires": { + "stackframe": "^1.1.1" + } + }, "stack-utils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.2.tgz", @@ -45341,6 +45602,41 @@ "escape-string-regexp": "^2.0.0" } }, + "stackframe": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", + "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==", + "dev": true + }, + "stacktrace-gps": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz", + "integrity": "sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg==", + "dev": true, + "requires": { + "source-map": "0.5.6", + "stackframe": "^1.1.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "dev": true + } + } + }, + "stacktrace-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", + "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", + "dev": true, + "requires": { + "error-stack-parser": "^2.0.6", + "stack-generator": "^2.0.5", + "stacktrace-gps": "^3.0.4" + } + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -45899,6 +46195,12 @@ } } }, + "stylis": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.0.tgz", + "integrity": "sha512-pP7yXN6dwMzAR29Q0mBrabPCe0/mNO1MSr93bhay+hcZondvMMTpeGyd8nbhYJdyperNT2DRxONQuUGcJr5iPw==", + "dev": true + }, "superagent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", @@ -46035,6 +46337,16 @@ "nth-check": "^1.0.2" } }, + "css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dev": true, + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + } + }, "css-what": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", @@ -46063,6 +46375,12 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "dev": true + }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -46072,6 +46390,12 @@ "minimist": "^1.2.5" } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -46289,6 +46613,12 @@ "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", "dev": true }, + "throttle-debounce": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.3.0.tgz", + "integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==", + "dev": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -46402,6 +46732,12 @@ "is-number": "^7.0.0" } }, + "toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=", + "dev": true + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -46474,6 +46810,12 @@ "utf8-byte-length": "^1.0.1" } }, + "ts-easing": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz", + "integrity": "sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==", + "dev": true + }, "ts-jest": { "version": "26.4.4", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.4.4.tgz", @@ -47143,13 +47485,6 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, - "use-long-press": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/use-long-press/-/use-long-press-1.0.4.tgz", - "integrity": "sha512-Lw70Sfjut//hEsm/La/ik/5aL042No+4c5AzIb4FhCslLiuVdD8AN2nxRckeJMWBU/88xOwPZ+x+CD5Tjk6sPA==", - "dev": true, - "requires": {} - }, "utf8-byte-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", diff --git a/package.json b/package.json index aeb57157..419fd822 100644 --- a/package.json +++ b/package.json @@ -62,8 +62,6 @@ "@babel/preset-react": "^7.12.5", "@babel/preset-typescript": "^7.12.1", "@formatjs/cli": "^2.13.12", - "@react-hook/media-query": "^1.1.1", - "@react-hook/window-size": "^3.0.7", "@types/bencode": "^2.0.0", "@types/body-parser": "^1.19.0", "@types/classnames": "^2.2.11", @@ -181,6 +179,7 @@ "react-router": "^5.2.0", "react-router-dom": "^5.2.0", "react-transition-group": "^4.4.1", + "react-use": "^15.3.4", "react-window": "^1.8.6", "ress": "^3.0.0", "sanitize-filename": "^1.6.3", @@ -198,7 +197,6 @@ "typed-emitter": "^1.3.1", "typescript": "^4.0.5", "url-loader": "^4.1.1", - "use-long-press": "^1.0.4", "webpack": "^5.4.0", "webpack-dev-server": "^3.11.0", "webpackbar": "^5.0.0-3",