mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
Thumbnail scrubber improvements (#4081)
* Remove deps from useDebounce hook * Add useThrottle hook * Throttle preview scrubber * Scrubber improvements
This commit is contained in:
@@ -53,10 +53,6 @@
|
|||||||
"import/namespace": "off",
|
"import/namespace": "off",
|
||||||
"import/no-unresolved": "off",
|
"import/no-unresolved": "off",
|
||||||
"react/display-name": "off",
|
"react/display-name": "off",
|
||||||
"react-hooks/exhaustive-deps": [
|
|
||||||
"error",
|
|
||||||
{ "additionalHooks": "^(useDebounce)$" }
|
|
||||||
],
|
|
||||||
"react/prop-types": "off",
|
"react/prop-types": "off",
|
||||||
"react/style-prop-object": [
|
"react/style-prop-object": [
|
||||||
"error",
|
"error",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
import { defineMessages, MessageDescriptor, useIntl } from "react-intl";
|
import { defineMessages, MessageDescriptor, useIntl } from "react-intl";
|
||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
import { keyboardClickHandler } from "src/utils/keyboard";
|
import { keyboardClickHandler } from "src/utils/keyboard";
|
||||||
import { useDebouncedSetState } from "src/hooks/debounce";
|
import { useDebounce } from "src/hooks/debounce";
|
||||||
import useFocus from "src/utils/focus";
|
import useFocus from "src/utils/focus";
|
||||||
|
|
||||||
interface ISelectedItem {
|
interface ISelectedItem {
|
||||||
@@ -192,7 +192,7 @@ export const ObjectsFilter = <
|
|||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
const [displayQuery, setDisplayQuery] = useState(query);
|
const [displayQuery, setDisplayQuery] = useState(query);
|
||||||
|
|
||||||
const debouncedSetQuery = useDebouncedSetState(setQuery, 250);
|
const debouncedSetQuery = useDebounce(setQuery, 250);
|
||||||
const onQueryChange = useCallback(
|
const onQueryChange = useCallback(
|
||||||
(input: string) => {
|
(input: string) => {
|
||||||
setDisplayQuery(input);
|
setDisplayQuery(input);
|
||||||
|
|||||||
@@ -75,16 +75,12 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
|||||||
[filter, onFilterUpdate]
|
[filter, onFilterUpdate]
|
||||||
);
|
);
|
||||||
|
|
||||||
const searchCallback = useDebounce(
|
const searchCallback = useDebounce((value: string) => {
|
||||||
(value: string) => {
|
|
||||||
const newFilter = cloneDeep(filter);
|
const newFilter = cloneDeep(filter);
|
||||||
newFilter.searchTerm = value;
|
newFilter.searchTerm = value;
|
||||||
newFilter.currentPage = 1;
|
newFilter.currentPage = 1;
|
||||||
onFilterUpdate(newFilter);
|
onFilterUpdate(newFilter);
|
||||||
},
|
}, 500);
|
||||||
[filter, onFilterUpdate],
|
|
||||||
500
|
|
||||||
);
|
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import * as GQL from "src/core/generated-graphql";
|
|||||||
import { ModalComponent } from "src/components/Shared/Modal";
|
import { ModalComponent } from "src/components/Shared/Modal";
|
||||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||||
import { useScrapePerformerList } from "src/core/StashService";
|
import { useScrapePerformerList } from "src/core/StashService";
|
||||||
import { useDebouncedSetState } from "src/hooks/debounce";
|
import { useDebounce } from "src/hooks/debounce";
|
||||||
|
|
||||||
const CLASSNAME = "PerformerScrapeModal";
|
const CLASSNAME = "PerformerScrapeModal";
|
||||||
const CLASSNAME_LIST = `${CLASSNAME}-list`;
|
const CLASSNAME_LIST = `${CLASSNAME}-list`;
|
||||||
@@ -33,7 +33,7 @@ const PerformerScrapeModal: React.FC<IProps> = ({
|
|||||||
|
|
||||||
const performers = data?.scrapeSinglePerformer ?? [];
|
const performers = data?.scrapeSinglePerformer ?? [];
|
||||||
|
|
||||||
const onInputChange = useDebouncedSetState(setQuery, 500);
|
const onInputChange = useDebounce(setQuery, 500);
|
||||||
|
|
||||||
useEffect(() => inputRef.current?.focus(), []);
|
useEffect(() => inputRef.current?.focus(), []);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import * as GQL from "src/core/generated-graphql";
|
|||||||
import { ModalComponent } from "src/components/Shared/Modal";
|
import { ModalComponent } from "src/components/Shared/Modal";
|
||||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||||
import { stashboxDisplayName } from "src/utils/stashbox";
|
import { stashboxDisplayName } from "src/utils/stashbox";
|
||||||
import { useDebouncedSetState } from "src/hooks/debounce";
|
import { useDebounce } from "src/hooks/debounce";
|
||||||
|
|
||||||
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
||||||
import { stringToGender } from "src/utils/gender";
|
import { stringToGender } from "src/utils/gender";
|
||||||
@@ -171,7 +171,7 @@ const PerformerStashBoxModal: React.FC<IProps> = ({
|
|||||||
|
|
||||||
const performers = data?.scrapeSinglePerformer ?? [];
|
const performers = data?.scrapeSinglePerformer ?? [];
|
||||||
|
|
||||||
const onInputChange = useDebouncedSetState(setQuery, 500);
|
const onInputChange = useDebounce(setQuery, 500);
|
||||||
|
|
||||||
useEffect(() => inputRef.current?.focus(), []);
|
useEffect(() => inputRef.current?.focus(), []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import React, { useMemo } from "react";
|
import React, { useRef, useMemo, useState, useLayoutEffect } from "react";
|
||||||
import { useDebounce } from "src/hooks/debounce";
|
|
||||||
import { useSpriteInfo } from "src/hooks/sprite";
|
import { useSpriteInfo } from "src/hooks/sprite";
|
||||||
|
import { useThrottle } from "src/hooks/throttle";
|
||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
|
|
||||||
interface IHoverScrubber {
|
interface IHoverScrubber {
|
||||||
totalSprites: number;
|
totalSprites: number;
|
||||||
activeIndex: number | undefined;
|
activeIndex: number | undefined;
|
||||||
setActiveIndex: (index: number | undefined) => void;
|
setActiveIndex: (index: number | undefined) => void;
|
||||||
onClick?: (index: number) => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HoverScrubber: React.FC<IHoverScrubber> = ({
|
const HoverScrubber: React.FC<IHoverScrubber> = ({
|
||||||
@@ -20,7 +20,12 @@ const HoverScrubber: React.FC<IHoverScrubber> = ({
|
|||||||
const { width } = e.currentTarget.getBoundingClientRect();
|
const { width } = e.currentTarget.getBoundingClientRect();
|
||||||
const x = e.nativeEvent.offsetX;
|
const x = e.nativeEvent.offsetX;
|
||||||
|
|
||||||
return Math.floor((x / width) * (totalSprites - 1));
|
const i = Math.floor((x / width) * totalSprites);
|
||||||
|
|
||||||
|
// clamp to [0, totalSprites)
|
||||||
|
if (i < 0) return 0;
|
||||||
|
if (i >= totalSprites) return totalSprites - 1;
|
||||||
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseMove(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
function onMouseMove(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
||||||
@@ -43,11 +48,11 @@ const HoverScrubber: React.FC<IHoverScrubber> = ({
|
|||||||
if (relatedTarget !== e.target) return;
|
if (relatedTarget !== e.target) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onClick(getActiveIndex(e));
|
onClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
const indicatorStyle = useMemo(() => {
|
const indicatorStyle = useMemo(() => {
|
||||||
if (activeIndex === undefined) return {};
|
if (activeIndex === undefined || !totalSprites) return {};
|
||||||
|
|
||||||
const width = (activeIndex / totalSprites) * 100;
|
const width = (activeIndex / totalSprites) * 100;
|
||||||
|
|
||||||
@@ -97,56 +102,54 @@ export const PreviewScrubber: React.FC<IScenePreviewProps> = ({
|
|||||||
vttPath,
|
vttPath,
|
||||||
onClick,
|
onClick,
|
||||||
}) => {
|
}) => {
|
||||||
const imageParentRef = React.useRef<HTMLDivElement>(null);
|
const imageParentRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [style, setStyle] = useState({});
|
||||||
|
|
||||||
const [activeIndex, setActiveIndex] = React.useState<number | undefined>();
|
const [activeIndex, setActiveIndex] = useState<number>();
|
||||||
|
|
||||||
const debounceSetActiveIndex = useDebounce(
|
const debounceSetActiveIndex = useThrottle(setActiveIndex, 50);
|
||||||
setActiveIndex,
|
|
||||||
[setActiveIndex],
|
|
||||||
1
|
|
||||||
);
|
|
||||||
|
|
||||||
const spriteInfo = useSpriteInfo(vttPath);
|
const spriteInfo = useSpriteInfo(vttPath);
|
||||||
|
|
||||||
const style = useMemo(() => {
|
const sprite = useMemo(() => {
|
||||||
if (!spriteInfo || activeIndex === undefined || !imageParentRef.current) {
|
if (!spriteInfo || activeIndex === undefined) {
|
||||||
return {};
|
return undefined;
|
||||||
|
}
|
||||||
|
return spriteInfo[activeIndex];
|
||||||
|
}, [activeIndex, spriteInfo]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const imageParent = imageParentRef.current;
|
||||||
|
|
||||||
|
if (!sprite || !imageParent) {
|
||||||
|
return setStyle({});
|
||||||
}
|
}
|
||||||
|
|
||||||
const sprite = spriteInfo[activeIndex];
|
const clientRect = imageParent.getBoundingClientRect();
|
||||||
|
const scale = scaleToFit(sprite, clientRect);
|
||||||
|
|
||||||
const clientRect = imageParentRef.current?.getBoundingClientRect();
|
setStyle({
|
||||||
const scale = clientRect ? scaleToFit(sprite, clientRect) : 1;
|
|
||||||
|
|
||||||
return {
|
|
||||||
backgroundPosition: `${-sprite.x}px ${-sprite.y}px`,
|
backgroundPosition: `${-sprite.x}px ${-sprite.y}px`,
|
||||||
backgroundImage: `url(${sprite.url})`,
|
backgroundImage: `url(${sprite.url})`,
|
||||||
width: `${sprite.w}px`,
|
width: `${sprite.w}px`,
|
||||||
height: `${sprite.h}px`,
|
height: `${sprite.h}px`,
|
||||||
transform: `scale(${scale})`,
|
transform: `scale(${scale})`,
|
||||||
};
|
});
|
||||||
}, [spriteInfo, activeIndex, imageParentRef]);
|
}, [sprite]);
|
||||||
|
|
||||||
const currentTime = useMemo(() => {
|
const currentTime = useMemo(() => {
|
||||||
if (!spriteInfo || activeIndex === undefined) {
|
if (!sprite) return undefined;
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sprite = spriteInfo[activeIndex];
|
|
||||||
|
|
||||||
const start = TextUtils.secondsToTimestamp(sprite.start);
|
const start = TextUtils.secondsToTimestamp(sprite.start);
|
||||||
|
|
||||||
return start;
|
return start;
|
||||||
}, [activeIndex, spriteInfo]);
|
}, [sprite]);
|
||||||
|
|
||||||
function onScrubberClick(index: number) {
|
function onScrubberClick() {
|
||||||
if (!spriteInfo || !onClick) {
|
if (!sprite || !onClick) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sprite = spriteInfo[index];
|
|
||||||
|
|
||||||
onClick(sprite.start);
|
onClick(sprite.start);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +157,7 @@ export const PreviewScrubber: React.FC<IScenePreviewProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="preview-scrubber">
|
<div className="preview-scrubber">
|
||||||
{activeIndex !== undefined && spriteInfo && (
|
{sprite && (
|
||||||
<div className="scene-card-preview-image" ref={imageParentRef}>
|
<div className="scene-card-preview-image" ref={imageParentRef}>
|
||||||
<div className="scrubber-image" style={style}></div>
|
<div className="scrubber-image" style={style}></div>
|
||||||
{currentTime !== undefined && (
|
{currentTime !== undefined && (
|
||||||
@@ -163,7 +166,7 @@ export const PreviewScrubber: React.FC<IScenePreviewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<HoverScrubber
|
<HoverScrubber
|
||||||
totalSprites={81}
|
totalSprites={spriteInfo.length}
|
||||||
activeIndex={activeIndex}
|
activeIndex={activeIndex}
|
||||||
setActiveIndex={(i) => debounceSetActiveIndex(i)}
|
setActiveIndex={(i) => debounceSetActiveIndex(i)}
|
||||||
onClick={onScrubberClick}
|
onClick={onScrubberClick}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||||||
setUI(data.configuration.ui);
|
setUI(data.configuration.ui);
|
||||||
}, [data, error]);
|
}, [data, error]);
|
||||||
|
|
||||||
const resetSuccess = useDebounce(() => setUpdateSuccess(undefined), [], 4000);
|
const resetSuccess = useDebounce(() => setUpdateSuccess(undefined), 4000);
|
||||||
|
|
||||||
const onSuccess = useCallback(() => {
|
const onSuccess = useCallback(() => {
|
||||||
setUpdateSuccess(true);
|
setUpdateSuccess(true);
|
||||||
@@ -158,7 +158,6 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||||||
setSaveError(e);
|
setSaveError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[updateGeneralConfig, onSuccess],
|
|
||||||
500
|
500
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -208,7 +207,6 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||||||
setSaveError(e);
|
setSaveError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[updateInterfaceConfig, onSuccess],
|
|
||||||
500
|
500
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -258,7 +256,6 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||||||
setSaveError(e);
|
setSaveError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[updateDefaultsConfig, onSuccess],
|
|
||||||
500
|
500
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -308,7 +305,6 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||||||
setSaveError(e);
|
setSaveError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[updateScrapingConfig, onSuccess],
|
|
||||||
500
|
500
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -342,8 +338,7 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// saves the configuration if no further changes are made after a half second
|
// saves the configuration if no further changes are made after a half second
|
||||||
const saveDLNAConfig = useDebounce(
|
const saveDLNAConfig = useDebounce(async (input: GQL.ConfigDlnaInput) => {
|
||||||
async (input: GQL.ConfigDlnaInput) => {
|
|
||||||
try {
|
try {
|
||||||
setUpdateSuccess(undefined);
|
setUpdateSuccess(undefined);
|
||||||
await updateDLNAConfig({
|
await updateDLNAConfig({
|
||||||
@@ -357,10 +352,7 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
setSaveError(e);
|
setSaveError(e);
|
||||||
}
|
}
|
||||||
},
|
}, 500);
|
||||||
[updateDLNAConfig, onSuccess],
|
|
||||||
500
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!pendingDLNA) {
|
if (!pendingDLNA) {
|
||||||
@@ -392,8 +384,7 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// saves the configuration if no further changes are made after a half second
|
// saves the configuration if no further changes are made after a half second
|
||||||
const saveUIConfig = useDebounce(
|
const saveUIConfig = useDebounce(async (input: IUIConfig) => {
|
||||||
async (input: IUIConfig) => {
|
|
||||||
try {
|
try {
|
||||||
setUpdateSuccess(undefined);
|
setUpdateSuccess(undefined);
|
||||||
await updateUIConfig({
|
await updateUIConfig({
|
||||||
@@ -407,10 +398,7 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
setSaveError(e);
|
setSaveError(e);
|
||||||
}
|
}
|
||||||
},
|
}, 500);
|
||||||
[updateUIConfig, onSuccess],
|
|
||||||
500
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!pendingUI) {
|
if (!pendingUI) {
|
||||||
|
|||||||
@@ -229,13 +229,9 @@ export const FilterSelectComponent = <
|
|||||||
};
|
};
|
||||||
|
|
||||||
const debounceDelay = 100;
|
const debounceDelay = 100;
|
||||||
const debounceLoadOptions = useDebounce(
|
const debounceLoadOptions = useDebounce((inputValue, callback) => {
|
||||||
(inputValue, callback) => {
|
|
||||||
loadOptions(inputValue).then(callback);
|
loadOptions(inputValue).then(callback);
|
||||||
},
|
}, debounceDelay);
|
||||||
[loadOptions],
|
|
||||||
debounceDelay
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectComponent<T, IsMulti>
|
<SelectComponent<T, IsMulti>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Icon } from "../Icon";
|
|||||||
import { LoadingIndicator } from "../LoadingIndicator";
|
import { LoadingIndicator } from "../LoadingIndicator";
|
||||||
import { useDirectory } from "src/core/StashService";
|
import { useDirectory } from "src/core/StashService";
|
||||||
import { faEllipsis, faTimes } from "@fortawesome/free-solid-svg-icons";
|
import { faEllipsis, faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { useDebouncedSetState } from "src/hooks/debounce";
|
import { useDebounce } from "src/hooks/debounce";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
currentDirectory: string;
|
currentDirectory: string;
|
||||||
@@ -44,7 +44,7 @@ export const FolderSelect: React.FC<IProps> = ({
|
|||||||
(error && hideError ? [] : defaultDirectoriesOrEmpty)
|
(error && hideError ? [] : defaultDirectoriesOrEmpty)
|
||||||
: defaultDirectoriesOrEmpty;
|
: defaultDirectoriesOrEmpty;
|
||||||
|
|
||||||
const debouncedSetDirectory = useDebouncedSetState(setDirectory, 250);
|
const debouncedSetDirectory = useDebounce(setDirectory, 250);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentDirectory !== directory) {
|
if (currentDirectory !== directory) {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import { objectTitle } from "src/core/files";
|
|||||||
import { galleryTitle } from "src/core/galleries";
|
import { galleryTitle } from "src/core/galleries";
|
||||||
import { TagPopover } from "../Tags/TagPopover";
|
import { TagPopover } from "../Tags/TagPopover";
|
||||||
import { defaultMaxOptionsShown, IUIConfig } from "src/core/config";
|
import { defaultMaxOptionsShown, IUIConfig } from "src/core/config";
|
||||||
import { useDebouncedSetState } from "src/hooks/debounce";
|
import { useDebounce } from "src/hooks/debounce";
|
||||||
import { Placement } from "react-bootstrap/esm/Overlay";
|
import { Placement } from "react-bootstrap/esm/Overlay";
|
||||||
import { PerformerIDSelect } from "../Performers/PerformerSelect";
|
import { PerformerIDSelect } from "../Performers/PerformerSelect";
|
||||||
|
|
||||||
@@ -352,7 +352,7 @@ export const GallerySelect: React.FC<ITitledSelect> = (props) => {
|
|||||||
value: g.id,
|
value: g.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const onInputChange = useDebouncedSetState(setQuery, 500);
|
const onInputChange = useDebounce(setQuery, 500);
|
||||||
|
|
||||||
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
||||||
const selected = getSelectedItems(selectedItems);
|
const selected = getSelectedItems(selectedItems);
|
||||||
@@ -403,7 +403,7 @@ export const SceneSelect: React.FC<ITitledSelect> = (props) => {
|
|||||||
value: s.id,
|
value: s.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const onInputChange = useDebouncedSetState(setQuery, 500);
|
const onInputChange = useDebounce(setQuery, 500);
|
||||||
|
|
||||||
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
||||||
const selected = getSelectedItems(selectedItems);
|
const selected = getSelectedItems(selectedItems);
|
||||||
@@ -453,7 +453,7 @@ export const ImageSelect: React.FC<ITitledSelect> = (props) => {
|
|||||||
value: s.id,
|
value: s.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const onInputChange = useDebouncedSetState(setQuery, 500);
|
const onInputChange = useDebounce(setQuery, 500);
|
||||||
|
|
||||||
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
||||||
const selected = getSelectedItems(selectedItems);
|
const selected = getSelectedItems(selectedItems);
|
||||||
|
|||||||
@@ -25,11 +25,7 @@ export const TruncatedText: React.FC<ITruncatedTextProps> = ({
|
|||||||
const [showTooltip, setShowTooltip] = useState(false);
|
const [showTooltip, setShowTooltip] = useState(false);
|
||||||
const target = useRef(null);
|
const target = useRef(null);
|
||||||
|
|
||||||
const startShowingTooltip = useDebounce(
|
const startShowingTooltip = useDebounce(() => setShowTooltip(true), delay);
|
||||||
() => setShowTooltip(true),
|
|
||||||
[],
|
|
||||||
delay
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!text) return <></>;
|
if (!text) return <></>;
|
||||||
|
|
||||||
|
|||||||
@@ -212,7 +212,6 @@ export const LightboxComponent: React.FC<IProps> = ({
|
|||||||
|
|
||||||
const disableInstantTransition = useDebounce(
|
const disableInstantTransition = useDebounce(
|
||||||
() => setInstantTransition(false),
|
() => setInstantTransition(false),
|
||||||
[],
|
|
||||||
400
|
400
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { DebounceSettings } from "lodash-es";
|
import { debounce, DebouncedFunc, DebounceSettings } from "lodash-es";
|
||||||
import debounce, { DebouncedFunc } from "lodash-es/debounce";
|
import { useCallback, useRef } from "react";
|
||||||
import React, { useCallback } from "react";
|
|
||||||
|
|
||||||
export function useDebounce<T extends (...args: any) => any>(
|
export function useDebounce<T extends (...args: any) => any>(
|
||||||
fn: T,
|
fn: T,
|
||||||
deps: React.DependencyList,
|
|
||||||
wait?: number,
|
wait?: number,
|
||||||
options?: DebounceSettings
|
options?: DebounceSettings
|
||||||
): DebouncedFunc<T> {
|
): DebouncedFunc<T> {
|
||||||
return useCallback(debounce(fn, wait, options), [...deps, wait, options]);
|
const func = useRef<T>(fn);
|
||||||
}
|
func.current = fn;
|
||||||
|
return useCallback(
|
||||||
// Convenience hook for use with state setters
|
debounce(
|
||||||
export function useDebouncedSetState<S>(
|
function (this: any) {
|
||||||
fn: React.Dispatch<React.SetStateAction<S>>,
|
return func.current.apply(this, arguments as any);
|
||||||
wait?: number,
|
},
|
||||||
options?: DebounceSettings
|
wait,
|
||||||
): DebouncedFunc<React.Dispatch<React.SetStateAction<S>>> {
|
options
|
||||||
return useDebounce(fn, [], wait, options);
|
),
|
||||||
|
[wait, options?.leading, options?.trailing, options?.maxWait]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
23
ui/v2.5/src/hooks/throttle.ts
Normal file
23
ui/v2.5/src/hooks/throttle.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import { DebouncedFunc, DebounceSettings, throttle } from "lodash-es";
|
||||||
|
import { useCallback, useRef } from "react";
|
||||||
|
|
||||||
|
export function useThrottle<T extends (...args: any) => any>(
|
||||||
|
fn: T,
|
||||||
|
wait?: number,
|
||||||
|
options?: DebounceSettings
|
||||||
|
): DebouncedFunc<T> {
|
||||||
|
const func = useRef<T>(fn);
|
||||||
|
func.current = fn;
|
||||||
|
return useCallback(
|
||||||
|
throttle(
|
||||||
|
function (this: any) {
|
||||||
|
return func.current.apply(this, arguments as any);
|
||||||
|
},
|
||||||
|
wait,
|
||||||
|
options
|
||||||
|
),
|
||||||
|
[wait, options?.leading, options?.trailing, options?.maxWait]
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user