mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-14 12:30:06 +02:00
optimize various base components
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||
@@ -147,8 +147,8 @@ export const JellyfinAlbumFilters = ({ disableArtistFilter }: JellyfinAlbumFilte
|
||||
[setMaxYear],
|
||||
);
|
||||
|
||||
const handleGenresFilter = useMemo(
|
||||
() => (e: string[] | undefined) => {
|
||||
const handleGenresFilter = useCallback(
|
||||
(e: null | string[]) => {
|
||||
setGenreId(e && e.length > 0 ? e : null);
|
||||
},
|
||||
[setGenreId],
|
||||
@@ -178,13 +178,16 @@ export const JellyfinAlbumFilters = ({ disableArtistFilter }: JellyfinAlbumFilte
|
||||
}));
|
||||
}, [albumArtistListQuery.data?.items]);
|
||||
|
||||
const handleAlbumArtistFilter = (e: null | string[]) => {
|
||||
setAlbumArtist(e ?? null);
|
||||
};
|
||||
const handleAlbumArtistFilter = useCallback(
|
||||
(e: null | string[]) => {
|
||||
setAlbumArtist(e ?? null);
|
||||
},
|
||||
[setAlbumArtist],
|
||||
);
|
||||
|
||||
const handleTagFilter = useMemo(
|
||||
() => (e: string[] | undefined) => {
|
||||
setCustom({ Tags: e?.join('|') ?? null });
|
||||
const handleTagFilter = useCallback(
|
||||
(e: null | string[]) => {
|
||||
setCustom({ Tags: e && e.length > 0 ? e.join('|') : null });
|
||||
},
|
||||
[setCustom],
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { ChangeEvent, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||
@@ -148,6 +148,28 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil
|
||||
|
||||
const debouncedHandleYearFilter = useDebouncedCallback(handleYearFilter, 300);
|
||||
|
||||
const handleGenreChange = useCallback(
|
||||
(e: null | string[]) => {
|
||||
if (e && e.length > 0) {
|
||||
setGenreId(e);
|
||||
} else {
|
||||
setGenreId(null);
|
||||
}
|
||||
},
|
||||
[setGenreId],
|
||||
);
|
||||
|
||||
const handleAlbumArtistChange = useCallback(
|
||||
(e: null | string[]) => {
|
||||
if (e && e.length > 0) {
|
||||
setAlbumArtist(e);
|
||||
} else {
|
||||
setAlbumArtist(null);
|
||||
}
|
||||
},
|
||||
[setAlbumArtist],
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack px="md" py="md">
|
||||
{yesNoUndefinedFilters.map((filter) => (
|
||||
@@ -180,7 +202,7 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil
|
||||
data={genreList}
|
||||
defaultValue={query.genreIds || []}
|
||||
label={t('entity.genre', { count: 2, postProcess: 'sentenceCase' })}
|
||||
onChange={(e) => (e && e.length > 0 ? setGenreId(e) : setGenreId(null))}
|
||||
onChange={handleGenreChange}
|
||||
searchable
|
||||
/>
|
||||
)}
|
||||
@@ -191,7 +213,7 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil
|
||||
disabled={disableArtistFilter}
|
||||
label={t('entity.artist', { count: 2, postProcess: 'sentenceCase' })}
|
||||
limit={300}
|
||||
onChange={(e) => (e && e.length > 0 ? setAlbumArtist(e) : setAlbumArtist(null))}
|
||||
onChange={handleAlbumArtistChange}
|
||||
rightSection={albumArtistListQuery.isFetching ? <SpinnerIcon /> : undefined}
|
||||
searchable
|
||||
/>
|
||||
@@ -224,6 +246,17 @@ const TagFilterItem = ({ label, onChange, options, tagValue, value }: TagFilterI
|
||||
return Array.isArray(value) ? value : [value];
|
||||
}, [value]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: null | string[]) => {
|
||||
if (e && e.length > 0) {
|
||||
onChange(e);
|
||||
} else {
|
||||
onChange(null);
|
||||
}
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<MultiSelectWithInvalidData
|
||||
clearable
|
||||
@@ -232,7 +265,7 @@ const TagFilterItem = ({ label, onChange, options, tagValue, value }: TagFilterI
|
||||
key={tagValue}
|
||||
label={label}
|
||||
limit={100}
|
||||
onChange={(e) => (e && e.length > 0 ? onChange(e) : onChange(null))}
|
||||
onChange={handleChange}
|
||||
searchable
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { ChangeEvent, useMemo, useState } from 'react';
|
||||
import { ChangeEvent, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||
@@ -63,8 +63,8 @@ export const SubsonicAlbumFilters = ({ disableArtistFilter }: SubsonicAlbumFilte
|
||||
}));
|
||||
}, [items]);
|
||||
|
||||
const handleAlbumArtistFilter = useMemo(
|
||||
() => (e: null | string[]) => {
|
||||
const handleAlbumArtistFilter = useCallback(
|
||||
(e: null | string[]) => {
|
||||
setAlbumArtist(e ?? null);
|
||||
},
|
||||
[setAlbumArtist],
|
||||
@@ -80,8 +80,8 @@ export const SubsonicAlbumFilters = ({ disableArtistFilter }: SubsonicAlbumFilte
|
||||
}));
|
||||
}, [genreListQuery.data]);
|
||||
|
||||
const handleGenresFilter = useMemo(
|
||||
() => (e: null | string) => {
|
||||
const handleGenresFilter = useCallback(
|
||||
(e: null | string) => {
|
||||
setGenreId(e ? [e] : null);
|
||||
},
|
||||
[setGenreId],
|
||||
@@ -178,7 +178,7 @@ export const SubsonicAlbumFilters = ({ disableArtistFilter }: SubsonicAlbumFilte
|
||||
defaultValue={query.genreIds?.[0] ?? undefined}
|
||||
disabled={Boolean(query.minYear || query.maxYear)}
|
||||
label={t('entity.genre', { count: 1, postProcess: 'titleCase' })}
|
||||
onChange={(e) => handleGenresFilter(e)}
|
||||
onChange={handleGenresFilter}
|
||||
searchable
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import { ComponentPropsWithoutRef } from 'react';
|
||||
import { ComponentPropsWithoutRef, memo, useMemo } from 'react';
|
||||
|
||||
import styles from './lyric-line.module.css';
|
||||
|
||||
@@ -12,23 +12,28 @@ interface LyricLineProps extends ComponentPropsWithoutRef<'div'> {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export const LyricLine = ({ alignment, className, fontSize, text, ...props }: LyricLineProps) => {
|
||||
const lines = text.split('_BREAK_');
|
||||
export const LyricLine = memo(
|
||||
({ alignment, className, fontSize, text, ...props }: LyricLineProps) => {
|
||||
const lines = useMemo(() => text.split('_BREAK_'), [text]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={clsx(styles.lyricLine, className)}
|
||||
style={{
|
||||
const style = useMemo(
|
||||
() => ({
|
||||
fontSize,
|
||||
textAlign: alignment,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<Stack gap={0}>
|
||||
{lines.map((line, index) => (
|
||||
<span key={index}>{line}</span>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
}),
|
||||
[fontSize, alignment],
|
||||
);
|
||||
|
||||
return (
|
||||
<Box className={clsx(styles.lyricLine, className)} style={style} {...props}>
|
||||
<Stack gap={0}>
|
||||
{lines.map((line, index) => (
|
||||
<span key={index}>{line}</span>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
LyricLine.displayName = 'LyricLine';
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { ChangeEvent, CSSProperties, KeyboardEvent, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
ChangeEvent,
|
||||
CSSProperties,
|
||||
KeyboardEvent,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { useSettingsStore } from '/@/renderer/store';
|
||||
@@ -90,29 +98,38 @@ export const SearchInput = ({
|
||||
const shouldShowInput = isInputMode || hasValue;
|
||||
const shouldExpand = isInputMode || hasValue;
|
||||
|
||||
const containerStyle: CSSProperties = {
|
||||
display: 'inline-flex',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
transition: 'width 0.3s ease-in-out',
|
||||
width: shouldExpand ? '200px' : '36px',
|
||||
};
|
||||
const containerStyle: CSSProperties = useMemo(
|
||||
() => ({
|
||||
display: 'inline-flex',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
transition: 'width 0.3s ease-in-out',
|
||||
width: shouldExpand ? '200px' : '36px',
|
||||
}),
|
||||
[shouldExpand],
|
||||
);
|
||||
|
||||
const buttonStyle: CSSProperties = {
|
||||
left: 0,
|
||||
opacity: shouldShowInput ? 0 : 1,
|
||||
pointerEvents: shouldShowInput ? 'none' : 'auto',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
transition: 'opacity 0.2s ease-in-out',
|
||||
zIndex: 10,
|
||||
};
|
||||
const buttonStyle: CSSProperties = useMemo(
|
||||
() => ({
|
||||
left: 0,
|
||||
opacity: shouldShowInput ? 0 : 1,
|
||||
pointerEvents: shouldShowInput ? 'none' : 'auto',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
transition: 'opacity 0.2s ease-in-out',
|
||||
zIndex: 10,
|
||||
}),
|
||||
[shouldShowInput],
|
||||
);
|
||||
|
||||
const inputStyle: CSSProperties = {
|
||||
opacity: shouldShowInput ? 1 : 0,
|
||||
transition: 'opacity 0.2s ease-in-out',
|
||||
width: '100%',
|
||||
};
|
||||
const inputStyle: CSSProperties = useMemo(
|
||||
() => ({
|
||||
opacity: shouldShowInput ? 1 : 0,
|
||||
transition: 'opacity 0.2s ease-in-out',
|
||||
width: '100%',
|
||||
}),
|
||||
[shouldShowInput],
|
||||
);
|
||||
|
||||
return (
|
||||
<Box style={containerStyle}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||
@@ -105,8 +105,8 @@ export const JellyfinSongFilters = () => {
|
||||
const debouncedHandleMinYearFilter = useDebouncedCallback(handleMinYearFilter, 300);
|
||||
const debouncedHandleMaxYearFilter = useDebouncedCallback(handleMaxYearFilter, 300);
|
||||
|
||||
const handleGenresFilter = useMemo(
|
||||
() => (e: string[] | undefined) => {
|
||||
const handleGenresFilter = useCallback(
|
||||
(e: null | string[]) => {
|
||||
setCustom((prev) => {
|
||||
const current = prev ?? {};
|
||||
|
||||
@@ -129,9 +129,9 @@ export const JellyfinSongFilters = () => {
|
||||
[setCustom],
|
||||
);
|
||||
|
||||
const handleTagFilter = useMemo(
|
||||
() => (e: string[] | undefined) => {
|
||||
setCustom({ Tags: e?.join('|') ?? null });
|
||||
const handleTagFilter = useCallback(
|
||||
(e: null | string[]) => {
|
||||
setCustom({ Tags: e && e.length > 0 ? e.join('|') : null });
|
||||
},
|
||||
[setCustom],
|
||||
);
|
||||
@@ -173,7 +173,7 @@ export const JellyfinSongFilters = () => {
|
||||
data={genreList}
|
||||
defaultValue={selectedGenres}
|
||||
label={t('entity.genre', { count: 1, postProcess: 'sentenceCase' })}
|
||||
onChange={(e) => handleGenresFilter(e)}
|
||||
onChange={handleGenresFilter}
|
||||
searchable
|
||||
/>
|
||||
)}
|
||||
@@ -183,7 +183,7 @@ export const JellyfinSongFilters = () => {
|
||||
data={tagsQuery.data.boolTags}
|
||||
defaultValue={selectedTags}
|
||||
label={t('common.tags', { postProcess: 'sentenceCase' })}
|
||||
onChange={(e) => handleTagFilter(e)}
|
||||
onChange={handleTagFilter}
|
||||
searchable
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||
@@ -73,6 +73,17 @@ export const NavidromeSongFilters = () => {
|
||||
|
||||
const debouncedHandleYearFilter = useDebouncedCallback(handleYearFilter, 300);
|
||||
|
||||
const handleGenreChange = useCallback(
|
||||
(e: null | string[]) => {
|
||||
if (e && e.length > 0) {
|
||||
setGenreId(e);
|
||||
} else {
|
||||
setGenreId(null);
|
||||
}
|
||||
},
|
||||
[setGenreId],
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack px="md" py="md">
|
||||
{yesNoUndefinedFilters.map((filter) => (
|
||||
@@ -99,7 +110,7 @@ export const NavidromeSongFilters = () => {
|
||||
data={genreList}
|
||||
defaultValue={query.genreIds || []}
|
||||
label={t('entity.genre', { count: 2, postProcess: 'sentenceCase' })}
|
||||
onChange={(e) => (e && e.length > 0 ? setGenreId(e) : setGenreId(null))}
|
||||
onChange={handleGenreChange}
|
||||
searchable
|
||||
/>
|
||||
)}
|
||||
@@ -132,6 +143,17 @@ const TagFilterItem = ({ label, onChange, options, tagValue, value }: TagFilterI
|
||||
return Array.isArray(value) ? value : [value];
|
||||
}, [value]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: null | string[]) => {
|
||||
if (e && e.length > 0) {
|
||||
onChange(e);
|
||||
} else {
|
||||
onChange(null);
|
||||
}
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<MultiSelectWithInvalidData
|
||||
clearable
|
||||
@@ -140,7 +162,7 @@ const TagFilterItem = ({ label, onChange, options, tagValue, value }: TagFilterI
|
||||
key={tagValue}
|
||||
label={label}
|
||||
limit={100}
|
||||
onChange={(e) => (e && e.length > 0 ? onChange(e) : onChange(null))}
|
||||
onChange={handleChange}
|
||||
searchable
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { ChangeEvent, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||
@@ -29,8 +29,8 @@ export const SubsonicSongFilters = () => {
|
||||
}));
|
||||
}, [genreListQuery.data]);
|
||||
|
||||
const handleGenresFilter = useMemo(
|
||||
() => (e: null | string) => {
|
||||
const handleGenresFilter = useCallback(
|
||||
(e: null | string) => {
|
||||
setGenreId(e ? [e] : null);
|
||||
},
|
||||
[setGenreId],
|
||||
|
||||
Reference in New Issue
Block a user