Added series index selection
This commit is contained in:
parent
5aad84dba4
commit
815a16d5cf
|
@ -0,0 +1,170 @@
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import areAllSelected from 'Utilities/Table/areAllSelected';
|
||||||
|
import selectAll from 'Utilities/Table/selectAll';
|
||||||
|
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||||
|
import ModelBase from './ModelBase';
|
||||||
|
|
||||||
|
export enum SelectActionType {
|
||||||
|
Reset,
|
||||||
|
SelectAll,
|
||||||
|
UnselectAll,
|
||||||
|
ToggleSelected,
|
||||||
|
RemoveItem,
|
||||||
|
UpdateItems,
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelectedState = Record<number, boolean>;
|
||||||
|
|
||||||
|
interface SelectState {
|
||||||
|
selectedState: SelectedState;
|
||||||
|
lastToggled: number | null;
|
||||||
|
allSelected: boolean;
|
||||||
|
allUnselected: boolean;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
items: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelectAction =
|
||||||
|
| { type: SelectActionType.Reset }
|
||||||
|
| { type: SelectActionType.SelectAll }
|
||||||
|
| { type: SelectActionType.UnselectAll }
|
||||||
|
| {
|
||||||
|
type: SelectActionType.ToggleSelected;
|
||||||
|
id: number;
|
||||||
|
isSelected: boolean;
|
||||||
|
shiftKey: boolean;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: SelectActionType.RemoveItem;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: SelectActionType.UpdateItems;
|
||||||
|
items: ModelBase[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type Dispatch = (action: SelectAction) => void;
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
selectedState: {},
|
||||||
|
lastToggled: null,
|
||||||
|
allSelected: false,
|
||||||
|
allUnselected: true,
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SelectProviderOptions<T extends ModelBase> {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
children: any;
|
||||||
|
isSelectMode: boolean;
|
||||||
|
items: Array<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedState(items: ModelBase[], existingState: SelectedState) {
|
||||||
|
return items.reduce((acc: SelectedState, item) => {
|
||||||
|
const id = item.id;
|
||||||
|
|
||||||
|
acc[id] = existingState[id] ?? false;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Can this be reused?
|
||||||
|
|
||||||
|
const SelectContext = React.createContext<[SelectState, Dispatch] | undefined>(
|
||||||
|
cloneDeep(undefined)
|
||||||
|
);
|
||||||
|
|
||||||
|
function selectReducer(state: SelectState, action: SelectAction): SelectState {
|
||||||
|
const { items, selectedState } = state;
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case SelectActionType.Reset: {
|
||||||
|
return cloneDeep(initialState);
|
||||||
|
}
|
||||||
|
case SelectActionType.SelectAll: {
|
||||||
|
return {
|
||||||
|
items,
|
||||||
|
...selectAll(selectedState, true),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case SelectActionType.UnselectAll: {
|
||||||
|
return {
|
||||||
|
items,
|
||||||
|
...selectAll(selectedState, false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case SelectActionType.ToggleSelected: {
|
||||||
|
var result = {
|
||||||
|
items,
|
||||||
|
...toggleSelected(
|
||||||
|
state,
|
||||||
|
items,
|
||||||
|
action.id,
|
||||||
|
action.isSelected,
|
||||||
|
action.shiftKey
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
case SelectActionType.UpdateItems: {
|
||||||
|
const nextSelectedState = getSelectedState(action.items, selectedState);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...areAllSelected(nextSelectedState),
|
||||||
|
selectedState: nextSelectedState,
|
||||||
|
items,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(`Unhandled action type: ${action.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SelectProvider<T extends ModelBase>(
|
||||||
|
props: SelectProviderOptions<T>
|
||||||
|
) {
|
||||||
|
const { isSelectMode, items } = props;
|
||||||
|
const selectedState = getSelectedState(items, {});
|
||||||
|
|
||||||
|
const [state, dispatch] = React.useReducer(selectReducer, {
|
||||||
|
selectedState,
|
||||||
|
lastToggled: null,
|
||||||
|
allSelected: false,
|
||||||
|
allUnselected: true,
|
||||||
|
items,
|
||||||
|
});
|
||||||
|
|
||||||
|
const value: [SelectState, Dispatch] = [state, dispatch];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isSelectMode) {
|
||||||
|
dispatch({ type: SelectActionType.Reset });
|
||||||
|
}
|
||||||
|
}, [isSelectMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch({ type: SelectActionType.UpdateItems, items });
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SelectContext.Provider value={value}>
|
||||||
|
{props.children}
|
||||||
|
</SelectContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSelect() {
|
||||||
|
const context = React.useContext(SelectContext);
|
||||||
|
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useSelect must be used within a SelectProvider');
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
|
@ -15,7 +15,8 @@ import {
|
||||||
faHdd as farHdd,
|
faHdd as farHdd,
|
||||||
faKeyboard as farKeyboard,
|
faKeyboard as farKeyboard,
|
||||||
faObjectGroup as farObjectGroup,
|
faObjectGroup as farObjectGroup,
|
||||||
faObjectUngroup as farObjectUngroup
|
faObjectUngroup as farObjectUngroup,
|
||||||
|
faSquare as farSquare
|
||||||
} from '@fortawesome/free-regular-svg-icons';
|
} from '@fortawesome/free-regular-svg-icons';
|
||||||
//
|
//
|
||||||
// Solid
|
// Solid
|
||||||
|
@ -83,6 +84,8 @@ import {
|
||||||
faSortDown as fasSortDown,
|
faSortDown as fasSortDown,
|
||||||
faSortUp as fasSortUp,
|
faSortUp as fasSortUp,
|
||||||
faSpinner as fasSpinner,
|
faSpinner as fasSpinner,
|
||||||
|
faSquareCheck as fasSquareCheck,
|
||||||
|
faSquareMinus as fasSquareMinus,
|
||||||
faStop as fasStop,
|
faStop as fasStop,
|
||||||
faSync as fasSync,
|
faSync as fasSync,
|
||||||
faTable as fasTable,
|
faTable as fasTable,
|
||||||
|
@ -116,6 +119,7 @@ export const CARET_DOWN = fasCaretDown;
|
||||||
export const CHECK = fasCheck;
|
export const CHECK = fasCheck;
|
||||||
export const CHECK_INDETERMINATE = fasMinus;
|
export const CHECK_INDETERMINATE = fasMinus;
|
||||||
export const CHECK_CIRCLE = fasCheckCircle;
|
export const CHECK_CIRCLE = fasCheckCircle;
|
||||||
|
export const CHECK_SQUARE = fasSquareCheck;
|
||||||
export const CIRCLE = fasCircle;
|
export const CIRCLE = fasCircle;
|
||||||
export const CIRCLE_OUTLINE = farCircle;
|
export const CIRCLE_OUTLINE = farCircle;
|
||||||
export const CLEAR = fasTrashAlt;
|
export const CLEAR = fasTrashAlt;
|
||||||
|
@ -192,6 +196,8 @@ export const SORT = fasSort;
|
||||||
export const SORT_ASCENDING = fasSortUp;
|
export const SORT_ASCENDING = fasSortUp;
|
||||||
export const SORT_DESCENDING = fasSortDown;
|
export const SORT_DESCENDING = fasSortDown;
|
||||||
export const SPINNER = fasSpinner;
|
export const SPINNER = fasSpinner;
|
||||||
|
export const SQUARE = farSquare;
|
||||||
|
export const SQUARE_MINUS = fasSquareMinus;
|
||||||
export const SUBTRACT = fasMinus;
|
export const SUBTRACT = fasMinus;
|
||||||
export const SYSTEM = fasLaptop;
|
export const SYSTEM = fasLaptop;
|
||||||
export const TABLE = fasTable;
|
export const TABLE = fasTable;
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { icons } from 'Helpers/Props';
|
||||||
import DeleteSeriesModal from 'Series/Delete/DeleteSeriesModal';
|
import DeleteSeriesModal from 'Series/Delete/DeleteSeriesModal';
|
||||||
import EditSeriesModalConnector from 'Series/Edit/EditSeriesModalConnector';
|
import EditSeriesModalConnector from 'Series/Edit/EditSeriesModalConnector';
|
||||||
import SeriesIndexProgressBar from 'Series/Index/ProgressBar/SeriesIndexProgressBar';
|
import SeriesIndexProgressBar from 'Series/Index/ProgressBar/SeriesIndexProgressBar';
|
||||||
|
import SeriesIndexPosterSelect from 'Series/Index/Select/SeriesIndexPosterSelect';
|
||||||
import SeriesPoster from 'Series/SeriesPoster';
|
import SeriesPoster from 'Series/SeriesPoster';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import dimensions from 'Styles/Variables/dimensions';
|
import dimensions from 'Styles/Variables/dimensions';
|
||||||
|
@ -35,6 +36,7 @@ interface SeriesIndexOverviewProps {
|
||||||
posterWidth: number;
|
posterWidth: number;
|
||||||
posterHeight: number;
|
posterHeight: number;
|
||||||
rowHeight: number;
|
rowHeight: number;
|
||||||
|
isSelectMode: boolean;
|
||||||
isSmallScreen: boolean;
|
isSmallScreen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +47,7 @@ function SeriesIndexOverview(props: SeriesIndexOverviewProps) {
|
||||||
posterWidth,
|
posterWidth,
|
||||||
posterHeight,
|
posterHeight,
|
||||||
rowHeight,
|
rowHeight,
|
||||||
|
isSelectMode,
|
||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
@ -135,6 +138,10 @@ function SeriesIndexOverview(props: SeriesIndexOverviewProps) {
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.poster}>
|
<div className={styles.poster}>
|
||||||
<div className={styles.posterContainer}>
|
<div className={styles.posterContainer}>
|
||||||
|
{isSelectMode ? (
|
||||||
|
<SeriesIndexPosterSelect seriesId={seriesId} />
|
||||||
|
) : null}
|
||||||
|
|
||||||
{status === 'ended' && (
|
{status === 'ended' && (
|
||||||
<div className={styles.ended} title="Ended" />
|
<div className={styles.ended} title="Ended" />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -27,6 +27,7 @@ interface RowItemData {
|
||||||
posterWidth: number;
|
posterWidth: number;
|
||||||
posterHeight: number;
|
posterHeight: number;
|
||||||
rowHeight: number;
|
rowHeight: number;
|
||||||
|
isSelectMode: boolean;
|
||||||
isSmallScreen: boolean;
|
isSmallScreen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@ interface SeriesIndexOverviewsProps {
|
||||||
jumpToCharacter?: string;
|
jumpToCharacter?: string;
|
||||||
scrollTop?: number;
|
scrollTop?: number;
|
||||||
scrollerRef: React.MutableRefObject<HTMLElement>;
|
scrollerRef: React.MutableRefObject<HTMLElement>;
|
||||||
|
isSelectMode: boolean;
|
||||||
isSmallScreen: boolean;
|
isSmallScreen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +67,14 @@ function getWindowScrollTopPosition() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function SeriesIndexOverviews(props: SeriesIndexOverviewsProps) {
|
function SeriesIndexOverviews(props: SeriesIndexOverviewsProps) {
|
||||||
const { items, sortKey, jumpToCharacter, isSmallScreen, scrollerRef } = props;
|
const {
|
||||||
|
items,
|
||||||
|
sortKey,
|
||||||
|
jumpToCharacter,
|
||||||
|
scrollerRef,
|
||||||
|
isSelectMode,
|
||||||
|
isSmallScreen,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const { size: posterSize, detailedProgressBar } = useSelector(
|
const { size: posterSize, detailedProgressBar } = useSelector(
|
||||||
selectOverviewOptions
|
selectOverviewOptions
|
||||||
|
@ -191,6 +200,7 @@ function SeriesIndexOverviews(props: SeriesIndexOverviewsProps) {
|
||||||
posterWidth,
|
posterWidth,
|
||||||
posterHeight,
|
posterHeight,
|
||||||
rowHeight,
|
rowHeight,
|
||||||
|
isSelectMode,
|
||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { icons } from 'Helpers/Props';
|
||||||
import DeleteSeriesModal from 'Series/Delete/DeleteSeriesModal';
|
import DeleteSeriesModal from 'Series/Delete/DeleteSeriesModal';
|
||||||
import EditSeriesModalConnector from 'Series/Edit/EditSeriesModalConnector';
|
import EditSeriesModalConnector from 'Series/Edit/EditSeriesModalConnector';
|
||||||
import SeriesIndexProgressBar from 'Series/Index/ProgressBar/SeriesIndexProgressBar';
|
import SeriesIndexProgressBar from 'Series/Index/ProgressBar/SeriesIndexProgressBar';
|
||||||
|
import SeriesIndexPosterSelect from 'Series/Index/Select/SeriesIndexPosterSelect';
|
||||||
import SeriesPoster from 'Series/SeriesPoster';
|
import SeriesPoster from 'Series/SeriesPoster';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
|
@ -21,12 +22,13 @@ import styles from './SeriesIndexPoster.css';
|
||||||
interface SeriesIndexPosterProps {
|
interface SeriesIndexPosterProps {
|
||||||
seriesId: number;
|
seriesId: number;
|
||||||
sortKey: string;
|
sortKey: string;
|
||||||
|
isSelectMode: boolean;
|
||||||
posterWidth: number;
|
posterWidth: number;
|
||||||
posterHeight: number;
|
posterHeight: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SeriesIndexPoster(props: SeriesIndexPosterProps) {
|
function SeriesIndexPoster(props: SeriesIndexPosterProps) {
|
||||||
const { seriesId, sortKey, posterWidth, posterHeight } = props;
|
const { seriesId, sortKey, isSelectMode, posterWidth, posterHeight } = props;
|
||||||
|
|
||||||
const { series, qualityProfile, isRefreshingSeries, isSearchingSeries } =
|
const { series, qualityProfile, isRefreshingSeries, isSearchingSeries } =
|
||||||
useSelector(createSeriesIndexItemSelector(props.seriesId));
|
useSelector(createSeriesIndexItemSelector(props.seriesId));
|
||||||
|
@ -120,6 +122,8 @@ function SeriesIndexPoster(props: SeriesIndexPosterProps) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.posterContainer}>
|
<div className={styles.posterContainer}>
|
||||||
|
{isSelectMode ? <SeriesIndexPosterSelect seriesId={seriesId} /> : null}
|
||||||
|
|
||||||
<Label className={styles.controls}>
|
<Label className={styles.controls}>
|
||||||
<SpinnerIconButton
|
<SpinnerIconButton
|
||||||
className={styles.action}
|
className={styles.action}
|
||||||
|
|
|
@ -36,6 +36,7 @@ interface CellItemData {
|
||||||
};
|
};
|
||||||
items: Series[];
|
items: Series[];
|
||||||
sortKey: string;
|
sortKey: string;
|
||||||
|
isSelectMode: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SeriesIndexPostersProps {
|
interface SeriesIndexPostersProps {
|
||||||
|
@ -45,6 +46,7 @@ interface SeriesIndexPostersProps {
|
||||||
jumpToCharacter?: string;
|
jumpToCharacter?: string;
|
||||||
scrollTop?: number;
|
scrollTop?: number;
|
||||||
scrollerRef: React.MutableRefObject<HTMLElement>;
|
scrollerRef: React.MutableRefObject<HTMLElement>;
|
||||||
|
isSelectMode: boolean;
|
||||||
isSmallScreen: boolean;
|
isSmallScreen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,10 +65,8 @@ const Cell: React.FC<GridChildComponentProps<CellItemData>> = ({
|
||||||
style,
|
style,
|
||||||
data,
|
data,
|
||||||
}) => {
|
}) => {
|
||||||
const { layout, items, sortKey } = data;
|
const { layout, items, sortKey, isSelectMode } = data;
|
||||||
|
|
||||||
const { columnCount, padding, posterWidth, posterHeight } = layout;
|
const { columnCount, padding, posterWidth, posterHeight } = layout;
|
||||||
|
|
||||||
const index = rowIndex * columnCount + columnIndex;
|
const index = rowIndex * columnCount + columnIndex;
|
||||||
|
|
||||||
if (index >= items.length) {
|
if (index >= items.length) {
|
||||||
|
@ -85,6 +85,7 @@ const Cell: React.FC<GridChildComponentProps<CellItemData>> = ({
|
||||||
<SeriesIndexPoster
|
<SeriesIndexPoster
|
||||||
seriesId={series.id}
|
seriesId={series.id}
|
||||||
sortKey={sortKey}
|
sortKey={sortKey}
|
||||||
|
isSelectMode={isSelectMode}
|
||||||
posterWidth={posterWidth}
|
posterWidth={posterWidth}
|
||||||
posterHeight={posterHeight}
|
posterHeight={posterHeight}
|
||||||
/>
|
/>
|
||||||
|
@ -97,7 +98,14 @@ function getWindowScrollTopPosition() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SeriesIndexPosters(props: SeriesIndexPostersProps) {
|
export default function SeriesIndexPosters(props: SeriesIndexPostersProps) {
|
||||||
const { scrollerRef, items, sortKey, jumpToCharacter, isSmallScreen } = props;
|
const {
|
||||||
|
scrollerRef,
|
||||||
|
items,
|
||||||
|
sortKey,
|
||||||
|
jumpToCharacter,
|
||||||
|
isSelectMode,
|
||||||
|
isSmallScreen,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const { posterOptions } = useSelector(seriesIndexSelector);
|
const { posterOptions } = useSelector(seriesIndexSelector);
|
||||||
const ref: React.MutableRefObject<Grid> = useRef();
|
const ref: React.MutableRefObject<Grid> = useRef();
|
||||||
|
@ -273,6 +281,7 @@ export default function SeriesIndexPosters(props: SeriesIndexPostersProps) {
|
||||||
},
|
},
|
||||||
items,
|
items,
|
||||||
sortKey,
|
sortKey,
|
||||||
|
isSelectMode,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Cell}
|
{Cell}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
.checkContainer {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
z-index: 3;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--defaultColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
left: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
composes: icon;
|
||||||
|
|
||||||
|
color: var(--sonarrBlue);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.unselected {
|
||||||
|
composes: icon;
|
||||||
|
|
||||||
|
color: var(--white);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--sonarrBlue);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||||
|
import IconButton from 'Components/Link/IconButton';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import styles from './SeriesIndexPosterSelect.css';
|
||||||
|
|
||||||
|
interface SeriesIndexPosterSelectProps {
|
||||||
|
seriesId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SeriesIndexPosterSelect(props: SeriesIndexPosterSelectProps) {
|
||||||
|
const { seriesId } = props;
|
||||||
|
const [selectState, selectDispatch] = useSelect();
|
||||||
|
const isSelected = selectState.selectedState[seriesId];
|
||||||
|
|
||||||
|
const onSelectPress = useCallback(
|
||||||
|
(event) => {
|
||||||
|
const shiftKey = event.nativeEvent.shiftKey;
|
||||||
|
|
||||||
|
selectDispatch({
|
||||||
|
type: SelectActionType.ToggleSelected,
|
||||||
|
id: seriesId,
|
||||||
|
isSelected: !isSelected,
|
||||||
|
shiftKey,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[seriesId, isSelected, selectDispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
className={styles.checkContainer}
|
||||||
|
iconClassName={isSelected ? styles.selected : styles.unselected}
|
||||||
|
name={isSelected ? icons.CHECK_CIRCLE : icons.CIRCLE_OUTLINE}
|
||||||
|
size={20}
|
||||||
|
onPress={onSelectPress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SeriesIndexPosterSelect;
|
|
@ -0,0 +1,35 @@
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||||
|
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
|
||||||
|
function SeriesIndexSelectAllButton() {
|
||||||
|
const [selectState, selectDispatch] = useSelect();
|
||||||
|
const { allSelected, allUnselected } = selectState;
|
||||||
|
|
||||||
|
let icon = icons.SQUARE_MINUS;
|
||||||
|
|
||||||
|
if (allSelected) {
|
||||||
|
icon = icons.CHECK_SQUARE;
|
||||||
|
} else if (allUnselected) {
|
||||||
|
icon = icons.SQUARE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPress = useCallback(() => {
|
||||||
|
selectDispatch({
|
||||||
|
type: allSelected
|
||||||
|
? SelectActionType.UnselectAll
|
||||||
|
: SelectActionType.SelectAll,
|
||||||
|
});
|
||||||
|
}, [allSelected, selectDispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageToolbarButton
|
||||||
|
label={allSelected ? 'Unselect All' : 'Select All'}
|
||||||
|
iconName={icon}
|
||||||
|
onPress={onPress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SeriesIndexSelectAllButton;
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { SelectProvider } from 'App/SelectContext';
|
||||||
import { REFRESH_SERIES, RSS_SYNC } from 'Commands/commandNames';
|
import { REFRESH_SERIES, RSS_SYNC } from 'Commands/commandNames';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import PageContent from 'Components/Page/PageContent';
|
import PageContent from 'Components/Page/PageContent';
|
||||||
|
@ -32,6 +33,7 @@ import SeriesIndexOverviewOptionsModal from './Overview/Options/SeriesIndexOverv
|
||||||
import SeriesIndexOverviews from './Overview/SeriesIndexOverviews';
|
import SeriesIndexOverviews from './Overview/SeriesIndexOverviews';
|
||||||
import SeriesIndexPosterOptionsModal from './Posters/Options/SeriesIndexPosterOptionsModal';
|
import SeriesIndexPosterOptionsModal from './Posters/Options/SeriesIndexPosterOptionsModal';
|
||||||
import SeriesIndexPosters from './Posters/SeriesIndexPosters';
|
import SeriesIndexPosters from './Posters/SeriesIndexPosters';
|
||||||
|
import SeriesIndexSelectAllButton from './Select/SeriesIndexSelectAllButton';
|
||||||
import SeriesIndexFooter from './SeriesIndexFooter';
|
import SeriesIndexFooter from './SeriesIndexFooter';
|
||||||
import SeriesIndexTable from './Table/SeriesIndexTable';
|
import SeriesIndexTable from './Table/SeriesIndexTable';
|
||||||
import SeriesIndexTableOptions from './Table/SeriesIndexTableOptions';
|
import SeriesIndexTableOptions from './Table/SeriesIndexTableOptions';
|
||||||
|
@ -53,7 +55,7 @@ interface SeriesIndexProps {
|
||||||
initialScrollTop?: number;
|
initialScrollTop?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SeriesIndex = withScrollPosition((props) => {
|
const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
|
||||||
const {
|
const {
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
|
@ -80,6 +82,7 @@ const SeriesIndex = withScrollPosition((props) => {
|
||||||
const scrollerRef = useRef<HTMLDivElement>();
|
const scrollerRef = useRef<HTMLDivElement>();
|
||||||
const [isOptionsModalOpen, setIsOptionsModalOpen] = useState(false);
|
const [isOptionsModalOpen, setIsOptionsModalOpen] = useState(false);
|
||||||
const [jumpToCharacter, setJumpToCharacter] = useState<string | null>(null);
|
const [jumpToCharacter, setJumpToCharacter] = useState<string | null>(null);
|
||||||
|
const [isSelectMode, setIsSelectMode] = useState(false);
|
||||||
|
|
||||||
const onRefreshSeriesPress = useCallback(() => {
|
const onRefreshSeriesPress = useCallback(() => {
|
||||||
dispatch(
|
dispatch(
|
||||||
|
@ -97,6 +100,10 @@ const SeriesIndex = withScrollPosition((props) => {
|
||||||
);
|
);
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const onSelectModePress = useCallback(() => {
|
||||||
|
setIsSelectMode(!isSelectMode);
|
||||||
|
}, [isSelectMode, setIsSelectMode]);
|
||||||
|
|
||||||
const onTableOptionChange = useCallback(
|
const onTableOptionChange = useCallback(
|
||||||
(payload) => {
|
(payload) => {
|
||||||
dispatch(setSeriesTableOption(payload));
|
dispatch(setSeriesTableOption(payload));
|
||||||
|
@ -194,118 +201,137 @@ const SeriesIndex = withScrollPosition((props) => {
|
||||||
const hasNoSeries = !totalItems;
|
const hasNoSeries = !totalItems;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent>
|
<SelectProvider isSelectMode={isSelectMode} items={items}>
|
||||||
<PageToolbar>
|
<PageContent>
|
||||||
<PageToolbarSection>
|
<PageToolbar>
|
||||||
<PageToolbarButton
|
<PageToolbarSection>
|
||||||
label="Update all"
|
|
||||||
iconName={icons.REFRESH}
|
|
||||||
spinningName={icons.REFRESH}
|
|
||||||
isSpinning={isRefreshingSeries}
|
|
||||||
isDisabled={hasNoSeries}
|
|
||||||
onPress={onRefreshSeriesPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PageToolbarButton
|
|
||||||
label="RSS Sync"
|
|
||||||
iconName={icons.RSS}
|
|
||||||
isSpinning={isRssSyncExecuting}
|
|
||||||
isDisabled={hasNoSeries}
|
|
||||||
onPress={onRssSyncPress}
|
|
||||||
/>
|
|
||||||
</PageToolbarSection>
|
|
||||||
|
|
||||||
<PageToolbarSection alignContent={align.RIGHT} collapseButtons={false}>
|
|
||||||
{view === 'table' ? (
|
|
||||||
<TableOptionsModalWrapper
|
|
||||||
columns={columns}
|
|
||||||
optionsComponent={SeriesIndexTableOptions}
|
|
||||||
onTableOptionChange={onTableOptionChange}
|
|
||||||
>
|
|
||||||
<PageToolbarButton label="Options" iconName={icons.TABLE} />
|
|
||||||
</TableOptionsModalWrapper>
|
|
||||||
) : (
|
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label="Options"
|
label="Update all"
|
||||||
iconName={view === 'posters' ? icons.POSTER : icons.OVERVIEW}
|
iconName={icons.REFRESH}
|
||||||
|
spinningName={icons.REFRESH}
|
||||||
|
isSpinning={isRefreshingSeries}
|
||||||
isDisabled={hasNoSeries}
|
isDisabled={hasNoSeries}
|
||||||
onPress={onOptionsPress}
|
onPress={onRefreshSeriesPress}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
<PageToolbarSeparator />
|
<PageToolbarButton
|
||||||
|
label="RSS Sync"
|
||||||
|
iconName={icons.RSS}
|
||||||
|
isSpinning={isRssSyncExecuting}
|
||||||
|
isDisabled={hasNoSeries}
|
||||||
|
onPress={onRssSyncPress}
|
||||||
|
/>
|
||||||
|
|
||||||
<SeriesIndexViewMenu
|
<PageToolbarSeparator />
|
||||||
view={view}
|
|
||||||
isDisabled={hasNoSeries}
|
|
||||||
onViewSelect={onViewSelect}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SeriesIndexSortMenu
|
<PageToolbarButton
|
||||||
sortKey={sortKey}
|
label={isSelectMode ? 'Stop Selecting' : 'Select Series'}
|
||||||
sortDirection={sortDirection}
|
iconName={isSelectMode ? icons.SERIES_ENDED : icons.CHECK}
|
||||||
isDisabled={hasNoSeries}
|
onPress={onSelectModePress}
|
||||||
onSortSelect={onSortSelect}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<SeriesIndexFilterMenu
|
{isSelectMode ? <SeriesIndexSelectAllButton /> : null}
|
||||||
selectedFilterKey={selectedFilterKey}
|
</PageToolbarSection>
|
||||||
filters={filters}
|
|
||||||
customFilters={customFilters}
|
|
||||||
isDisabled={hasNoSeries}
|
|
||||||
onFilterSelect={onFilterSelect}
|
|
||||||
/>
|
|
||||||
</PageToolbarSection>
|
|
||||||
</PageToolbar>
|
|
||||||
<div className={styles.pageContentBodyWrapper}>
|
|
||||||
<PageContentBody
|
|
||||||
ref={scrollerRef}
|
|
||||||
className={styles.contentBody}
|
|
||||||
innerClassName={styles[`${view}InnerContentBody`]}
|
|
||||||
initialScrollTop={props.initialScrollTop}
|
|
||||||
onScroll={onScroll}
|
|
||||||
>
|
|
||||||
{isFetching && !isPopulated ? <LoadingIndicator /> : null}
|
|
||||||
|
|
||||||
{!isFetching && !!error ? <div>Unable to load series</div> : null}
|
<PageToolbarSection
|
||||||
|
alignContent={align.RIGHT}
|
||||||
{isLoaded ? (
|
collapseButtons={false}
|
||||||
<div className={styles.contentBodyContainer}>
|
>
|
||||||
<ViewComponent
|
{view === 'table' ? (
|
||||||
scrollerRef={scrollerRef}
|
<TableOptionsModalWrapper
|
||||||
items={items}
|
columns={columns}
|
||||||
sortKey={sortKey}
|
optionsComponent={SeriesIndexTableOptions}
|
||||||
sortDirection={sortDirection}
|
onTableOptionChange={onTableOptionChange}
|
||||||
jumpToCharacter={jumpToCharacter}
|
>
|
||||||
isSmallScreen={isSmallScreen}
|
<PageToolbarButton label="Options" iconName={icons.TABLE} />
|
||||||
|
</TableOptionsModalWrapper>
|
||||||
|
) : (
|
||||||
|
<PageToolbarButton
|
||||||
|
label="Options"
|
||||||
|
iconName={view === 'posters' ? icons.POSTER : icons.OVERVIEW}
|
||||||
|
isDisabled={hasNoSeries}
|
||||||
|
onPress={onOptionsPress}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<SeriesIndexFooter />
|
<PageToolbarSeparator />
|
||||||
</div>
|
|
||||||
|
<SeriesIndexViewMenu
|
||||||
|
view={view}
|
||||||
|
isDisabled={hasNoSeries}
|
||||||
|
onViewSelect={onViewSelect}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SeriesIndexSortMenu
|
||||||
|
sortKey={sortKey}
|
||||||
|
sortDirection={sortDirection}
|
||||||
|
isDisabled={hasNoSeries}
|
||||||
|
onSortSelect={onSortSelect}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SeriesIndexFilterMenu
|
||||||
|
selectedFilterKey={selectedFilterKey}
|
||||||
|
filters={filters}
|
||||||
|
customFilters={customFilters}
|
||||||
|
isDisabled={hasNoSeries}
|
||||||
|
onFilterSelect={onFilterSelect}
|
||||||
|
/>
|
||||||
|
</PageToolbarSection>
|
||||||
|
</PageToolbar>
|
||||||
|
<div className={styles.pageContentBodyWrapper}>
|
||||||
|
<PageContentBody
|
||||||
|
ref={scrollerRef}
|
||||||
|
className={styles.contentBody}
|
||||||
|
innerClassName={styles[`${view}InnerContentBody`]}
|
||||||
|
initialScrollTop={props.initialScrollTop}
|
||||||
|
onScroll={onScroll}
|
||||||
|
>
|
||||||
|
{isFetching && !isPopulated ? <LoadingIndicator /> : null}
|
||||||
|
|
||||||
|
{!isFetching && !!error ? <div>Unable to load series</div> : null}
|
||||||
|
|
||||||
|
{isLoaded ? (
|
||||||
|
<div className={styles.contentBodyContainer}>
|
||||||
|
<ViewComponent
|
||||||
|
scrollerRef={scrollerRef}
|
||||||
|
items={items}
|
||||||
|
sortKey={sortKey}
|
||||||
|
sortDirection={sortDirection}
|
||||||
|
jumpToCharacter={jumpToCharacter}
|
||||||
|
isSelectMode={isSelectMode}
|
||||||
|
isSmallScreen={isSmallScreen}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SeriesIndexFooter />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{!error && isPopulated && !items.length ? (
|
||||||
|
<NoSeries totalItems={totalItems} />
|
||||||
|
) : null}
|
||||||
|
</PageContentBody>
|
||||||
|
|
||||||
|
{isLoaded && !!jumpBarItems.order.length ? (
|
||||||
|
<PageJumpBar
|
||||||
|
items={jumpBarItems}
|
||||||
|
onItemPress={onJumpBarItemPress}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
</div>
|
||||||
{!error && isPopulated && !items.length ? (
|
{view === 'posters' ? (
|
||||||
<NoSeries totalItems={totalItems} />
|
<SeriesIndexPosterOptionsModal
|
||||||
) : null}
|
isOpen={isOptionsModalOpen}
|
||||||
</PageContentBody>
|
onModalClose={onOptionsModalClose}
|
||||||
|
/>
|
||||||
{isLoaded && !!jumpBarItems.order.length ? (
|
|
||||||
<PageJumpBar items={jumpBarItems} onItemPress={onJumpBarItemPress} />
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
{view === 'overview' ? (
|
||||||
{view === 'posters' ? (
|
<SeriesIndexOverviewOptionsModal
|
||||||
<SeriesIndexPosterOptionsModal
|
isOpen={isOptionsModalOpen}
|
||||||
isOpen={isOptionsModalOpen}
|
onModalClose={onOptionsModalClose}
|
||||||
onModalClose={onOptionsModalClose}
|
/>
|
||||||
/>
|
) : null}
|
||||||
) : null}
|
</PageContent>
|
||||||
{view === 'overview' ? (
|
</SelectProvider>
|
||||||
<SeriesIndexOverviewOptionsModal
|
|
||||||
isOpen={isOptionsModalOpen}
|
|
||||||
onModalClose={onOptionsModalClose}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</PageContent>
|
|
||||||
);
|
);
|
||||||
}, 'seriesIndex');
|
}, 'seriesIndex');
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||||
import ProgressBar from 'Components/ProgressBar';
|
import ProgressBar from 'Components/ProgressBar';
|
||||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||||
|
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
|
||||||
import Column from 'Components/Table/Column';
|
import Column from 'Components/Table/Column';
|
||||||
import TagListConnector from 'Components/TagListConnector';
|
import TagListConnector from 'Components/TagListConnector';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
|
@ -32,10 +33,11 @@ interface SeriesIndexRowProps {
|
||||||
seriesId: number;
|
seriesId: number;
|
||||||
sortKey: string;
|
sortKey: string;
|
||||||
columns: Column[];
|
columns: Column[];
|
||||||
|
isSelectMode: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SeriesIndexRow(props: SeriesIndexRowProps) {
|
function SeriesIndexRow(props: SeriesIndexRowProps) {
|
||||||
const { seriesId, columns } = props;
|
const { seriesId, columns, isSelectMode } = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
series,
|
series,
|
||||||
|
@ -82,6 +84,7 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
|
||||||
const [hasBannerError, setHasBannerError] = useState(false);
|
const [hasBannerError, setHasBannerError] = useState(false);
|
||||||
const [isEditSeriesModalOpen, setIsEditSeriesModalOpen] = useState(false);
|
const [isEditSeriesModalOpen, setIsEditSeriesModalOpen] = useState(false);
|
||||||
const [isDeleteSeriesModalOpen, setIsDeleteSeriesModalOpen] = useState(false);
|
const [isDeleteSeriesModalOpen, setIsDeleteSeriesModalOpen] = useState(false);
|
||||||
|
const [selectState, selectDispatch] = useSelect();
|
||||||
|
|
||||||
const onRefreshPress = useCallback(() => {
|
const onRefreshPress = useCallback(() => {
|
||||||
dispatch(
|
dispatch(
|
||||||
|
@ -130,8 +133,29 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
|
||||||
// Mock handler to satisfy `onChange` being required for `CheckInput`.
|
// Mock handler to satisfy `onChange` being required for `CheckInput`.
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onSelectedChange = useCallback(
|
||||||
|
({ id, value, shiftKey }) => {
|
||||||
|
selectDispatch({
|
||||||
|
type: SelectActionType.ToggleSelected,
|
||||||
|
id,
|
||||||
|
isSelected: value,
|
||||||
|
shiftKey,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[selectDispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{isSelectMode ? (
|
||||||
|
<VirtualTableSelectCell
|
||||||
|
id={seriesId}
|
||||||
|
isSelected={selectState.selectedState[seriesId]}
|
||||||
|
isDisabled={false}
|
||||||
|
onSelectedChange={onSelectedChange}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{columns.map((column) => {
|
{columns.map((column) => {
|
||||||
const { name, isVisible } = column;
|
const { name, isVisible } = column;
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ interface RowItemData {
|
||||||
items: Series[];
|
items: Series[];
|
||||||
sortKey: string;
|
sortKey: string;
|
||||||
columns: Column[];
|
columns: Column[];
|
||||||
|
isSelectMode: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SeriesIndexTableProps {
|
interface SeriesIndexTableProps {
|
||||||
|
@ -34,6 +35,7 @@ interface SeriesIndexTableProps {
|
||||||
jumpToCharacter?: string;
|
jumpToCharacter?: string;
|
||||||
scrollTop?: number;
|
scrollTop?: number;
|
||||||
scrollerRef: React.MutableRefObject<HTMLElement>;
|
scrollerRef: React.MutableRefObject<HTMLElement>;
|
||||||
|
isSelectMode: boolean;
|
||||||
isSmallScreen: boolean;
|
isSmallScreen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +49,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
|
||||||
style,
|
style,
|
||||||
data,
|
data,
|
||||||
}) => {
|
}) => {
|
||||||
const { items, sortKey, columns } = data;
|
const { items, sortKey, columns, isSelectMode } = data;
|
||||||
|
|
||||||
if (index >= items.length) {
|
if (index >= items.length) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -67,6 +69,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
|
||||||
seriesId={series.id}
|
seriesId={series.id}
|
||||||
sortKey={sortKey}
|
sortKey={sortKey}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
isSelectMode={isSelectMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -82,6 +85,7 @@ function SeriesIndexTable(props: SeriesIndexTableProps) {
|
||||||
sortKey,
|
sortKey,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
jumpToCharacter,
|
jumpToCharacter,
|
||||||
|
isSelectMode,
|
||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
scrollerRef,
|
scrollerRef,
|
||||||
} = props;
|
} = props;
|
||||||
|
@ -177,6 +181,7 @@ function SeriesIndexTable(props: SeriesIndexTableProps) {
|
||||||
columns={columns}
|
columns={columns}
|
||||||
sortKey={sortKey}
|
sortKey={sortKey}
|
||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
|
isSelectMode={isSelectMode}
|
||||||
/>
|
/>
|
||||||
<List<RowItemData>
|
<List<RowItemData>
|
||||||
ref={listRef}
|
ref={listRef}
|
||||||
|
@ -193,6 +198,7 @@ function SeriesIndexTable(props: SeriesIndexTableProps) {
|
||||||
items,
|
items,
|
||||||
sortKey,
|
sortKey,
|
||||||
columns,
|
columns,
|
||||||
|
isSelectMode,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Row}
|
{Row}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import Column from 'Components/Table/Column';
|
||||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||||
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
||||||
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
||||||
|
import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import SortDirection from 'Helpers/Props/SortDirection';
|
import SortDirection from 'Helpers/Props/SortDirection';
|
||||||
import {
|
import {
|
||||||
|
@ -22,12 +23,13 @@ interface SeriesIndexTableHeaderProps {
|
||||||
columns: Column[];
|
columns: Column[];
|
||||||
sortKey?: string;
|
sortKey?: string;
|
||||||
sortDirection?: SortDirection;
|
sortDirection?: SortDirection;
|
||||||
|
isSelectMode: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SeriesIndexTableHeader(props: SeriesIndexTableHeaderProps) {
|
function SeriesIndexTableHeader(props: SeriesIndexTableHeaderProps) {
|
||||||
const { showBanners, columns, sortKey, sortDirection } = props;
|
const { showBanners, columns, sortKey, sortDirection, isSelectMode } = props;
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const [selectState, selectDispatch] = useSelect();
|
||||||
|
|
||||||
const onSortPress = useCallback(
|
const onSortPress = useCallback(
|
||||||
(value) => {
|
(value) => {
|
||||||
|
@ -43,8 +45,25 @@ function SeriesIndexTableHeader(props: SeriesIndexTableHeaderProps) {
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onSelectAllChange = useCallback(
|
||||||
|
({ value }) => {
|
||||||
|
selectDispatch({
|
||||||
|
type: value ? SelectActionType.SelectAll : SelectActionType.UnselectAll,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[selectDispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VirtualTableHeader>
|
<VirtualTableHeader>
|
||||||
|
{isSelectMode ? (
|
||||||
|
<VirtualTableSelectAllHeaderCell
|
||||||
|
allSelected={selectState.allSelected}
|
||||||
|
allUnselected={selectState.allUnselected}
|
||||||
|
onSelectAllChange={onSelectAllChange}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{columns.map((column) => {
|
{columns.map((column) => {
|
||||||
const { name, label, isSortable, isVisible } = column;
|
const { name, label, isSortable, isVisible } = column;
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,29 @@
|
||||||
import areAllSelected from './areAllSelected';
|
import areAllSelected from './areAllSelected';
|
||||||
import getToggledRange from './getToggledRange';
|
import getToggledRange from './getToggledRange';
|
||||||
|
|
||||||
function toggleSelected(state, items, id, selected, shiftKey) {
|
function toggleSelected(selectedState, items, id, selected, shiftKey) {
|
||||||
const lastToggled = state.lastToggled;
|
const lastToggled = selectedState.lastToggled;
|
||||||
const selectedState = {
|
const nextSelectedState = {
|
||||||
...state.selectedState,
|
...selectedState.selectedState,
|
||||||
[id]: selected
|
[id]: selected
|
||||||
};
|
};
|
||||||
|
|
||||||
if (selected == null) {
|
if (selected == null) {
|
||||||
delete selectedState[id];
|
delete nextSelectedState[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shiftKey && lastToggled) {
|
if (shiftKey && lastToggled) {
|
||||||
const { lower, upper } = getToggledRange(items, id, lastToggled);
|
const { lower, upper } = getToggledRange(items, id, lastToggled);
|
||||||
|
|
||||||
for (let i = lower; i < upper; i++) {
|
for (let i = lower; i < upper; i++) {
|
||||||
selectedState[items[i].id] = selected;
|
nextSelectedState[items[i].id] = selected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...areAllSelected(selectedState),
|
...areAllSelected(nextSelectedState),
|
||||||
lastToggled: id,
|
lastToggled: id,
|
||||||
selectedState
|
selectedState: nextSelectedState
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue