mirror of
https://github.com/zoriya/flood.git
synced 2026-06-07 20:30:42 +00:00
client: fix up and migrate to native scrolling
Native scrolling is much simpler and more performant. This change also fixes a bunch of related visual bugs. Ditch the unmaintained react-custom-scrollbars. A more stylish scrollbar can be added in a later change.
This commit is contained in:
@@ -1,92 +0,0 @@
|
||||
import classnames from 'classnames';
|
||||
import React from 'react';
|
||||
import {positionValues, Scrollbars} from 'react-custom-scrollbars';
|
||||
|
||||
const horizontalThumb: React.StatelessComponent = (props) => {
|
||||
return <div {...props} className="scrollbars__thumb scrollbars__thumb--horizontal" />;
|
||||
};
|
||||
|
||||
const verticalThumb: React.StatelessComponent = (props) => {
|
||||
return <div {...props} className="scrollbars__thumb scrollbars__thumb--vertical" />;
|
||||
};
|
||||
|
||||
const renderView: React.StatelessComponent = (props) => {
|
||||
return (
|
||||
<div {...props} className="scrollbars__view">
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface CustomScrollbarsProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
autoHeight?: boolean;
|
||||
autoHeightMin?: number | string;
|
||||
autoHeightMax?: number | string;
|
||||
inverted?: boolean;
|
||||
getHorizontalThumb?: React.StatelessComponent;
|
||||
getVerticalThumb?: React.StatelessComponent;
|
||||
nativeScrollHandler?: (event: React.UIEvent) => void;
|
||||
scrollHandler?: (values: positionValues) => void;
|
||||
onScrollStart?: () => void;
|
||||
onScrollStop?: () => void;
|
||||
}
|
||||
|
||||
const CustomScrollbars = React.forwardRef<Scrollbars, CustomScrollbarsProps>((props: CustomScrollbarsProps, ref) => {
|
||||
const {
|
||||
children,
|
||||
className,
|
||||
style,
|
||||
autoHeight,
|
||||
autoHeightMin,
|
||||
autoHeightMax,
|
||||
inverted,
|
||||
getHorizontalThumb,
|
||||
getVerticalThumb,
|
||||
nativeScrollHandler,
|
||||
scrollHandler,
|
||||
onScrollStart,
|
||||
onScrollStop,
|
||||
} = props;
|
||||
const classes = classnames('scrollbars', className, {
|
||||
'is-inverted': inverted,
|
||||
});
|
||||
|
||||
return (
|
||||
<Scrollbars
|
||||
className={classes}
|
||||
style={style}
|
||||
autoHeight={autoHeight}
|
||||
autoHeightMin={autoHeightMin}
|
||||
autoHeightMax={autoHeightMax}
|
||||
ref={ref}
|
||||
renderView={renderView}
|
||||
renderThumbHorizontal={getHorizontalThumb}
|
||||
renderThumbVertical={getVerticalThumb}
|
||||
onScroll={nativeScrollHandler}
|
||||
onScrollFrame={scrollHandler}
|
||||
onScrollStart={onScrollStart}
|
||||
onScrollStop={onScrollStop}>
|
||||
{children}
|
||||
</Scrollbars>
|
||||
);
|
||||
});
|
||||
|
||||
CustomScrollbars.defaultProps = {
|
||||
className: '',
|
||||
style: undefined,
|
||||
autoHeight: undefined,
|
||||
autoHeightMin: undefined,
|
||||
autoHeightMax: undefined,
|
||||
inverted: undefined,
|
||||
getHorizontalThumb: horizontalThumb,
|
||||
getVerticalThumb: verticalThumb,
|
||||
nativeScrollHandler: undefined,
|
||||
scrollHandler: undefined,
|
||||
onScrollStart: undefined,
|
||||
onScrollStop: undefined,
|
||||
};
|
||||
|
||||
export default CustomScrollbars;
|
||||
@@ -1,282 +1,37 @@
|
||||
import debounce from 'lodash/debounce';
|
||||
import React from 'react';
|
||||
import {positionValues, Scrollbars} from 'react-custom-scrollbars';
|
||||
import throttle from 'lodash/throttle';
|
||||
|
||||
import CustomScrollbars from './CustomScrollbars';
|
||||
|
||||
const METHODS_TO_BIND = [
|
||||
'handleScroll',
|
||||
'handleScrollStart',
|
||||
'handleScrollStop',
|
||||
'measureItemHeight',
|
||||
'scrollToTop',
|
||||
'setScrollPosition',
|
||||
'setViewportHeight',
|
||||
] as const;
|
||||
|
||||
interface ListViewportProps {
|
||||
children?: React.ReactNode;
|
||||
itemRenderer: (index: number) => React.ReactNode;
|
||||
listClass: string;
|
||||
listLength: number;
|
||||
scrollContainerClass: string;
|
||||
topSpacerClass?: string;
|
||||
bottomSpacerClass?: string;
|
||||
itemScrollOffset?: number;
|
||||
getVerticalThumb?: React.StatelessComponent;
|
||||
onScroll?: () => void;
|
||||
}
|
||||
|
||||
interface ListViewportStates {
|
||||
itemHeight: number | null;
|
||||
listVerticalPadding: number | null;
|
||||
scrollTop: number;
|
||||
viewportHeight: number | null;
|
||||
}
|
||||
// TODO: Implement windowing or infinite scrolling
|
||||
const ListViewport = React.forwardRef<HTMLDivElement, ListViewportProps>((props: ListViewportProps, ref) => {
|
||||
const {children, listClass, listLength, itemRenderer, onScroll} = props;
|
||||
|
||||
class ListViewport extends React.Component<ListViewportProps, ListViewportStates> {
|
||||
scrollbarRef: Scrollbars | null = null;
|
||||
listRef: HTMLUListElement | null = null;
|
||||
topSpacerRef: HTMLLIElement | null = null;
|
||||
isScrolling = false;
|
||||
lastScrollTop = 0;
|
||||
const list = [];
|
||||
|
||||
static defaultProps = {
|
||||
bottomSpacerClass: 'list__spacer list__spacer--bottom',
|
||||
itemScrollOffset: 10,
|
||||
topSpacerClass: 'list__spacer list__spacer--top',
|
||||
};
|
||||
|
||||
constructor(props: ListViewportProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
itemHeight: null,
|
||||
listVerticalPadding: null,
|
||||
scrollTop: 0,
|
||||
viewportHeight: null,
|
||||
};
|
||||
|
||||
METHODS_TO_BIND.forEach(<T extends typeof METHODS_TO_BIND[number]>(methodName: T) => {
|
||||
this[methodName] = this[methodName].bind(this);
|
||||
});
|
||||
|
||||
this.setViewportHeight = debounce(this.setViewportHeight, 250);
|
||||
this.updateAfterScrolling = debounce(this.updateAfterScrolling, 500, {
|
||||
leading: true,
|
||||
trailing: true,
|
||||
});
|
||||
this.setScrollPosition = throttle(this.setScrollPosition, 100);
|
||||
// For loops are fast, and performance matters here.
|
||||
for (let index = 0; index < listLength; index += 1) {
|
||||
list.push(itemRenderer(index));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
global.addEventListener('resize', this.setViewportHeight);
|
||||
this.setViewportHeight();
|
||||
}
|
||||
const listContent = <ul className={listClass}>{list}</ul>;
|
||||
|
||||
shouldComponentUpdate(_nextProps: ListViewportProps, nextState: ListViewportStates) {
|
||||
const {scrollTop} = this.state;
|
||||
const scrollDelta = Math.abs(scrollTop - nextState.scrollTop);
|
||||
return (
|
||||
<div className="torrent__list__viewport" onScroll={onScroll} ref={ref}>
|
||||
{children}
|
||||
{listContent}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
if (this.isScrolling && scrollDelta > 20) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
global.removeEventListener('resize', this.setViewportHeight);
|
||||
}
|
||||
|
||||
getViewportLimits(scrollDelta: number) {
|
||||
const {itemScrollOffset, listLength} = this.props;
|
||||
const {itemHeight, listVerticalPadding, scrollTop, viewportHeight} = this.state;
|
||||
|
||||
if (
|
||||
itemHeight == null ||
|
||||
itemHeight <= 0 ||
|
||||
itemScrollOffset == null ||
|
||||
viewportHeight == null ||
|
||||
viewportHeight <= 0
|
||||
) {
|
||||
return {
|
||||
minItemIndex: 0,
|
||||
maxItemIndex: Math.min(50, listLength),
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate the number of items that should be rendered based on the height
|
||||
// of the viewport. We offset this to render a few more outside of the
|
||||
// container's dimensions, which looks nicer when the user scrolls.
|
||||
const offsetBottom = scrollDelta > 0 ? itemScrollOffset * 2 : itemScrollOffset / 2;
|
||||
const offsetTop = scrollDelta < 0 ? itemScrollOffset * 2 : itemScrollOffset / 2;
|
||||
|
||||
let viewportHeightPadded = viewportHeight;
|
||||
|
||||
if (listVerticalPadding) {
|
||||
viewportHeightPadded -= listVerticalPadding;
|
||||
}
|
||||
|
||||
// The number of elements in view is the height of the viewport divided
|
||||
// by the height of the elements.
|
||||
const elementsInView = Math.ceil(viewportHeightPadded / itemHeight);
|
||||
|
||||
// The minimum item index to render is the number of items above the
|
||||
// viewport's current scroll position, minus the offset.
|
||||
const minItemIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - offsetTop);
|
||||
|
||||
// The maximum item index to render is the minimum item rendered, plus the
|
||||
// number of items in view, plus double the offset.
|
||||
const maxItemIndex = Math.min(listLength, minItemIndex + elementsInView + offsetBottom + offsetTop);
|
||||
|
||||
return {minItemIndex, maxItemIndex};
|
||||
}
|
||||
|
||||
getListPadding(minItemIndex: number, maxItemIndex: number, itemCount: number) {
|
||||
const {itemHeight} = this.state;
|
||||
|
||||
if (itemHeight == null) {
|
||||
return {bottom: 0, top: 0};
|
||||
}
|
||||
|
||||
const bottom = (itemCount - maxItemIndex) * itemHeight;
|
||||
const top = minItemIndex * itemHeight;
|
||||
|
||||
return {bottom, top};
|
||||
}
|
||||
|
||||
setScrollPosition(scrollValues: positionValues) {
|
||||
const {scrollTop} = this.state;
|
||||
|
||||
this.lastScrollTop = scrollTop;
|
||||
this.setState({scrollTop: scrollValues.scrollTop});
|
||||
}
|
||||
|
||||
setViewportHeight() {
|
||||
if (this.scrollbarRef) {
|
||||
this.setState({
|
||||
viewportHeight: this.scrollbarRef.getClientHeight(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
scrollToTop() {
|
||||
const {scrollTop} = this.state;
|
||||
|
||||
if (scrollTop !== 0) {
|
||||
if (this.scrollbarRef != null) {
|
||||
this.scrollbarRef.scrollToTop();
|
||||
}
|
||||
|
||||
this.lastScrollTop = 0;
|
||||
this.setState({scrollTop: 0});
|
||||
}
|
||||
}
|
||||
|
||||
measureItemHeight() {
|
||||
this.lastScrollTop = 0;
|
||||
|
||||
this.setState(
|
||||
{
|
||||
scrollTop: 0,
|
||||
itemHeight: null,
|
||||
},
|
||||
() => {
|
||||
if (this.scrollbarRef != null) {
|
||||
this.scrollbarRef.scrollTop(0);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
handleScroll(scrollValues: positionValues) {
|
||||
this.setScrollPosition(scrollValues);
|
||||
}
|
||||
|
||||
handleScrollStart() {
|
||||
this.isScrolling = true;
|
||||
}
|
||||
|
||||
handleScrollStop() {
|
||||
this.isScrolling = false;
|
||||
this.updateAfterScrolling();
|
||||
}
|
||||
|
||||
updateAfterScrolling() {
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
children,
|
||||
listClass,
|
||||
topSpacerClass,
|
||||
bottomSpacerClass,
|
||||
scrollContainerClass,
|
||||
listLength,
|
||||
getVerticalThumb,
|
||||
itemRenderer,
|
||||
} = this.props;
|
||||
const {itemHeight, scrollTop, listVerticalPadding} = this.state;
|
||||
|
||||
const {minItemIndex, maxItemIndex} = this.getViewportLimits(scrollTop - this.lastScrollTop);
|
||||
const listPadding = this.getListPadding(minItemIndex, maxItemIndex, listLength);
|
||||
const list = [];
|
||||
|
||||
// For loops are fast, and performance matters here.
|
||||
for (let index = minItemIndex; index < maxItemIndex; index += 1) {
|
||||
list.push(itemRenderer(index));
|
||||
}
|
||||
|
||||
const listContent = (
|
||||
<ul
|
||||
className={listClass}
|
||||
ref={(ref) => {
|
||||
this.listRef = ref;
|
||||
|
||||
if (listVerticalPadding == null && this.listRef != null) {
|
||||
const listStyle = global.getComputedStyle(this.listRef);
|
||||
const paddingBottom = Number(listStyle.getPropertyValue('padding-bottom').replace('px', ''));
|
||||
const paddingTop = Number(listStyle.getPropertyValue('padding-top').replace('px', ''));
|
||||
|
||||
this.setState({
|
||||
listVerticalPadding: paddingBottom + paddingTop,
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<li
|
||||
className={topSpacerClass}
|
||||
ref={(ref) => {
|
||||
this.topSpacerRef = ref;
|
||||
|
||||
if (itemHeight == null && this.topSpacerRef?.nextSibling != null) {
|
||||
this.setState({
|
||||
itemHeight: (this.topSpacerRef.nextSibling as HTMLLIElement).offsetHeight,
|
||||
});
|
||||
}
|
||||
}}
|
||||
style={{height: `${listPadding.top}px`}}
|
||||
/>
|
||||
{list}
|
||||
<li className={bottomSpacerClass} style={{height: `${listPadding.bottom}px`}} />
|
||||
</ul>
|
||||
);
|
||||
|
||||
return (
|
||||
<CustomScrollbars
|
||||
className={scrollContainerClass}
|
||||
getVerticalThumb={getVerticalThumb}
|
||||
onScrollStart={this.handleScrollStart}
|
||||
onScrollStop={this.handleScrollStop}
|
||||
ref={(ref) => {
|
||||
this.scrollbarRef = ref;
|
||||
}}
|
||||
scrollHandler={this.handleScroll}>
|
||||
{children}
|
||||
{listContent}
|
||||
</CustomScrollbars>
|
||||
);
|
||||
}
|
||||
}
|
||||
ListViewport.defaultProps = {
|
||||
children: undefined,
|
||||
onScroll: undefined,
|
||||
};
|
||||
|
||||
export default ListViewport;
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import {defineMessages, WrappedComponentProps} from 'react-intl';
|
||||
|
||||
import ArrowIcon from '../../icons/ArrowIcon';
|
||||
import CustomScrollbars from '../CustomScrollbars';
|
||||
import File from '../../icons/File';
|
||||
import FolderClosedSolid from '../../icons/FolderClosedSolid';
|
||||
import FloodActions from '../../../actions/FloodActions';
|
||||
@@ -28,7 +27,6 @@ const MESSAGES = defineMessages({
|
||||
interface FilesystemBrowserProps extends WrappedComponentProps {
|
||||
selectable?: 'files' | 'directories';
|
||||
directory: string;
|
||||
maxHeight?: number | string | null;
|
||||
onItemSelection?: (newDestination: string, isDirectory?: boolean) => void;
|
||||
}
|
||||
|
||||
@@ -112,7 +110,7 @@ class FilesystemBrowser extends React.PureComponent<FilesystemBrowserProps, File
|
||||
};
|
||||
|
||||
render() {
|
||||
const {intl, selectable, maxHeight} = this.props;
|
||||
const {intl, selectable} = this.props;
|
||||
const {directories, errorResponse, files} = this.state;
|
||||
let errorMessage = null;
|
||||
let listItems = null;
|
||||
@@ -198,13 +196,11 @@ class FilesystemBrowser extends React.PureComponent<FilesystemBrowserProps, File
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomScrollbars autoHeight autoHeightMin={0} autoHeightMax={maxHeight || undefined}>
|
||||
<div className="filesystem__directory-list context-menu__items__padding-surrogate">
|
||||
{parentDirectory}
|
||||
{errorMessage}
|
||||
{listItems}
|
||||
</div>
|
||||
</CustomScrollbars>
|
||||
<div className="filesystem__directory-list context-menu__items__padding-surrogate">
|
||||
{parentDirectory}
|
||||
{errorMessage}
|
||||
{listItems}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,16 +198,10 @@ class FilesystemBrowserTextbox extends React.Component<FilesystemBrowserTextboxP
|
||||
setRef={(ref) => {
|
||||
this.contextMenuNodeRef = ref;
|
||||
}}
|
||||
scrolling={false}
|
||||
triggerRef={this.textboxRef}>
|
||||
<FilesystemBrowser
|
||||
directory={destination}
|
||||
intl={intl}
|
||||
maxHeight={
|
||||
this.contextMenuInstanceRef &&
|
||||
this.contextMenuInstanceRef.dropdownStyle &&
|
||||
this.contextMenuInstanceRef.dropdownStyle.maxHeight
|
||||
}
|
||||
selectable={selectable}
|
||||
onItemSelection={this.handleItemSelection}
|
||||
/>
|
||||
|
||||
@@ -9,7 +9,6 @@ import type {Notification} from '@shared/types/Notification';
|
||||
import FloodActions from '../../actions/FloodActions';
|
||||
import ChevronLeftIcon from '../icons/ChevronLeftIcon';
|
||||
import ChevronRightIcon from '../icons/ChevronRightIcon';
|
||||
import CustomScrollbars from '../general/CustomScrollbars';
|
||||
import LoadingIndicatorDots from '../icons/LoadingIndicatorDots';
|
||||
import NotificationIcon from '../icons/NotificationIcon';
|
||||
import NotificationStore from '../../stores/NotificationStore';
|
||||
@@ -221,11 +220,9 @@ class NotificationsButton extends React.Component<WrappedComponentProps, Notific
|
||||
<div className={notificationsWrapperClasses}>
|
||||
{this.getTopToolbar()}
|
||||
<div className="notifications__loading-indicator">{loadingIndicatorIcon}</div>
|
||||
<CustomScrollbars autoHeight autoHeightMin={0} autoHeightMax={300} inverted>
|
||||
<ul className="notifications__list tooltip__content--padding-surrogate">
|
||||
{notifications.map(this.getNotification)}
|
||||
</ul>
|
||||
</CustomScrollbars>
|
||||
<ul className="notifications__list tooltip__content--padding-surrogate">
|
||||
{notifications.map(this.getNotification)}
|
||||
</ul>
|
||||
{this.getBottomToolbar()}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
import CustomScrollbars from '../general/CustomScrollbars';
|
||||
import FeedsButton from './FeedsButton';
|
||||
import LogoutButton from './LogoutButton';
|
||||
import NotificationsButton from './NotificationsButton';
|
||||
@@ -16,7 +15,7 @@ import DiskUsage from './DiskUsage';
|
||||
|
||||
const Sidebar = () => {
|
||||
return (
|
||||
<CustomScrollbars className="application__sidebar" inverted>
|
||||
<div className="application__sidebar">
|
||||
<SidebarActions>
|
||||
<SpeedLimitDropdown />
|
||||
<SettingsButton />
|
||||
@@ -30,7 +29,7 @@ const Sidebar = () => {
|
||||
<TagFilters />
|
||||
<TrackerFilters />
|
||||
<DiskUsage />
|
||||
</CustomScrollbars>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ const pointerDownStyles = `
|
||||
`;
|
||||
|
||||
interface TableHeadingProps extends WrappedComponentProps {
|
||||
scrollOffset: number;
|
||||
onCellClick: (column: TorrentListColumn) => void;
|
||||
onWidthsChange: (column: TorrentListColumn, width: number) => void;
|
||||
setRef?: React.RefCallback<HTMLDivElement>;
|
||||
}
|
||||
|
||||
@observer
|
||||
@@ -26,7 +26,6 @@ class TableHeading extends React.PureComponent<TableHeadingProps> {
|
||||
lastPointerX: number | null = null;
|
||||
tableHeading: HTMLDivElement | null = null;
|
||||
resizeLine: HTMLDivElement | null = null;
|
||||
tableHeadingX = 0;
|
||||
|
||||
constructor(props: TableHeadingProps) {
|
||||
super(props);
|
||||
@@ -36,12 +35,6 @@ class TableHeading extends React.PureComponent<TableHeadingProps> {
|
||||
this.handlePointerMove = this.handlePointerMove.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.tableHeading != null) {
|
||||
this.tableHeadingX = this.tableHeading.getBoundingClientRect().left;
|
||||
}
|
||||
}
|
||||
|
||||
getHeadingElements() {
|
||||
const {intl, onCellClick} = this.props;
|
||||
|
||||
@@ -103,11 +96,10 @@ class TableHeading extends React.PureComponent<TableHeadingProps> {
|
||||
if (nextCellWidth > 20) {
|
||||
this.focusedCellWidth = nextCellWidth;
|
||||
this.lastPointerX = event.clientX;
|
||||
if (this.resizeLine != null) {
|
||||
this.resizeLine.style.transform = `translateX(${Math.max(
|
||||
0,
|
||||
event.clientX - this.tableHeadingX + this.props.scrollOffset,
|
||||
)}px)`;
|
||||
if (this.resizeLine != null && this.tableHeading != null) {
|
||||
this.resizeLine.style.transform = `translate(${Math.max(0, event.clientX)}px, ${
|
||||
this.tableHeading.getBoundingClientRect().top
|
||||
}px)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,7 +125,7 @@ class TableHeading extends React.PureComponent<TableHeadingProps> {
|
||||
}
|
||||
|
||||
handlePointerDown(event: React.PointerEvent, slug: TorrentListColumn, width: number) {
|
||||
if (!this.isPointerDown && this.resizeLine != null) {
|
||||
if (!this.isPointerDown && this.resizeLine != null && this.tableHeading != null) {
|
||||
global.document.addEventListener('pointerup', this.handlePointerUp);
|
||||
global.document.addEventListener('pointermove', this.handlePointerMove);
|
||||
UIStore.addGlobalStyle(pointerDownStyles);
|
||||
@@ -142,20 +134,24 @@ class TableHeading extends React.PureComponent<TableHeadingProps> {
|
||||
this.focusedCellWidth = width;
|
||||
this.isPointerDown = true;
|
||||
this.lastPointerX = event.clientX;
|
||||
this.resizeLine.style.transform = `translateX(${Math.max(
|
||||
0,
|
||||
event.clientX - this.tableHeadingX + this.props.scrollOffset,
|
||||
)}px)`;
|
||||
this.resizeLine.style.transform = `translate(${Math.max(0, event.clientX)}px, ${
|
||||
this.tableHeading.getBoundingClientRect().top
|
||||
}px)`;
|
||||
this.resizeLine.style.opacity = '1';
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {setRef} = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="table__row table__row--heading"
|
||||
ref={(ref) => {
|
||||
this.tableHeading = ref;
|
||||
if (setRef != null) {
|
||||
setRef(ref);
|
||||
}
|
||||
}}>
|
||||
{this.getHeadingElements()}
|
||||
<div className="table__cell table__heading table__heading--fill" />
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import {FormattedMessage, injectIntl, WrappedComponentProps} from 'react-intl';
|
||||
import debounce from 'lodash/debounce';
|
||||
import Dropzone from 'react-dropzone';
|
||||
import {observer} from 'mobx-react';
|
||||
import {Scrollbars} from 'react-custom-scrollbars';
|
||||
import {observable, reaction, runInAction} from 'mobx';
|
||||
import {observable, reaction} from 'mobx';
|
||||
import React from 'react';
|
||||
|
||||
import defaultFloodSettings from '@shared/constants/defaultFloodSettings';
|
||||
@@ -13,7 +11,6 @@ import type {TorrentProperties} from '@shared/types/Torrent';
|
||||
|
||||
import {Button} from '../../ui';
|
||||
import ClientStatusStore from '../../stores/ClientStatusStore';
|
||||
import CustomScrollbars from '../general/CustomScrollbars';
|
||||
import Files from '../icons/Files';
|
||||
import GlobalContextMenuMountPoint from '../general/GlobalContextMenuMountPoint';
|
||||
import ListViewport from '../general/ListViewport';
|
||||
@@ -59,18 +56,11 @@ const getEmptyTorrentListNotification = (): React.ReactNode => {
|
||||
const handleClick = (torrent: TorrentProperties, event: React.MouseEvent) =>
|
||||
UIActions.handleTorrentClick({hash: torrent.hash, event});
|
||||
const handleDoubleClick = (torrent: TorrentProperties) => TorrentListContextMenu.handleDetailsClick(torrent);
|
||||
|
||||
interface TorrentListStates {
|
||||
tableScrollLeft: number;
|
||||
}
|
||||
|
||||
@observer
|
||||
class TorrentList extends React.Component<WrappedComponentProps, TorrentListStates> {
|
||||
class TorrentList extends React.Component<WrappedComponentProps> {
|
||||
listContainer: HTMLDivElement | null = null;
|
||||
listViewportRef: ListViewport | null = null;
|
||||
horizontalScrollRef: Scrollbars | null = null;
|
||||
verticalScrollbarThumb: HTMLDivElement | null = null;
|
||||
lastScrollLeft = 0;
|
||||
listHeaderRef: HTMLDivElement | null = null;
|
||||
listViewportRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
torrentListViewportSize = observable.object<{width: number; height: number}>({
|
||||
width: window.innerWidth,
|
||||
@@ -80,48 +70,17 @@ class TorrentList extends React.Component<WrappedComponentProps, TorrentListStat
|
||||
constructor(props: WrappedComponentProps) {
|
||||
super(props);
|
||||
|
||||
reaction(
|
||||
() => SettingStore.floodSettings.torrentListViewSize,
|
||||
(currentTorrentListViewSize, prevTorrentListViewSize) => {
|
||||
const isCondensed = currentTorrentListViewSize === 'condensed';
|
||||
const wasCondensed = prevTorrentListViewSize === 'condensed';
|
||||
|
||||
if (this.verticalScrollbarThumb != null) {
|
||||
if (!isCondensed && wasCondensed) {
|
||||
this.updateVerticalThumbPosition(0);
|
||||
} else if (isCondensed && this.listContainer != null) {
|
||||
this.updateVerticalThumbPosition(
|
||||
(SettingStore.totalCellWidth - this.listContainer.clientWidth) * -1 + this.lastScrollLeft,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTorrentListViewSize !== prevTorrentListViewSize && this.listViewportRef != null) {
|
||||
this.listViewportRef.measureItemHeight();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
reaction(() => TorrentFilterStore.filters, this.handleTorrentFilterChange);
|
||||
|
||||
this.state = {
|
||||
tableScrollLeft: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('resize', this.updateTorrentListViewSize);
|
||||
}
|
||||
handleColumnWidthChange = (column: TorrentListColumn, width: number) => {
|
||||
const {torrentListColumnWidths = defaultFloodSettings.torrentListColumnWidths} = SettingStore.floodSettings;
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.horizontalScrollRef != null && this.torrentListViewportSize.width === 0) {
|
||||
this.updateTorrentListViewSize();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.updateTorrentListViewSize);
|
||||
}
|
||||
SettingActions.saveSetting('torrentListColumnWidths', {
|
||||
...torrentListColumnWidths,
|
||||
[column]: width,
|
||||
});
|
||||
};
|
||||
|
||||
handleContextMenuClick = (torrent: TorrentProperties, event: React.MouseEvent | React.TouchEvent) => {
|
||||
if (event.cancelable === true) {
|
||||
@@ -184,70 +143,14 @@ class TorrentList extends React.Component<WrappedComponentProps, TorrentListStat
|
||||
};
|
||||
|
||||
handleTorrentFilterChange = () => {
|
||||
if (this.listViewportRef != null) {
|
||||
this.listViewportRef.scrollToTop();
|
||||
if (this.listViewportRef.current != null) {
|
||||
this.listViewportRef.current.scrollTop = 0;
|
||||
}
|
||||
};
|
||||
|
||||
getVerticalScrollbarThumb: React.StatelessComponent = (props) => {
|
||||
return (
|
||||
<div {...props}>
|
||||
<div
|
||||
className="scrollbars__thumb scrollbars__thumb--horizontal scrollbars__thumb--surrogate"
|
||||
ref={(ref) => {
|
||||
this.verticalScrollbarThumb = ref;
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
handleHorizontalScroll = (event: React.UIEvent) => {
|
||||
if (this.verticalScrollbarThumb != null) {
|
||||
const {clientWidth, scrollLeft, scrollWidth} = event.target as HTMLElement;
|
||||
this.lastScrollLeft = scrollLeft;
|
||||
this.updateVerticalThumbPosition((scrollWidth - clientWidth) * -1 + scrollLeft);
|
||||
}
|
||||
};
|
||||
|
||||
handleHorizontalScrollStop = () => {
|
||||
this.setState({tableScrollLeft: this.lastScrollLeft});
|
||||
};
|
||||
|
||||
handleColumnWidthChange = (column: TorrentListColumn, width: number) => {
|
||||
const {torrentListColumnWidths = defaultFloodSettings.torrentListColumnWidths} = SettingStore.floodSettings;
|
||||
|
||||
SettingActions.saveSetting('torrentListColumnWidths', {
|
||||
...torrentListColumnWidths,
|
||||
[column]: width,
|
||||
});
|
||||
};
|
||||
|
||||
/* eslint-disable react/sort-comp */
|
||||
updateTorrentListViewSize = debounce(
|
||||
() => {
|
||||
runInAction(() => {
|
||||
this.torrentListViewportSize.height = this.horizontalScrollRef?.getClientHeight() || window.innerHeight;
|
||||
this.torrentListViewportSize.width = this.horizontalScrollRef?.getClientWidth() || window.innerWidth;
|
||||
});
|
||||
if (SettingStore.floodSettings.torrentListViewSize === 'condensed') {
|
||||
if (this.verticalScrollbarThumb != null && this.listContainer != null) {
|
||||
this.updateVerticalThumbPosition(
|
||||
(SettingStore.totalCellWidth - this.listContainer.clientWidth) * -1 + this.lastScrollLeft,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
100,
|
||||
{trailing: true},
|
||||
);
|
||||
/* eslint-enable react/sort-comp */
|
||||
|
||||
updateVerticalThumbPosition = (offset: number) => {
|
||||
if (this.verticalScrollbarThumb != null) {
|
||||
this.verticalScrollbarThumb.style.transform = `translateX(${offset}px)`;
|
||||
handleViewportScroll = () => {
|
||||
if (this.listHeaderRef != null && this.listViewportRef.current != null) {
|
||||
this.listHeaderRef.scrollLeft = this.listViewportRef.current.scrollLeft;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -285,23 +188,9 @@ class TorrentList extends React.Component<WrappedComponentProps, TorrentListStat
|
||||
} else if (isListEmpty || torrents == null) {
|
||||
content = getEmptyTorrentListNotification();
|
||||
} else {
|
||||
content = (
|
||||
<ListViewport
|
||||
getVerticalThumb={this.getVerticalScrollbarThumb}
|
||||
itemRenderer={this.renderListItem}
|
||||
listClass="torrent__list"
|
||||
listLength={torrents.length}
|
||||
ref={(ref) => {
|
||||
this.listViewportRef = ref;
|
||||
}}
|
||||
scrollContainerClass="torrent__list__scrollbars--vertical"
|
||||
/>
|
||||
);
|
||||
|
||||
if (isCondensed) {
|
||||
torrentListHeading = (
|
||||
<TableHeading
|
||||
scrollOffset={this.state.tableScrollLeft}
|
||||
onCellClick={(property: TorrentListColumn) => {
|
||||
const currentSort = SettingStore.floodSettings.sortTorrents;
|
||||
|
||||
@@ -319,12 +208,23 @@ class TorrentList extends React.Component<WrappedComponentProps, TorrentListStat
|
||||
SettingActions.saveSetting('sortTorrents', sortBy);
|
||||
}}
|
||||
onWidthsChange={this.handleColumnWidthChange}
|
||||
setRef={(ref) => {
|
||||
this.listHeaderRef = ref;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const listViewportWidth = this.torrentListViewportSize.width;
|
||||
content = (
|
||||
<ListViewport
|
||||
itemRenderer={this.renderListItem}
|
||||
listClass="torrent__list"
|
||||
listLength={torrents.length}
|
||||
onScroll={this.handleViewportScroll}
|
||||
ref={this.listViewportRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropzone onDrop={this.handleFileDrop} noClick noKeyboard>
|
||||
@@ -335,26 +235,11 @@ class TorrentList extends React.Component<WrappedComponentProps, TorrentListStat
|
||||
ref={(ref) => {
|
||||
this.listContainer = ref;
|
||||
}}>
|
||||
<CustomScrollbars
|
||||
className="torrent__list__scrollbars--horizontal"
|
||||
onScrollStop={this.handleHorizontalScrollStop}
|
||||
nativeScrollHandler={this.handleHorizontalScroll}
|
||||
ref={(ref) => {
|
||||
this.horizontalScrollRef = ref;
|
||||
}}>
|
||||
<div
|
||||
className="torrent__list__wrapper"
|
||||
style={
|
||||
isCondensed && !isListEmpty
|
||||
? {width: `${Math.max(listViewportWidth, SettingStore.totalCellWidth)}px`}
|
||||
: {}
|
||||
}>
|
||||
<GlobalContextMenuMountPoint id="torrent-list-item" />
|
||||
{torrentListHeading}
|
||||
{content}
|
||||
</div>
|
||||
</CustomScrollbars>
|
||||
|
||||
<div className="torrent__list__wrapper">
|
||||
<GlobalContextMenuMountPoint id="torrent-list-item" />
|
||||
{torrentListHeading}
|
||||
{content}
|
||||
</div>
|
||||
<div className="dropzone__overlay">
|
||||
<div className="dropzone__copy">
|
||||
<div className="dropzone__icon">
|
||||
|
||||
@@ -5,3 +5,30 @@ body {
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 4px;
|
||||
background: #8d8d8d;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
border-radius: 4px;
|
||||
background: lighten(#8d8d8d, 10%);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
flex: 1;
|
||||
min-width: 240px;
|
||||
max-width: 250px;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: overlay;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
transition: transform 0.2s;
|
||||
|
||||
@@ -9,6 +9,7 @@ $table--heading--resize--indicator--width: 1px;
|
||||
@include theme('color', 'table--heading--color');
|
||||
display: flex;
|
||||
height: 24px;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
z-index: 1;
|
||||
@@ -121,7 +122,7 @@ $table--heading--resize--indicator--width: 1px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
transition: opacity 0.125s;
|
||||
will-change: opacity, transform;
|
||||
|
||||
@@ -475,9 +475,11 @@ $more-info--border: $textbox-repeater--button--border;
|
||||
@include theme('border-top', 'torrent--border');
|
||||
display: flex;
|
||||
height: 30px;
|
||||
min-width: max-content;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
|
||||
&:nth-child(0n + 2) {
|
||||
&:nth-child(0n + 1) {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
@include theme('background', 'torrent-list--background');
|
||||
@include theme('box-shadow', 'torrent-list--border');
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex: 1 1 0px;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
.loading-indicator {
|
||||
@@ -52,11 +53,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__viewport {
|
||||
overflow-x: auto;
|
||||
overflow-y: overlay;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
list-style: none;
|
||||
opacity: 1;
|
||||
@@ -101,7 +109,6 @@
|
||||
box-shadow: -1px 0 $torrent-list--border;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
flex: 0 1 100%;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -477,6 +477,7 @@ declare const styles: {
|
||||
readonly torrent__list: string;
|
||||
readonly 'torrent__list__scrollbars--horizontal': string;
|
||||
readonly 'torrent__list__scrollbars--vertical': string;
|
||||
readonly torrent__list__viewport: string;
|
||||
readonly torrent__list__wrapper: string;
|
||||
readonly 'torrent__list--loading-enter': string;
|
||||
readonly 'torrent__list--loading-enter-active': string;
|
||||
|
||||
Generated
-158
@@ -48,7 +48,6 @@
|
||||
"@types/passport": "^1.0.4",
|
||||
"@types/passport-jwt": "^3.0.3",
|
||||
"@types/react": "^16.9.52",
|
||||
"@types/react-custom-scrollbars": "^4.0.7",
|
||||
"@types/react-dnd-multi-backend": "^6.0.0",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-measure": "^2.0.6",
|
||||
@@ -128,7 +127,6 @@
|
||||
"prettier": "^2.1.2",
|
||||
"promise": "^8.1.0",
|
||||
"react": "^16.14.0",
|
||||
"react-custom-scrollbars": "^4.2.1",
|
||||
"react-dev-utils": "^10.2.1",
|
||||
"react-dnd": "^11.1.3",
|
||||
"react-dnd-html5-backend": "^11.1.3",
|
||||
@@ -3105,15 +3103,6 @@
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-custom-scrollbars": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-custom-scrollbars/-/react-custom-scrollbars-4.0.7.tgz",
|
||||
"integrity": "sha512-4QPZdwd+wmzWq9TyNSA/4MZFYvlQn1GlEFFkpFx8VSs13gR/L+hQne0vFnbzwlQmGG7OksthkoVpYxWJjzz95w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dnd-multi-backend": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dnd-multi-backend/-/react-dnd-multi-backend-6.0.0.tgz",
|
||||
@@ -3806,12 +3795,6 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/add-px-to-style": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/add-px-to-style/-/add-px-to-style-1.0.0.tgz",
|
||||
"integrity": "sha1-0ME1RB+oAUqBN5BFMQlvZ/KPJjo=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/address": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz",
|
||||
@@ -7933,17 +7916,6 @@
|
||||
"utila": "~0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-css": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-css/-/dom-css-2.1.0.tgz",
|
||||
"integrity": "sha1-/bwtWgFdCj4YcuEUcrvQ57nmogI=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"add-px-to-style": "1.0.0",
|
||||
"prefix-style": "2.0.1",
|
||||
"to-camel-case": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz",
|
||||
@@ -20189,12 +20161,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prefix-style": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prefix-style/-/prefix-style-2.0.1.tgz",
|
||||
"integrity": "sha1-ZrupqHDP2jCKXcIOhekSCTLJWgY=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
@@ -20470,15 +20436,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/raf": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
||||
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"performance-now": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
@@ -20558,21 +20515,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-custom-scrollbars": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz",
|
||||
"integrity": "sha1-gw/ZUCkn6X6KeMIIaBOJmyqLZts=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"dom-css": "^2.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"raf": "^3.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^0.14.0 || ^15.0.0 || ^16.0.0",
|
||||
"react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dev-utils": {
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz",
|
||||
@@ -25009,15 +24951,6 @@
|
||||
"integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/to-camel-case": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-camel-case/-/to-camel-case-1.0.0.tgz",
|
||||
"integrity": "sha1-GlYFSy+daWKYzmamCJcyK29CPkY=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"to-space-case": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
@@ -25027,12 +24960,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-no-case": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz",
|
||||
"integrity": "sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/to-object-path": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
|
||||
@@ -25084,15 +25011,6 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-space-case": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-space-case/-/to-space-case-1.0.0.tgz",
|
||||
"integrity": "sha1-sFLar7Gysp3HcM6gFj5ewOvJ/Bc=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"to-no-case": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
@@ -30859,15 +30777,6 @@
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"@types/react-custom-scrollbars": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-custom-scrollbars/-/react-custom-scrollbars-4.0.7.tgz",
|
||||
"integrity": "sha512-4QPZdwd+wmzWq9TyNSA/4MZFYvlQn1GlEFFkpFx8VSs13gR/L+hQne0vFnbzwlQmGG7OksthkoVpYxWJjzz95w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-dnd-multi-backend": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dnd-multi-backend/-/react-dnd-multi-backend-6.0.0.tgz",
|
||||
@@ -31456,12 +31365,6 @@
|
||||
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
|
||||
"dev": true
|
||||
},
|
||||
"add-px-to-style": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/add-px-to-style/-/add-px-to-style-1.0.0.tgz",
|
||||
"integrity": "sha1-0ME1RB+oAUqBN5BFMQlvZ/KPJjo=",
|
||||
"dev": true
|
||||
},
|
||||
"address": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz",
|
||||
@@ -34865,17 +34768,6 @@
|
||||
"utila": "~0.4"
|
||||
}
|
||||
},
|
||||
"dom-css": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-css/-/dom-css-2.1.0.tgz",
|
||||
"integrity": "sha1-/bwtWgFdCj4YcuEUcrvQ57nmogI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"add-px-to-style": "1.0.0",
|
||||
"prefix-style": "2.0.1",
|
||||
"to-camel-case": "1.0.0"
|
||||
}
|
||||
},
|
||||
"dom-helpers": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz",
|
||||
@@ -44676,12 +44568,6 @@
|
||||
"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
|
||||
"dev": true
|
||||
},
|
||||
"prefix-style": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prefix-style/-/prefix-style-2.0.1.tgz",
|
||||
"integrity": "sha1-ZrupqHDP2jCKXcIOhekSCTLJWgY=",
|
||||
"dev": true
|
||||
},
|
||||
"prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
@@ -44909,15 +44795,6 @@
|
||||
"integrity": "sha512-J95OVUiS4b8qqmpqhCodN8yPpHG2mpZUPQ8tDGyIY0VhM+kBHszOuvsMJVGNQ1OH2BnTFbqz45i+2jGpDw9H0w==",
|
||||
"dev": true
|
||||
},
|
||||
"raf": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
||||
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"performance-now": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
@@ -44987,17 +44864,6 @@
|
||||
"prop-types": "^15.6.2"
|
||||
}
|
||||
},
|
||||
"react-custom-scrollbars": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz",
|
||||
"integrity": "sha1-gw/ZUCkn6X6KeMIIaBOJmyqLZts=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dom-css": "^2.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"raf": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"react-dev-utils": {
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz",
|
||||
@@ -48618,27 +48484,12 @@
|
||||
"integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
|
||||
"dev": true
|
||||
},
|
||||
"to-camel-case": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-camel-case/-/to-camel-case-1.0.0.tgz",
|
||||
"integrity": "sha1-GlYFSy+daWKYzmamCJcyK29CPkY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"to-space-case": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
|
||||
"dev": true
|
||||
},
|
||||
"to-no-case": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz",
|
||||
"integrity": "sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo=",
|
||||
"dev": true
|
||||
},
|
||||
"to-object-path": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
|
||||
@@ -48680,15 +48531,6 @@
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"to-space-case": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-space-case/-/to-space-case-1.0.0.tgz",
|
||||
"integrity": "sha1-sFLar7Gysp3HcM6gFj5ewOvJ/Bc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"to-no-case": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
|
||||
@@ -87,7 +87,6 @@
|
||||
"@types/passport": "^1.0.4",
|
||||
"@types/passport-jwt": "^3.0.3",
|
||||
"@types/react": "^16.9.52",
|
||||
"@types/react-custom-scrollbars": "^4.0.7",
|
||||
"@types/react-dnd-multi-backend": "^6.0.0",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-measure": "^2.0.6",
|
||||
@@ -167,7 +166,6 @@
|
||||
"prettier": "^2.1.2",
|
||||
"promise": "^8.1.0",
|
||||
"react": "^16.14.0",
|
||||
"react-custom-scrollbars": "^4.2.1",
|
||||
"react-dev-utils": "^10.2.1",
|
||||
"react-dnd": "^11.1.3",
|
||||
"react-dnd-html5-backend": "^11.1.3",
|
||||
|
||||
Reference in New Issue
Block a user