mirror of
https://github.com/lidarr/Lidarr
synced 2024-12-21 23:32:27 +00:00
Typings cleanup and improvements
Appease linter (cherry picked from commit b2c43fb2a67965d68d3d35b72302b0cddb5aca23) (cherry picked from commit 3b5e83670b844cf7c20bf7d744d9fbc96fde6902) Closes #3516 Closes #3510 Closes #2778
This commit is contained in:
parent
8e5942d5c5
commit
efe0a3d283
46 changed files with 420 additions and 269 deletions
|
@ -7,6 +7,8 @@ import React, {
|
|||
} from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { SelectProvider } from 'App/SelectContext';
|
||||
import ArtistAppState, { ArtistIndexAppState } from 'App/State/ArtistAppState';
|
||||
import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState';
|
||||
import NoArtist from 'Artist/NoArtist';
|
||||
import { RSS_SYNC } from 'Commands/commandNames';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
|
@ -89,16 +91,19 @@ const ArtistIndex = withScrollPosition((props: ArtistIndexProps) => {
|
|||
sortKey,
|
||||
sortDirection,
|
||||
view,
|
||||
} = useSelector(createArtistClientSideCollectionItemsSelector('artistIndex'));
|
||||
}: ArtistAppState & ArtistIndexAppState & ClientSideCollectionAppState =
|
||||
useSelector(createArtistClientSideCollectionItemsSelector('artistIndex'));
|
||||
|
||||
const isRssSyncExecuting = useSelector(
|
||||
createCommandExecutingSelector(RSS_SYNC)
|
||||
);
|
||||
const { isSmallScreen } = useSelector(createDimensionsSelector());
|
||||
const dispatch = useDispatch();
|
||||
const scrollerRef = useRef<HTMLDivElement>();
|
||||
const scrollerRef = useRef<HTMLDivElement>(null);
|
||||
const [isOptionsModalOpen, setIsOptionsModalOpen] = useState(false);
|
||||
const [jumpToCharacter, setJumpToCharacter] = useState<string | null>(null);
|
||||
const [jumpToCharacter, setJumpToCharacter] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [isSelectMode, setIsSelectMode] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -118,14 +123,14 @@ const ArtistIndex = withScrollPosition((props: ArtistIndexProps) => {
|
|||
}, [isSelectMode, setIsSelectMode]);
|
||||
|
||||
const onTableOptionChange = useCallback(
|
||||
(payload) => {
|
||||
(payload: unknown) => {
|
||||
dispatch(setArtistTableOption(payload));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const onViewSelect = useCallback(
|
||||
(value) => {
|
||||
(value: string) => {
|
||||
dispatch(setArtistView({ view: value }));
|
||||
|
||||
if (scrollerRef.current) {
|
||||
|
@ -136,14 +141,14 @@ const ArtistIndex = withScrollPosition((props: ArtistIndexProps) => {
|
|||
);
|
||||
|
||||
const onSortSelect = useCallback(
|
||||
(value) => {
|
||||
(value: string) => {
|
||||
dispatch(setArtistSort({ sortKey: value }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const onFilterSelect = useCallback(
|
||||
(value) => {
|
||||
(value: string) => {
|
||||
dispatch(setArtistFilter({ selectedFilterKey: value }));
|
||||
},
|
||||
[dispatch]
|
||||
|
@ -158,15 +163,15 @@ const ArtistIndex = withScrollPosition((props: ArtistIndexProps) => {
|
|||
}, [setIsOptionsModalOpen]);
|
||||
|
||||
const onJumpBarItemPress = useCallback(
|
||||
(character) => {
|
||||
(character: string) => {
|
||||
setJumpToCharacter(character);
|
||||
},
|
||||
[setJumpToCharacter]
|
||||
);
|
||||
|
||||
const onScroll = useCallback(
|
||||
({ scrollTop }) => {
|
||||
setJumpToCharacter(null);
|
||||
({ scrollTop }: { scrollTop: number }) => {
|
||||
setJumpToCharacter(undefined);
|
||||
scrollPositions.artistIndex = scrollTop;
|
||||
},
|
||||
[setJumpToCharacter]
|
||||
|
@ -180,10 +185,10 @@ const ArtistIndex = withScrollPosition((props: ArtistIndexProps) => {
|
|||
};
|
||||
}
|
||||
|
||||
const characters = items.reduce((acc, item) => {
|
||||
const characters = items.reduce((acc: Record<string, number>, item) => {
|
||||
let char = item.sortName.charAt(0);
|
||||
|
||||
if (!isNaN(char)) {
|
||||
if (!isNaN(Number(char))) {
|
||||
char = '#';
|
||||
}
|
||||
|
||||
|
@ -300,6 +305,8 @@ const ArtistIndex = withScrollPosition((props: ArtistIndexProps) => {
|
|||
<PageContentBody
|
||||
ref={scrollerRef}
|
||||
className={styles.contentBody}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
innerClassName={styles[`${view}InnerContentBody`]}
|
||||
initialScrollTop={props.initialScrollTop}
|
||||
onScroll={onScroll}
|
||||
|
|
|
@ -23,7 +23,13 @@ function createFilterBuilderPropsSelector() {
|
|||
);
|
||||
}
|
||||
|
||||
export default function ArtistIndexFilterModal(props) {
|
||||
interface ArtistIndexFilterModalProps {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export default function ArtistIndexFilterModal(
|
||||
props: ArtistIndexFilterModalProps
|
||||
) {
|
||||
const sectionItems = useSelector(createArtistSelector());
|
||||
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
|
||||
const customFilterType = 'artist';
|
||||
|
@ -31,7 +37,7 @@ export default function ArtistIndexFilterModal(props) {
|
|||
const dispatch = useDispatch();
|
||||
|
||||
const dispatchSetFilter = useCallback(
|
||||
(payload) => {
|
||||
(payload: unknown) => {
|
||||
dispatch(setArtistFilter(payload));
|
||||
},
|
||||
[dispatch]
|
||||
|
@ -39,6 +45,7 @@ export default function ArtistIndexFilterModal(props) {
|
|||
|
||||
return (
|
||||
<FilterModal
|
||||
// TODO: Don't spread all the props
|
||||
{...props}
|
||||
sectionItems={sectionItems}
|
||||
filterBuilderProps={filterBuilderProps}
|
||||
|
|
|
@ -206,7 +206,7 @@ function ArtistIndexBanner(props: ArtistIndexBannerProps) {
|
|||
</div>
|
||||
) : null}
|
||||
|
||||
{showQualityProfile ? (
|
||||
{showQualityProfile && !!qualityProfile?.name ? (
|
||||
<div className={styles.title} title={translate('QualityProfile')}>
|
||||
{qualityProfile.name}
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { throttle } from 'lodash';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { RefObject, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { FixedSizeGrid as Grid, GridChildComponentProps } from 'react-window';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import Artist from 'Artist/Artist';
|
||||
import ArtistIndexBanner from 'Artist/Index/Banners/ArtistIndexBanner';
|
||||
import useMeasure from 'Helpers/Hooks/useMeasure';
|
||||
|
@ -21,7 +22,7 @@ const columnPaddingSmallScreen = parseInt(
|
|||
const progressBarHeight = parseInt(dimensions.progressBarSmallHeight);
|
||||
const detailedProgressBarHeight = parseInt(dimensions.progressBarMediumHeight);
|
||||
|
||||
const ADDITIONAL_COLUMN_COUNT = {
|
||||
const ADDITIONAL_COLUMN_COUNT: Record<string, number> = {
|
||||
small: 3,
|
||||
medium: 2,
|
||||
large: 1,
|
||||
|
@ -41,17 +42,17 @@ interface CellItemData {
|
|||
|
||||
interface ArtistIndexBannersProps {
|
||||
items: Artist[];
|
||||
sortKey?: string;
|
||||
sortKey: string;
|
||||
sortDirection?: SortDirection;
|
||||
jumpToCharacter?: string;
|
||||
scrollTop?: number;
|
||||
scrollerRef: React.MutableRefObject<HTMLElement>;
|
||||
scrollerRef: RefObject<HTMLElement>;
|
||||
isSelectMode: boolean;
|
||||
isSmallScreen: boolean;
|
||||
}
|
||||
|
||||
const artistIndexSelector = createSelector(
|
||||
(state) => state.artistIndex.bannerOptions,
|
||||
(state: AppState) => state.artistIndex.bannerOptions,
|
||||
(bannerOptions) => {
|
||||
return {
|
||||
bannerOptions,
|
||||
|
@ -108,7 +109,7 @@ export default function ArtistIndexBanners(props: ArtistIndexBannersProps) {
|
|||
} = props;
|
||||
|
||||
const { bannerOptions } = useSelector(artistIndexSelector);
|
||||
const ref: React.MutableRefObject<Grid> = useRef();
|
||||
const ref = useRef<Grid>(null);
|
||||
const [measureRef, bounds] = useMeasure();
|
||||
const [size, setSize] = useState({ width: 0, height: 0 });
|
||||
|
||||
|
@ -222,8 +223,8 @@ export default function ArtistIndexBanners(props: ArtistIndexBannersProps) {
|
|||
}, [isSmallScreen, scrollerRef, bounds]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentScrollListener = isSmallScreen ? window : scrollerRef.current;
|
||||
const currentScrollerRef = scrollerRef.current;
|
||||
const currentScrollerRef = scrollerRef.current as HTMLElement;
|
||||
const currentScrollListener = isSmallScreen ? window : currentScrollerRef;
|
||||
|
||||
const handleScroll = throttle(() => {
|
||||
const { offsetTop = 0 } = currentScrollerRef;
|
||||
|
@ -232,7 +233,7 @@ export default function ArtistIndexBanners(props: ArtistIndexBannersProps) {
|
|||
? getWindowScrollTopPosition()
|
||||
: currentScrollerRef.scrollTop) - offsetTop;
|
||||
|
||||
ref.current.scrollTo({ scrollLeft: 0, scrollTop });
|
||||
ref.current?.scrollTo({ scrollLeft: 0, scrollTop });
|
||||
}, 10);
|
||||
|
||||
currentScrollListener.addEventListener('scroll', handleScroll);
|
||||
|
@ -255,8 +256,8 @@ export default function ArtistIndexBanners(props: ArtistIndexBannersProps) {
|
|||
|
||||
const scrollTop = rowIndex * rowHeight + padding;
|
||||
|
||||
ref.current.scrollTo({ scrollLeft: 0, scrollTop });
|
||||
scrollerRef.current.scrollTo(0, scrollTop);
|
||||
ref.current?.scrollTo({ scrollLeft: 0, scrollTop });
|
||||
scrollerRef.current?.scrollTo(0, scrollTop);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
|
|
|
@ -59,7 +59,7 @@ function ArtistIndexBannerOptionsModalContent(
|
|||
const dispatch = useDispatch();
|
||||
|
||||
const onBannerOptionChange = useCallback(
|
||||
({ name, value }) => {
|
||||
({ name, value }: { name: string; value: unknown }) => {
|
||||
dispatch(setArtistBannerOption({ [name]: value }));
|
||||
},
|
||||
[dispatch]
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { CustomFilter } from 'App/State/AppState';
|
||||
import ArtistIndexFilterModal from 'Artist/Index/ArtistIndexFilterModal';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import { align } from 'Helpers/Props';
|
||||
|
||||
function ArtistIndexFilterMenu(props) {
|
||||
interface ArtistIndexFilterMenuProps {
|
||||
selectedFilterKey: string | number;
|
||||
filters: object[];
|
||||
customFilters: CustomFilter[];
|
||||
isDisabled: boolean;
|
||||
onFilterSelect(filterName: string): unknown;
|
||||
}
|
||||
|
||||
function ArtistIndexFilterMenu(props: ArtistIndexFilterMenuProps) {
|
||||
const {
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
|
@ -26,15 +34,6 @@ function ArtistIndexFilterMenu(props) {
|
|||
);
|
||||
}
|
||||
|
||||
ArtistIndexFilterMenu.propTypes = {
|
||||
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
||||
.isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
ArtistIndexFilterMenu.defaultProps = {
|
||||
showCustomFilters: false,
|
||||
};
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import MenuContent from 'Components/Menu/MenuContent';
|
||||
import SortMenu from 'Components/Menu/SortMenu';
|
||||
import SortMenuItem from 'Components/Menu/SortMenuItem';
|
||||
import { align, sortDirections } from 'Helpers/Props';
|
||||
import { align } from 'Helpers/Props';
|
||||
import SortDirection from 'Helpers/Props/SortDirection';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
function ArtistIndexSortMenu(props) {
|
||||
interface SeriesIndexSortMenuProps {
|
||||
sortKey?: string;
|
||||
sortDirection?: SortDirection;
|
||||
isDisabled: boolean;
|
||||
onSortSelect(sortKey: string): unknown;
|
||||
}
|
||||
|
||||
function ArtistIndexSortMenu(props: SeriesIndexSortMenuProps) {
|
||||
const { sortKey, sortDirection, isDisabled, onSortSelect } = props;
|
||||
|
||||
return (
|
||||
|
@ -17,7 +25,7 @@ function ArtistIndexSortMenu(props) {
|
|||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Monitored/Status
|
||||
{translate('MonitoredStatus')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
|
@ -26,7 +34,7 @@ function ArtistIndexSortMenu(props) {
|
|||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Name
|
||||
{translate('Name')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
|
@ -35,7 +43,7 @@ function ArtistIndexSortMenu(props) {
|
|||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Type
|
||||
{translate('Type')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
|
@ -44,7 +52,7 @@ function ArtistIndexSortMenu(props) {
|
|||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Quality Profile
|
||||
{translate('QualityProfile')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
|
@ -53,7 +61,7 @@ function ArtistIndexSortMenu(props) {
|
|||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Metadata Profile
|
||||
{translate('MetadataProfile')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
|
@ -62,7 +70,7 @@ function ArtistIndexSortMenu(props) {
|
|||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Next Album
|
||||
{translate('NextAlbum')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
|
@ -71,7 +79,7 @@ function ArtistIndexSortMenu(props) {
|
|||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Last Album
|
||||
{translate('Last Album')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
|
@ -80,7 +88,7 @@ function ArtistIndexSortMenu(props) {
|
|||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Added
|
||||
{translate('Added')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
|
@ -89,7 +97,7 @@ function ArtistIndexSortMenu(props) {
|
|||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Albums
|
||||
{translate('Albums')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
|
@ -98,7 +106,7 @@ function ArtistIndexSortMenu(props) {
|
|||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Tracks
|
||||
{translate('Tracks')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
|
@ -107,7 +115,7 @@ function ArtistIndexSortMenu(props) {
|
|||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Track Count
|
||||
{translate('TrackCount')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
|
@ -116,7 +124,7 @@ function ArtistIndexSortMenu(props) {
|
|||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Path
|
||||
{translate('Path')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
|
@ -125,7 +133,7 @@ function ArtistIndexSortMenu(props) {
|
|||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Size on Disk
|
||||
{translate('SizeOnDisk')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
|
@ -134,18 +142,11 @@ function ArtistIndexSortMenu(props) {
|
|||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Tags
|
||||
{translate('Tags')}
|
||||
</SortMenuItem>
|
||||
</MenuContent>
|
||||
</SortMenu>
|
||||
);
|
||||
}
|
||||
|
||||
ArtistIndexSortMenu.propTypes = {
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
onSortSelect: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ArtistIndexSortMenu;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import MenuContent from 'Components/Menu/MenuContent';
|
||||
import ViewMenu from 'Components/Menu/ViewMenu';
|
||||
|
@ -6,7 +5,13 @@ import ViewMenuItem from 'Components/Menu/ViewMenuItem';
|
|||
import { align } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
function ArtistIndexViewMenu(props) {
|
||||
interface ArtistIndexViewMenuProps {
|
||||
view: string;
|
||||
isDisabled: boolean;
|
||||
onViewSelect(value: string): unknown;
|
||||
}
|
||||
|
||||
function ArtistIndexViewMenu(props: ArtistIndexViewMenuProps) {
|
||||
const { view, isDisabled, onViewSelect } = props;
|
||||
|
||||
return (
|
||||
|
@ -36,10 +41,4 @@ function ArtistIndexViewMenu(props) {
|
|||
);
|
||||
}
|
||||
|
||||
ArtistIndexViewMenu.propTypes = {
|
||||
view: PropTypes.string.isRequired,
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
onViewSelect: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ArtistIndexViewMenu;
|
||||
|
|
|
@ -1,15 +1,51 @@
|
|||
import { IconDefinition } from '@fortawesome/free-regular-svg-icons';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import Album from 'Album/Album';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import QualityProfile from 'typings/QualityProfile';
|
||||
import { UiSettings } from 'typings/UiSettings';
|
||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import ArtistIndexOverviewInfoRow from './ArtistIndexOverviewInfoRow';
|
||||
import styles from './ArtistIndexOverviewInfo.css';
|
||||
|
||||
interface RowProps {
|
||||
name: string;
|
||||
showProp: string;
|
||||
valueProp: string;
|
||||
}
|
||||
|
||||
interface RowInfoProps {
|
||||
title: string;
|
||||
iconName: IconDefinition;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface ArtistIndexOverviewInfoProps {
|
||||
height: number;
|
||||
showMonitored: boolean;
|
||||
showQualityProfile: boolean;
|
||||
showLastAlbum: boolean;
|
||||
showAdded: boolean;
|
||||
showAlbumCount: boolean;
|
||||
showPath: boolean;
|
||||
showSizeOnDisk: boolean;
|
||||
monitored: boolean;
|
||||
nextAlbum?: Album;
|
||||
qualityProfile?: QualityProfile;
|
||||
lastAlbum?: Album;
|
||||
added?: string;
|
||||
albumCount: number;
|
||||
path: string;
|
||||
sizeOnDisk?: number;
|
||||
sortKey: string;
|
||||
}
|
||||
|
||||
const infoRowHeight = parseInt(dimensions.artistIndexOverviewInfoRowHeight);
|
||||
|
||||
const rows = [
|
||||
|
@ -50,11 +86,17 @@ const rows = [
|
|||
},
|
||||
];
|
||||
|
||||
function getInfoRowProps(row, props, uiSettings) {
|
||||
function getInfoRowProps(
|
||||
row: RowProps,
|
||||
props: ArtistIndexOverviewInfoProps,
|
||||
uiSettings: UiSettings
|
||||
): RowInfoProps | null {
|
||||
const { name } = row;
|
||||
|
||||
if (name === 'monitored') {
|
||||
const monitoredText = props.monitored ? 'Monitored' : 'Unmonitored';
|
||||
const monitoredText = props.monitored
|
||||
? translate('Monitored')
|
||||
: translate('Unmonitored');
|
||||
|
||||
return {
|
||||
title: monitoredText,
|
||||
|
@ -63,9 +105,9 @@ function getInfoRowProps(row, props, uiSettings) {
|
|||
};
|
||||
}
|
||||
|
||||
if (name === 'qualityProfileId') {
|
||||
if (name === 'qualityProfileId' && !!props.qualityProfile?.name) {
|
||||
return {
|
||||
title: 'Quality Profile',
|
||||
title: translate('QualityProfile'),
|
||||
iconName: icons.PROFILE,
|
||||
label: props.qualityProfile.name,
|
||||
};
|
||||
|
@ -78,15 +120,16 @@ function getInfoRowProps(row, props, uiSettings) {
|
|||
return {
|
||||
title: `Last Album: ${lastAlbum.title}`,
|
||||
iconName: icons.CALENDAR,
|
||||
label: getRelativeDate(
|
||||
lastAlbum.releaseDate,
|
||||
shortDateFormat,
|
||||
showRelativeDates,
|
||||
{
|
||||
timeFormat,
|
||||
timeForToday: true,
|
||||
}
|
||||
),
|
||||
label:
|
||||
getRelativeDate(
|
||||
lastAlbum.releaseDate,
|
||||
shortDateFormat,
|
||||
showRelativeDates,
|
||||
{
|
||||
timeFormat,
|
||||
timeForToday: true,
|
||||
}
|
||||
) ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -98,10 +141,11 @@ function getInfoRowProps(row, props, uiSettings) {
|
|||
return {
|
||||
title: `Added: ${formatDateTime(added, longDateFormat, timeFormat)}`,
|
||||
iconName: icons.ADD,
|
||||
label: getRelativeDate(added, shortDateFormat, showRelativeDates, {
|
||||
timeFormat,
|
||||
timeForToday: true,
|
||||
}),
|
||||
label:
|
||||
getRelativeDate(added, shortDateFormat, showRelativeDates, {
|
||||
timeFormat,
|
||||
timeForToday: true,
|
||||
}) ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -116,7 +160,7 @@ function getInfoRowProps(row, props, uiSettings) {
|
|||
}
|
||||
|
||||
return {
|
||||
title: 'Album Count',
|
||||
title: translate('AlbumCount'),
|
||||
iconName: icons.CIRCLE,
|
||||
label: albums,
|
||||
};
|
||||
|
@ -124,7 +168,7 @@ function getInfoRowProps(row, props, uiSettings) {
|
|||
|
||||
if (name === 'path') {
|
||||
return {
|
||||
title: 'Path',
|
||||
title: translate('Path'),
|
||||
iconName: icons.FOLDER,
|
||||
label: props.path,
|
||||
};
|
||||
|
@ -132,31 +176,13 @@ function getInfoRowProps(row, props, uiSettings) {
|
|||
|
||||
if (name === 'sizeOnDisk') {
|
||||
return {
|
||||
title: 'Size on Disk',
|
||||
title: translate('SizeOnDisk'),
|
||||
iconName: icons.DRIVE,
|
||||
label: formatBytes(props.sizeOnDisk),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface ArtistIndexOverviewInfoProps {
|
||||
height: number;
|
||||
showMonitored: boolean;
|
||||
showQualityProfile: boolean;
|
||||
showLastAlbum: boolean;
|
||||
showAdded: boolean;
|
||||
showAlbumCount: boolean;
|
||||
showPath: boolean;
|
||||
showSizeOnDisk: boolean;
|
||||
monitored: boolean;
|
||||
nextAlbum?: Album;
|
||||
qualityProfile: object;
|
||||
lastAlbum?: Album;
|
||||
added?: string;
|
||||
albumCount: number;
|
||||
path: string;
|
||||
sizeOnDisk?: number;
|
||||
sortKey: string;
|
||||
return null;
|
||||
}
|
||||
|
||||
function ArtistIndexOverviewInfo(props: ArtistIndexOverviewInfoProps) {
|
||||
|
@ -175,6 +201,8 @@ function ArtistIndexOverviewInfo(props: ArtistIndexOverviewInfoProps) {
|
|||
const { name, showProp, valueProp } = row;
|
||||
|
||||
const isVisible =
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore ts(7053)
|
||||
props[valueProp] != null && (props[showProp] || props.sortKey === name);
|
||||
|
||||
return {
|
||||
|
@ -219,6 +247,10 @@ function ArtistIndexOverviewInfo(props: ArtistIndexOverviewInfoProps) {
|
|||
|
||||
const infoRowProps = getInfoRowProps(row, props, uiSettings);
|
||||
|
||||
if (infoRowProps == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <ArtistIndexOverviewInfoRow key={row.name} {...infoRowProps} />;
|
||||
})}
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { IconDefinition } from '@fortawesome/free-regular-svg-icons';
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import styles from './ArtistIndexOverviewInfoRow.css';
|
||||
|
||||
interface ArtistIndexOverviewInfoRowProps {
|
||||
title?: string;
|
||||
iconName: object;
|
||||
label: string;
|
||||
iconName?: IconDefinition;
|
||||
label: string | null;
|
||||
}
|
||||
|
||||
function ArtistIndexOverviewInfoRow(props: ArtistIndexOverviewInfoRowProps) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { throttle } from 'lodash';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { RefObject, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { FixedSizeList as List, ListChildComponentProps } from 'react-window';
|
||||
import Artist from 'Artist/Artist';
|
||||
|
@ -33,11 +33,11 @@ interface RowItemData {
|
|||
|
||||
interface ArtistIndexOverviewsProps {
|
||||
items: Artist[];
|
||||
sortKey?: string;
|
||||
sortKey: string;
|
||||
sortDirection?: string;
|
||||
jumpToCharacter?: string;
|
||||
scrollTop?: number;
|
||||
scrollerRef: React.MutableRefObject<HTMLElement>;
|
||||
scrollerRef: RefObject<HTMLElement>;
|
||||
isSelectMode: boolean;
|
||||
isSmallScreen: boolean;
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ function ArtistIndexOverviews(props: ArtistIndexOverviewsProps) {
|
|||
const { size: posterSize, detailedProgressBar } = useSelector(
|
||||
selectOverviewOptions
|
||||
);
|
||||
const listRef: React.MutableRefObject<List> = useRef();
|
||||
const listRef = useRef<List>(null);
|
||||
const [measureRef, bounds] = useMeasure();
|
||||
const [size, setSize] = useState({ width: 0, height: 0 });
|
||||
|
||||
|
@ -136,8 +136,8 @@ function ArtistIndexOverviews(props: ArtistIndexOverviewsProps) {
|
|||
}, [isSmallScreen, scrollerRef, bounds]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentScrollListener = isSmallScreen ? window : scrollerRef.current;
|
||||
const currentScrollerRef = scrollerRef.current;
|
||||
const currentScrollerRef = scrollerRef.current as HTMLElement;
|
||||
const currentScrollListener = isSmallScreen ? window : currentScrollerRef;
|
||||
|
||||
const handleScroll = throttle(() => {
|
||||
const { offsetTop = 0 } = currentScrollerRef;
|
||||
|
@ -146,7 +146,7 @@ function ArtistIndexOverviews(props: ArtistIndexOverviewsProps) {
|
|||
? getWindowScrollTopPosition()
|
||||
: currentScrollerRef.scrollTop) - offsetTop;
|
||||
|
||||
listRef.current.scrollTo(scrollTop);
|
||||
listRef.current?.scrollTo(scrollTop);
|
||||
}, 10);
|
||||
|
||||
currentScrollListener.addEventListener('scroll', handleScroll);
|
||||
|
@ -175,8 +175,8 @@ function ArtistIndexOverviews(props: ArtistIndexOverviewsProps) {
|
|||
scrollTop += offset;
|
||||
}
|
||||
|
||||
listRef.current.scrollTo(scrollTop);
|
||||
scrollerRef.current.scrollTo(0, scrollTop);
|
||||
listRef.current?.scrollTo(scrollTop);
|
||||
scrollerRef.current?.scrollTo(0, scrollTop);
|
||||
}
|
||||
}
|
||||
}, [jumpToCharacter, rowHeight, items, scrollerRef, listRef]);
|
||||
|
|
|
@ -60,7 +60,7 @@ function ArtistIndexOverviewOptionsModalContent(
|
|||
const dispatch = useDispatch();
|
||||
|
||||
const onOverviewOptionChange = useCallback(
|
||||
({ name, value }) => {
|
||||
({ name, value }: { name: string; value: unknown }) => {
|
||||
dispatch(setArtistOverviewOption({ [name]: value }));
|
||||
},
|
||||
[dispatch]
|
||||
|
|
|
@ -206,7 +206,7 @@ function ArtistIndexPoster(props: ArtistIndexPosterProps) {
|
|||
</div>
|
||||
) : null}
|
||||
|
||||
{showQualityProfile ? (
|
||||
{showQualityProfile && !!qualityProfile?.name ? (
|
||||
<div className={styles.title} title={translate('QualityProfile')}>
|
||||
{qualityProfile.name}
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { throttle } from 'lodash';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { RefObject, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { FixedSizeGrid as Grid, GridChildComponentProps } from 'react-window';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import Artist from 'Artist/Artist';
|
||||
import ArtistIndexPoster from 'Artist/Index/Posters/ArtistIndexPoster';
|
||||
import useMeasure from 'Helpers/Hooks/useMeasure';
|
||||
|
@ -21,7 +22,7 @@ const columnPaddingSmallScreen = parseInt(
|
|||
const progressBarHeight = parseInt(dimensions.progressBarSmallHeight);
|
||||
const detailedProgressBarHeight = parseInt(dimensions.progressBarMediumHeight);
|
||||
|
||||
const ADDITIONAL_COLUMN_COUNT = {
|
||||
const ADDITIONAL_COLUMN_COUNT: Record<string, number> = {
|
||||
small: 3,
|
||||
medium: 2,
|
||||
large: 1,
|
||||
|
@ -41,17 +42,17 @@ interface CellItemData {
|
|||
|
||||
interface ArtistIndexPostersProps {
|
||||
items: Artist[];
|
||||
sortKey?: string;
|
||||
sortKey: string;
|
||||
sortDirection?: SortDirection;
|
||||
jumpToCharacter?: string;
|
||||
scrollTop?: number;
|
||||
scrollerRef: React.MutableRefObject<HTMLElement>;
|
||||
scrollerRef: RefObject<HTMLElement>;
|
||||
isSelectMode: boolean;
|
||||
isSmallScreen: boolean;
|
||||
}
|
||||
|
||||
const artistIndexSelector = createSelector(
|
||||
(state) => state.artistIndex.posterOptions,
|
||||
(state: AppState) => state.artistIndex.posterOptions,
|
||||
(posterOptions) => {
|
||||
return {
|
||||
posterOptions,
|
||||
|
@ -108,7 +109,7 @@ export default function ArtistIndexPosters(props: ArtistIndexPostersProps) {
|
|||
} = props;
|
||||
|
||||
const { posterOptions } = useSelector(artistIndexSelector);
|
||||
const ref: React.MutableRefObject<Grid> = useRef();
|
||||
const ref = useRef<Grid>(null);
|
||||
const [measureRef, bounds] = useMeasure();
|
||||
const [size, setSize] = useState({ width: 0, height: 0 });
|
||||
|
||||
|
@ -231,8 +232,8 @@ export default function ArtistIndexPosters(props: ArtistIndexPostersProps) {
|
|||
}, [isSmallScreen, size, scrollerRef, bounds]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentScrollListener = isSmallScreen ? window : scrollerRef.current;
|
||||
const currentScrollerRef = scrollerRef.current;
|
||||
const currentScrollerRef = scrollerRef.current as HTMLElement;
|
||||
const currentScrollListener = isSmallScreen ? window : currentScrollerRef;
|
||||
|
||||
const handleScroll = throttle(() => {
|
||||
const { offsetTop = 0 } = currentScrollerRef;
|
||||
|
@ -241,7 +242,7 @@ export default function ArtistIndexPosters(props: ArtistIndexPostersProps) {
|
|||
? getWindowScrollTopPosition()
|
||||
: currentScrollerRef.scrollTop) - offsetTop;
|
||||
|
||||
ref.current.scrollTo({ scrollLeft: 0, scrollTop });
|
||||
ref.current?.scrollTo({ scrollLeft: 0, scrollTop });
|
||||
}, 10);
|
||||
|
||||
currentScrollListener.addEventListener('scroll', handleScroll);
|
||||
|
@ -264,8 +265,8 @@ export default function ArtistIndexPosters(props: ArtistIndexPostersProps) {
|
|||
|
||||
const scrollTop = rowIndex * rowHeight + padding;
|
||||
|
||||
ref.current.scrollTo({ scrollLeft: 0, scrollTop });
|
||||
scrollerRef.current.scrollTo(0, scrollTop);
|
||||
ref.current?.scrollTo({ scrollLeft: 0, scrollTop });
|
||||
scrollerRef.current?.scrollTo(0, scrollTop);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
|
|
|
@ -59,7 +59,7 @@ function ArtistIndexPosterOptionsModalContent(
|
|||
const dispatch = useDispatch();
|
||||
|
||||
const onPosterOptionChange = useCallback(
|
||||
({ name, value }) => {
|
||||
({ name, value }: { name: string; value: unknown }) => {
|
||||
dispatch(setArtistPosterOption({ [name]: value }));
|
||||
},
|
||||
[dispatch]
|
||||
|
|
|
@ -57,7 +57,7 @@ function AlbumDetails(props: AlbumDetailsProps) {
|
|||
albumType,
|
||||
monitored,
|
||||
statistics,
|
||||
isSaving,
|
||||
isSaving = false,
|
||||
} = album;
|
||||
|
||||
return (
|
||||
|
|
|
@ -11,7 +11,7 @@ interface AlbumStudioAlbumProps {
|
|||
artistId: number;
|
||||
albumId: number;
|
||||
title: string;
|
||||
disambiguation: string;
|
||||
disambiguation?: string;
|
||||
albumType: string;
|
||||
monitored: boolean;
|
||||
statistics: Statistics;
|
||||
|
|
|
@ -33,7 +33,7 @@ function ChangeMonitoringModalContent(
|
|||
const [monitor, setMonitor] = useState(NO_CHANGE);
|
||||
|
||||
const onInputChange = useCallback(
|
||||
({ value }) => {
|
||||
({ value }: { value: string }) => {
|
||||
setMonitor(value);
|
||||
},
|
||||
[setMonitor]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import React, { SyntheticEvent, useCallback } from 'react';
|
||||
import { useSelect } from 'App/SelectContext';
|
||||
import Icon from 'Components/Icon';
|
||||
import Link from 'Components/Link/Link';
|
||||
|
@ -15,8 +15,9 @@ function ArtistIndexPosterSelect(props: ArtistIndexPosterSelectProps) {
|
|||
const isSelected = selectState.selectedState[artistId];
|
||||
|
||||
const onSelectPress = useCallback(
|
||||
(event) => {
|
||||
const shiftKey = event.nativeEvent.shiftKey;
|
||||
(event: SyntheticEvent) => {
|
||||
const nativeEvent = event.nativeEvent as PointerEvent;
|
||||
const shiftKey = nativeEvent.shiftKey;
|
||||
|
||||
selectDispatch({
|
||||
type: 'toggleSelected',
|
||||
|
|
|
@ -6,7 +6,7 @@ import { icons } from 'Helpers/Props';
|
|||
interface ArtistIndexSelectAllButtonProps {
|
||||
label: string;
|
||||
isSelectMode: boolean;
|
||||
overflowComponent: React.FunctionComponent;
|
||||
overflowComponent: React.FunctionComponent<never>;
|
||||
}
|
||||
|
||||
function ArtistIndexSelectAllButton(props: ArtistIndexSelectAllButtonProps) {
|
||||
|
|
|
@ -24,6 +24,14 @@ import OrganizeArtistModal from './Organize/OrganizeArtistModal';
|
|||
import TagsModal from './Tags/TagsModal';
|
||||
import styles from './ArtistIndexSelectFooter.css';
|
||||
|
||||
interface SavePayload {
|
||||
monitored?: boolean;
|
||||
qualityProfileId?: number;
|
||||
metadataProfileId?: number;
|
||||
rootFolderPath?: string;
|
||||
moveFiles?: boolean;
|
||||
}
|
||||
|
||||
const artistEditorSelector = createSelector(
|
||||
(state: AppState) => state.artist,
|
||||
(artist) => {
|
||||
|
@ -79,7 +87,7 @@ function ArtistIndexSelectFooter() {
|
|||
}, [setIsEditModalOpen]);
|
||||
|
||||
const onSavePress = useCallback(
|
||||
(payload) => {
|
||||
(payload: SavePayload) => {
|
||||
setIsSavingArtist(true);
|
||||
setIsEditModalOpen(false);
|
||||
|
||||
|
@ -118,7 +126,7 @@ function ArtistIndexSelectFooter() {
|
|||
}, [setIsTagsModalOpen]);
|
||||
|
||||
const onApplyTagsPress = useCallback(
|
||||
(tags, applyTags) => {
|
||||
(tags: number[], applyTags: string) => {
|
||||
setIsSavingTags(true);
|
||||
setIsTagsModalOpen(false);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ interface ArtistIndexSelectModeButtonProps {
|
|||
label: string;
|
||||
iconName: IconDefinition;
|
||||
isSelectMode: boolean;
|
||||
overflowComponent: React.FunctionComponent;
|
||||
overflowComponent: React.FunctionComponent<never>;
|
||||
onPress: () => void;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,9 +28,15 @@ function RetagArtistModalContent(props: RetagArtistModalContentProps) {
|
|||
const dispatch = useDispatch();
|
||||
|
||||
const artistNames = useMemo(() => {
|
||||
const artists = artistIds.map((id) => {
|
||||
return allArtists.find((a) => a.id === id);
|
||||
});
|
||||
const artists = artistIds.reduce((acc: Artist[], id) => {
|
||||
const a = allArtists.find((a) => a.id === id);
|
||||
|
||||
if (a) {
|
||||
acc.push(a);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const sorted = orderBy(artists, ['sortName']);
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
|
|||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import { bulkDeleteArtist, setDeleteOption } from 'Store/Actions/artistActions';
|
||||
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
|
||||
import { CheckInputChanged } from 'typings/inputs';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './DeleteArtistModalContent.css';
|
||||
|
||||
|
@ -37,16 +38,16 @@ function DeleteArtistModalContent(props: DeleteArtistModalContentProps) {
|
|||
|
||||
const [deleteFiles, setDeleteFiles] = useState(false);
|
||||
|
||||
const artists = useMemo(() => {
|
||||
const artists = artistIds.map((id) => {
|
||||
const artists = useMemo((): Artist[] => {
|
||||
const artistList = artistIds.map((id) => {
|
||||
return allArtists.find((a) => a.id === id);
|
||||
});
|
||||
}) as Artist[];
|
||||
|
||||
return orderBy(artists, ['sortName']);
|
||||
return orderBy(artistList, ['sortName']);
|
||||
}, [artistIds, allArtists]);
|
||||
|
||||
const onDeleteFilesChange = useCallback(
|
||||
({ value }) => {
|
||||
({ value }: CheckInputChanged) => {
|
||||
setDeleteFiles(value);
|
||||
},
|
||||
[setDeleteFiles]
|
||||
|
|
|
@ -66,7 +66,7 @@ function EditArtistModalContent(props: EditArtistModalContentProps) {
|
|||
const [isConfirmMoveModalOpen, setIsConfirmMoveModalOpen] = useState(false);
|
||||
|
||||
const save = useCallback(
|
||||
(moveFiles) => {
|
||||
(moveFiles: boolean) => {
|
||||
let hasChanges = false;
|
||||
const payload: SavePayload = {};
|
||||
|
||||
|
@ -114,7 +114,7 @@ function EditArtistModalContent(props: EditArtistModalContentProps) {
|
|||
);
|
||||
|
||||
const onInputChange = useCallback(
|
||||
({ name, value }) => {
|
||||
({ name, value }: { name: string; value: string }) => {
|
||||
switch (name) {
|
||||
case 'monitored':
|
||||
setMonitored(value);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { uniq } from 'lodash';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Tag } from 'App/State/TagsAppState';
|
||||
import Artist from 'Artist/Artist';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
|
@ -28,7 +29,7 @@ function TagsModalContent(props: TagsModalContentProps) {
|
|||
const { artistIds, onModalClose, onApplyTagsPress } = props;
|
||||
|
||||
const allArtists: Artist[] = useSelector(createAllArtistSelector());
|
||||
const tagList = useSelector(createTagsSelector());
|
||||
const tagList: Tag[] = useSelector(createTagsSelector());
|
||||
|
||||
const [tags, setTags] = useState<number[]>([]);
|
||||
const [applyTags, setApplyTags] = useState('add');
|
||||
|
@ -48,14 +49,14 @@ function TagsModalContent(props: TagsModalContentProps) {
|
|||
}, [artistIds, allArtists]);
|
||||
|
||||
const onTagsChange = useCallback(
|
||||
({ value }) => {
|
||||
({ value }: { value: number[] }) => {
|
||||
setTags(value);
|
||||
},
|
||||
[setTags]
|
||||
);
|
||||
|
||||
const onApplyTagsChange = useCallback(
|
||||
({ value }) => {
|
||||
({ value }: { value: string }) => {
|
||||
setApplyTags(value);
|
||||
},
|
||||
[setApplyTags]
|
||||
|
|
|
@ -23,6 +23,7 @@ import Column from 'Components/Table/Column';
|
|||
import TagListConnector from 'Components/TagListConnector';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { SelectStateInputProps } from 'typings/props';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import firstCharToUpper from 'Utilities/String/firstCharToUpper';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
@ -128,7 +129,7 @@ function ArtistIndexRow(props: ArtistIndexRowProps) {
|
|||
}, [setIsDeleteArtistModalOpen]);
|
||||
|
||||
const onSelectedChange = useCallback(
|
||||
({ id, value, shiftKey }) => {
|
||||
({ id, value, shiftKey }: SelectStateInputProps) => {
|
||||
selectDispatch({
|
||||
type: 'toggleSelected',
|
||||
id,
|
||||
|
@ -219,7 +220,7 @@ function ArtistIndexRow(props: ArtistIndexRowProps) {
|
|||
if (name === 'qualityProfileId') {
|
||||
return (
|
||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||
{qualityProfile.name}
|
||||
{qualityProfile?.name ?? ''}
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
@ -227,7 +228,7 @@ function ArtistIndexRow(props: ArtistIndexRowProps) {
|
|||
if (name === 'metadataProfileId') {
|
||||
return (
|
||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||
{metadataProfile.name}
|
||||
{metadataProfile?.name ?? ''}
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
@ -280,6 +281,8 @@ function ArtistIndexRow(props: ArtistIndexRowProps) {
|
|||
|
||||
if (name === 'added') {
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore ts(2739)
|
||||
<RelativeDateCellConnector
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { throttle } from 'lodash';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { RefObject, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { FixedSizeList as List, ListChildComponentProps } from 'react-window';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import Artist from 'Artist/Artist';
|
||||
import ArtistIndexRow from 'Artist/Index/Table/ArtistIndexRow';
|
||||
import ArtistIndexTableHeader from 'Artist/Index/Table/ArtistIndexTableHeader';
|
||||
|
@ -30,17 +31,17 @@ interface RowItemData {
|
|||
|
||||
interface ArtistIndexTableProps {
|
||||
items: Artist[];
|
||||
sortKey?: string;
|
||||
sortKey: string;
|
||||
sortDirection?: SortDirection;
|
||||
jumpToCharacter?: string;
|
||||
scrollTop?: number;
|
||||
scrollerRef: React.MutableRefObject<HTMLElement>;
|
||||
scrollerRef: RefObject<HTMLElement>;
|
||||
isSelectMode: boolean;
|
||||
isSmallScreen: boolean;
|
||||
}
|
||||
|
||||
const columnsSelector = createSelector(
|
||||
(state) => state.artistIndex.columns,
|
||||
(state: AppState) => state.artistIndex.columns,
|
||||
(columns) => columns
|
||||
);
|
||||
|
||||
|
@ -93,7 +94,7 @@ function ArtistIndexTable(props: ArtistIndexTableProps) {
|
|||
|
||||
const columns = useSelector(columnsSelector);
|
||||
const { showBanners } = useSelector(selectTableOptions);
|
||||
const listRef: React.MutableRefObject<List> = useRef();
|
||||
const listRef = useRef<List<RowItemData>>(null);
|
||||
const [measureRef, bounds] = useMeasure();
|
||||
const [size, setSize] = useState({ width: 0, height: 0 });
|
||||
const windowWidth = window.innerWidth;
|
||||
|
@ -104,7 +105,7 @@ function ArtistIndexTable(props: ArtistIndexTableProps) {
|
|||
}, [showBanners]);
|
||||
|
||||
useEffect(() => {
|
||||
const current = scrollerRef.current as HTMLElement;
|
||||
const current = scrollerRef?.current as HTMLElement;
|
||||
|
||||
if (isSmallScreen) {
|
||||
setSize({
|
||||
|
@ -128,8 +129,8 @@ function ArtistIndexTable(props: ArtistIndexTableProps) {
|
|||
}, [isSmallScreen, windowWidth, windowHeight, scrollerRef, bounds]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentScrollListener = isSmallScreen ? window : scrollerRef.current;
|
||||
const currentScrollerRef = scrollerRef.current;
|
||||
const currentScrollerRef = scrollerRef.current as HTMLElement;
|
||||
const currentScrollListener = isSmallScreen ? window : currentScrollerRef;
|
||||
|
||||
const handleScroll = throttle(() => {
|
||||
const { offsetTop = 0 } = currentScrollerRef;
|
||||
|
@ -138,7 +139,7 @@ function ArtistIndexTable(props: ArtistIndexTableProps) {
|
|||
? getWindowScrollTopPosition()
|
||||
: currentScrollerRef.scrollTop) - offsetTop;
|
||||
|
||||
listRef.current.scrollTo(scrollTop);
|
||||
listRef.current?.scrollTo(scrollTop);
|
||||
}, 10);
|
||||
|
||||
currentScrollListener.addEventListener('scroll', handleScroll);
|
||||
|
@ -167,8 +168,8 @@ function ArtistIndexTable(props: ArtistIndexTableProps) {
|
|||
scrollTop += offset;
|
||||
}
|
||||
|
||||
listRef.current.scrollTo(scrollTop);
|
||||
scrollerRef.current.scrollTo(0, scrollTop);
|
||||
listRef.current?.scrollTo(scrollTop);
|
||||
scrollerRef?.current?.scrollTo(0, scrollTop);
|
||||
}
|
||||
}
|
||||
}, [jumpToCharacter, rowHeight, items, scrollerRef, listRef]);
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
setArtistSort,
|
||||
setArtistTableOption,
|
||||
} from 'Store/Actions/artistIndexActions';
|
||||
import { CheckInputChanged } from 'typings/inputs';
|
||||
import hasGrowableColumns from './hasGrowableColumns';
|
||||
import styles from './ArtistIndexTableHeader.css';
|
||||
|
||||
|
@ -32,21 +33,21 @@ function ArtistIndexTableHeader(props: ArtistIndexTableHeaderProps) {
|
|||
const [selectState, selectDispatch] = useSelect();
|
||||
|
||||
const onSortPress = useCallback(
|
||||
(value) => {
|
||||
(value: string) => {
|
||||
dispatch(setArtistSort({ sortKey: value }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const onTableOptionChange = useCallback(
|
||||
(payload) => {
|
||||
(payload: unknown) => {
|
||||
dispatch(setArtistTableOption(payload));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const onSelectAllChange = useCallback(
|
||||
({ value }) => {
|
||||
({ value }: CheckInputChanged) => {
|
||||
selectDispatch({
|
||||
type: value ? 'selectAll' : 'unselectAll',
|
||||
});
|
||||
|
@ -94,6 +95,8 @@ function ArtistIndexTableHeader(props: ArtistIndexTableHeaderProps) {
|
|||
<VirtualTableHeaderCell
|
||||
key={name}
|
||||
className={classNames(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
styles[name],
|
||||
name === 'sortName' && showBanners && styles.banner,
|
||||
name === 'sortName' &&
|
||||
|
|
|
@ -4,6 +4,7 @@ import FormGroup from 'Components/Form/FormGroup';
|
|||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import { CheckInputChanged } from 'typings/inputs';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import selectTableOptions from './selectTableOptions';
|
||||
|
||||
|
@ -19,7 +20,7 @@ function ArtistIndexTableOptions(props: ArtistIndexTableOptionsProps) {
|
|||
const { showBanners, showSearchAction } = tableOptions;
|
||||
|
||||
const onTableOptionChangeWrapper = useCallback(
|
||||
({ name, value }) => {
|
||||
({ name, value }: CheckInputChanged) => {
|
||||
onTableOptionChange({
|
||||
tableOptions: {
|
||||
...tableOptions,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import Artist from 'Artist/Artist';
|
||||
import Command from 'Commands/Command';
|
||||
import { ARTIST_SEARCH, REFRESH_ARTIST } from 'Commands/commandNames';
|
||||
import createArtistMetadataProfileSelector from 'Store/Selectors/createArtistMetadataProfileSelector';
|
||||
import createArtistQualityProfileSelector from 'Store/Selectors/createArtistQualityProfileSelector';
|
||||
|
@ -12,25 +13,21 @@ function createArtistIndexItemSelector(artistId: number) {
|
|||
createArtistQualityProfileSelector(artistId),
|
||||
createArtistMetadataProfileSelector(artistId),
|
||||
createExecutingCommandsSelector(),
|
||||
(artist: Artist, qualityProfile, metadataProfile, executingCommands) => {
|
||||
// If an artist is deleted this selector may fire before the parent
|
||||
// selectors, which will result in an undefined artist, if that happens
|
||||
// we want to return early here and again in the render function to avoid
|
||||
// trying to show an artist that has no information available.
|
||||
|
||||
if (!artist) {
|
||||
return {};
|
||||
}
|
||||
|
||||
(
|
||||
artist: Artist,
|
||||
qualityProfile,
|
||||
metadataProfile,
|
||||
executingCommands: Command[]
|
||||
) => {
|
||||
const isRefreshingArtist = executingCommands.some((command) => {
|
||||
return (
|
||||
command.name === REFRESH_ARTIST && command.body.artistId === artist.id
|
||||
command.name === REFRESH_ARTIST && command.body.artistId === artistId
|
||||
);
|
||||
});
|
||||
|
||||
const isSearchingArtist = executingCommands.some((command) => {
|
||||
return (
|
||||
command.name === ARTIST_SEARCH && command.body.artistId === artist.id
|
||||
command.name === ARTIST_SEARCH && command.body.artistId === artistId
|
||||
);
|
||||
});
|
||||
|
||||
|
|
37
frontend/src/Commands/Command.ts
Normal file
37
frontend/src/Commands/Command.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import ModelBase from 'App/ModelBase';
|
||||
|
||||
export interface CommandBody {
|
||||
sendUpdatesToClient: boolean;
|
||||
updateScheduledTask: boolean;
|
||||
completionMessage: string;
|
||||
requiresDiskAccess: boolean;
|
||||
isExclusive: boolean;
|
||||
isLongRunning: boolean;
|
||||
name: string;
|
||||
lastExecutionTime: string;
|
||||
lastStartTime: string;
|
||||
trigger: string;
|
||||
suppressMessages: boolean;
|
||||
artistId?: number;
|
||||
}
|
||||
|
||||
interface Command extends ModelBase {
|
||||
name: string;
|
||||
commandName: string;
|
||||
message: string;
|
||||
body: CommandBody;
|
||||
priority: string;
|
||||
status: string;
|
||||
result: string;
|
||||
queued: string;
|
||||
started: string;
|
||||
ended: string;
|
||||
duration: string;
|
||||
trigger: string;
|
||||
stateChangeTime: string;
|
||||
sendUpdatesToClient: boolean;
|
||||
updateScheduledTask: boolean;
|
||||
lastExecutionTime: string;
|
||||
}
|
||||
|
||||
export default Command;
|
|
@ -1,5 +1,5 @@
|
|||
import React, { forwardRef, ReactNode, useCallback } from 'react';
|
||||
import Scroller from 'Components/Scroller/Scroller';
|
||||
import React, { ForwardedRef, forwardRef, ReactNode, useCallback } from 'react';
|
||||
import Scroller, { OnScroll } from 'Components/Scroller/Scroller';
|
||||
import ScrollDirection from 'Helpers/Props/ScrollDirection';
|
||||
import { isLocked } from 'Utilities/scrollLock';
|
||||
import styles from './PageContentBody.css';
|
||||
|
@ -9,14 +9,11 @@ interface PageContentBodyProps {
|
|||
innerClassName?: string;
|
||||
children: ReactNode;
|
||||
initialScrollTop?: number;
|
||||
onScroll?: (payload) => void;
|
||||
onScroll?: (payload: OnScroll) => void;
|
||||
}
|
||||
|
||||
const PageContentBody = forwardRef(
|
||||
(
|
||||
props: PageContentBodyProps,
|
||||
ref: React.MutableRefObject<HTMLDivElement>
|
||||
) => {
|
||||
(props: PageContentBodyProps, ref: ForwardedRef<HTMLDivElement>) => {
|
||||
const {
|
||||
className = styles.contentBody,
|
||||
innerClassName = styles.innerContentBody,
|
||||
|
@ -26,7 +23,7 @@ const PageContentBody = forwardRef(
|
|||
} = props;
|
||||
|
||||
const onScrollWrapper = useCallback(
|
||||
(payload) => {
|
||||
(payload: OnScroll) => {
|
||||
if (onScroll && !isLocked()) {
|
||||
onScroll(payload);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,21 @@
|
|||
import classNames from 'classnames';
|
||||
import { throttle } from 'lodash';
|
||||
import React, { forwardRef, ReactNode, useEffect, useRef } from 'react';
|
||||
import React, {
|
||||
ForwardedRef,
|
||||
forwardRef,
|
||||
MutableRefObject,
|
||||
ReactNode,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import ScrollDirection from 'Helpers/Props/ScrollDirection';
|
||||
import styles from './Scroller.css';
|
||||
|
||||
export interface OnScroll {
|
||||
scrollLeft: number;
|
||||
scrollTop: number;
|
||||
}
|
||||
|
||||
interface ScrollerProps {
|
||||
className?: string;
|
||||
scrollDirection?: ScrollDirection;
|
||||
|
@ -12,11 +24,11 @@ interface ScrollerProps {
|
|||
scrollTop?: number;
|
||||
initialScrollTop?: number;
|
||||
children?: ReactNode;
|
||||
onScroll?: (payload) => void;
|
||||
onScroll?: (payload: OnScroll) => void;
|
||||
}
|
||||
|
||||
const Scroller = forwardRef(
|
||||
(props: ScrollerProps, ref: React.MutableRefObject<HTMLDivElement>) => {
|
||||
(props: ScrollerProps, ref: ForwardedRef<HTMLDivElement>) => {
|
||||
const {
|
||||
className,
|
||||
autoFocus = false,
|
||||
|
@ -30,7 +42,7 @@ const Scroller = forwardRef(
|
|||
} = props;
|
||||
|
||||
const internalRef = useRef();
|
||||
const currentRef = ref ?? internalRef;
|
||||
const currentRef = (ref as MutableRefObject<HTMLDivElement>) ?? internalRef;
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
|
|
|
@ -7,6 +7,8 @@ import { scrollDirections } from 'Helpers/Props';
|
|||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||
import styles from './VirtualTable.css';
|
||||
|
||||
const ROW_HEIGHT = 38;
|
||||
|
||||
function overscanIndicesGetter(options) {
|
||||
const {
|
||||
cellCount,
|
||||
|
@ -48,8 +50,7 @@ class VirtualTable extends Component {
|
|||
const {
|
||||
items,
|
||||
scrollIndex,
|
||||
scrollTop,
|
||||
onRecompute
|
||||
scrollTop
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
|
@ -57,10 +58,7 @@ class VirtualTable extends Component {
|
|||
scrollRestored
|
||||
} = this.state;
|
||||
|
||||
if (this._grid &&
|
||||
(prevState.width !== width ||
|
||||
hasDifferentItemsOrOrder(prevProps.items, items))) {
|
||||
onRecompute(width);
|
||||
if (this._grid && (prevState.width !== width || hasDifferentItemsOrOrder(prevProps.items, items))) {
|
||||
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
||||
this._grid.recomputeGridSize();
|
||||
}
|
||||
|
@ -103,7 +101,6 @@ class VirtualTable extends Component {
|
|||
className,
|
||||
items,
|
||||
scroller,
|
||||
scrollTop: ignored,
|
||||
header,
|
||||
headerHeight,
|
||||
rowHeight,
|
||||
|
@ -149,6 +146,7 @@ class VirtualTable extends Component {
|
|||
{header}
|
||||
<div ref={registerChild}>
|
||||
<Grid
|
||||
{...otherProps}
|
||||
ref={this.setGridRef}
|
||||
autoContainerWidth={true}
|
||||
autoHeight={true}
|
||||
|
@ -170,7 +168,6 @@ class VirtualTable extends Component {
|
|||
className={styles.tableBodyContainer}
|
||||
style={gridStyle}
|
||||
containerStyle={containerStyle}
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
</Scroller>
|
||||
|
@ -192,16 +189,14 @@ VirtualTable.propTypes = {
|
|||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
header: PropTypes.node.isRequired,
|
||||
headerHeight: PropTypes.number.isRequired,
|
||||
rowHeight: PropTypes.oneOfType([PropTypes.func, PropTypes.number]).isRequired,
|
||||
rowRenderer: PropTypes.func.isRequired,
|
||||
onRecompute: PropTypes.func.isRequired
|
||||
rowHeight: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
VirtualTable.defaultProps = {
|
||||
className: styles.tableContainer,
|
||||
headerHeight: 38,
|
||||
rowHeight: 38,
|
||||
onRecompute: () => {}
|
||||
rowHeight: ROW_HEIGHT
|
||||
};
|
||||
|
||||
export default VirtualTable;
|
||||
|
|
|
@ -1,24 +1,30 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import scrollPositions from 'Store/scrollPositions';
|
||||
|
||||
function withScrollPosition(WrappedComponent, scrollPositionKey) {
|
||||
function ScrollPosition(props) {
|
||||
interface WrappedComponentProps {
|
||||
initialScrollTop: number;
|
||||
}
|
||||
|
||||
interface ScrollPositionProps {
|
||||
history: RouteComponentProps['history'];
|
||||
location: RouteComponentProps['location'];
|
||||
match: RouteComponentProps['match'];
|
||||
}
|
||||
|
||||
function withScrollPosition(
|
||||
WrappedComponent: React.FC<WrappedComponentProps>,
|
||||
scrollPositionKey: string
|
||||
) {
|
||||
function ScrollPosition(props: ScrollPositionProps) {
|
||||
const { history } = props;
|
||||
|
||||
const initialScrollTop =
|
||||
history.action === 'POP' ||
|
||||
(history.location.state && history.location.state.restoreScrollPosition)
|
||||
? scrollPositions[scrollPositionKey]
|
||||
: 0;
|
||||
history.action === 'POP' ? scrollPositions[scrollPositionKey] : 0;
|
||||
|
||||
return <WrappedComponent {...props} initialScrollTop={initialScrollTop} />;
|
||||
}
|
||||
|
||||
ScrollPosition.propTypes = {
|
||||
history: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
return ScrollPosition;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
const scrollPositions = {
|
||||
artistIndex: 0
|
||||
};
|
||||
|
||||
export default scrollPositions;
|
5
frontend/src/Store/scrollPositions.ts
Normal file
5
frontend/src/Store/scrollPositions.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
const scrollPositions: Record<string, number> = {
|
||||
artistIndex: 0,
|
||||
};
|
||||
|
||||
export default scrollPositions;
|
|
@ -1,27 +0,0 @@
|
|||
const thunks = {};
|
||||
|
||||
function identity(payload) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
export function createThunk(type, identityFunction = identity) {
|
||||
return function(payload = {}) {
|
||||
return function(dispatch, getState) {
|
||||
const thunk = thunks[type];
|
||||
|
||||
if (thunk) {
|
||||
return thunk(getState, identityFunction(payload), dispatch);
|
||||
}
|
||||
|
||||
throw Error(`Thunk handler has not been registered for ${type}`);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function handleThunks(handlers) {
|
||||
const types = Object.keys(handlers);
|
||||
|
||||
types.forEach((type) => {
|
||||
thunks[type] = handlers[type];
|
||||
});
|
||||
}
|
39
frontend/src/Store/thunks.ts
Normal file
39
frontend/src/Store/thunks.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { Dispatch } from 'redux';
|
||||
import AppState from 'App/State/AppState';
|
||||
|
||||
type GetState = () => AppState;
|
||||
type Thunk = (
|
||||
getState: GetState,
|
||||
identityFn: never,
|
||||
dispatch: Dispatch
|
||||
) => unknown;
|
||||
|
||||
const thunks: Record<string, Thunk> = {};
|
||||
|
||||
function identity<T, TResult>(payload: T): TResult {
|
||||
return payload as unknown as TResult;
|
||||
}
|
||||
|
||||
export function createThunk(type: string, identityFunction = identity) {
|
||||
return function <T>(payload?: T) {
|
||||
return function (dispatch: Dispatch, getState: GetState) {
|
||||
const thunk = thunks[type];
|
||||
|
||||
if (thunk) {
|
||||
const finalPayload = payload ?? {};
|
||||
|
||||
return thunk(getState, identityFunction(finalPayload), dispatch);
|
||||
}
|
||||
|
||||
throw Error(`Thunk handler has not been registered for ${type}`);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function handleThunks(handlers: Record<string, Thunk>) {
|
||||
const types = Object.keys(handlers);
|
||||
|
||||
types.forEach((type) => {
|
||||
thunks[type] = handlers[type];
|
||||
});
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
function getSelectedIds(selectedState, { parseIds = true } = {}) {
|
||||
return _.reduce(selectedState, (result, value, id) => {
|
||||
if (value) {
|
||||
const parsedId = parseIds ? parseInt(id) : id;
|
||||
|
||||
result.push(parsedId);
|
||||
}
|
||||
|
||||
return result;
|
||||
}, []);
|
||||
}
|
||||
|
||||
export default getSelectedIds;
|
18
frontend/src/Utilities/Table/getSelectedIds.ts
Normal file
18
frontend/src/Utilities/Table/getSelectedIds.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { reduce } from 'lodash';
|
||||
import { SelectedState } from 'Helpers/Hooks/useSelectState';
|
||||
|
||||
function getSelectedIds(selectedState: SelectedState): number[] {
|
||||
return reduce(
|
||||
selectedState,
|
||||
(result: number[], value, id) => {
|
||||
if (value) {
|
||||
result.push(parseInt(id));
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
export default getSelectedIds;
|
6
frontend/src/typings/callbacks.ts
Normal file
6
frontend/src/typings/callbacks.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import SortDirection from 'Helpers/Props/SortDirection';
|
||||
|
||||
export type SortCallback = (
|
||||
sortKey: string,
|
||||
sortDirection: SortDirection
|
||||
) => void;
|
4
frontend/src/typings/inputs.ts
Normal file
4
frontend/src/typings/inputs.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export type CheckInputChanged = {
|
||||
name: string;
|
||||
value: boolean;
|
||||
};
|
|
@ -7,7 +7,15 @@
|
|||
"jsx": "react",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"typeRoots": ["node_modules/@types", "typings"],
|
||||
"paths": {
|
||||
|
|
|
@ -680,6 +680,7 @@
|
|||
"Monitored": "Monitored",
|
||||
"MonitoredHelpText": "Download monitored albums from this artist",
|
||||
"MonitoredOnly": "Monitored Only",
|
||||
"MonitoredStatus": "Monitored/Status",
|
||||
"Monitoring": "Monitoring",
|
||||
"MonitoringOptions": "Monitoring Options",
|
||||
"MonitoringOptionsHelpText": "Which albums should be monitored after the artist is added (one-time adjustment)",
|
||||
|
|
Loading…
Reference in a new issue