mirror of
https://github.com/stashapp/stash.git
synced 2025-12-15 21:03:22 +01:00
parent
798b3e6dd7
commit
bc3730d49f
11 changed files with 180 additions and 181 deletions
|
|
@ -53,6 +53,10 @@
|
|||
"import/namespace": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"react/display-name": "off",
|
||||
"react-hooks/exhaustive-deps": [
|
||||
"error",
|
||||
{ "additionalHooks": "^(useDebounce)$" }
|
||||
],
|
||||
"react/prop-types": "off",
|
||||
"react/style-prop-object": [
|
||||
"error",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import debounce from "lodash-es/debounce";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import React, {
|
||||
HTMLAttributes,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
|
|
@ -40,6 +38,7 @@ import {
|
|||
faRandom,
|
||||
faTimes,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { useDebounce } from "src/hooks/debounce";
|
||||
|
||||
const maxPageSize = 1000;
|
||||
interface IListFilterProps {
|
||||
|
|
@ -79,13 +78,15 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
|||
[filter, onFilterUpdate]
|
||||
);
|
||||
|
||||
// useMemo to prevent debounce from being recreated on every render
|
||||
const debouncedSearchQueryUpdated = useMemo(
|
||||
() =>
|
||||
debounce((value: string) => {
|
||||
searchQueryUpdated(value);
|
||||
}, 500),
|
||||
[searchQueryUpdated]
|
||||
const searchCallback = useDebounce(
|
||||
(value: string) => {
|
||||
const newFilter = cloneDeep(filter);
|
||||
newFilter.searchTerm = value;
|
||||
newFilter.currentPage = 1;
|
||||
onFilterUpdate(newFilter);
|
||||
},
|
||||
[filter, onFilterUpdate],
|
||||
500
|
||||
);
|
||||
|
||||
const intl = useIntl();
|
||||
|
|
@ -145,7 +146,7 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
|||
}
|
||||
|
||||
function onChangeQuery(event: React.FormEvent<HTMLInputElement>) {
|
||||
debouncedSearchQueryUpdated(event.currentTarget.value);
|
||||
searchCallback(event.currentTarget.value);
|
||||
setQueryClearShowing(!!event.currentTarget.value);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import debounce from "lodash-es/debounce";
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
|
|
@ -7,6 +6,7 @@ import * as GQL from "src/core/generated-graphql";
|
|||
import { ModalComponent } from "src/components/Shared/Modal";
|
||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||
import { useScrapePerformerList } from "src/core/StashService";
|
||||
import { useDebouncedSetState } from "src/hooks/debounce";
|
||||
|
||||
const CLASSNAME = "PerformerScrapeModal";
|
||||
const CLASSNAME_LIST = `${CLASSNAME}-list`;
|
||||
|
|
@ -33,9 +33,7 @@ const PerformerScrapeModal: React.FC<IProps> = ({
|
|||
|
||||
const performers = data?.scrapeSinglePerformer ?? [];
|
||||
|
||||
const onInputChange = debounce((input: string) => {
|
||||
setQuery(input);
|
||||
}, 500);
|
||||
const onInputChange = useDebouncedSetState(setQuery, 500);
|
||||
|
||||
useEffect(() => inputRef.current?.focus(), []);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import debounce from "lodash-es/debounce";
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
|
|
@ -7,6 +6,7 @@ import * as GQL from "src/core/generated-graphql";
|
|||
import { ModalComponent } from "src/components/Shared/Modal";
|
||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||
import { stashboxDisplayName } from "src/utils/stashbox";
|
||||
import { useDebouncedSetState } from "src/hooks/debounce";
|
||||
|
||||
const CLASSNAME = "PerformerScrapeModal";
|
||||
const CLASSNAME_LIST = `${CLASSNAME}-list`;
|
||||
|
|
@ -44,9 +44,7 @@ const PerformerStashBoxModal: React.FC<IProps> = ({
|
|||
|
||||
const performers = data?.scrapeSinglePerformer ?? [];
|
||||
|
||||
const onInputChange = debounce((input: string) => {
|
||||
setQuery(input);
|
||||
}, 500);
|
||||
const onInputChange = useDebouncedSetState(setQuery, 500);
|
||||
|
||||
useEffect(() => inputRef.current?.focus(), []);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,7 @@ import {
|
|||
faCheckCircle,
|
||||
faTimesCircle,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import debounce from "lodash-es/debounce";
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useCallback,
|
||||
useRef,
|
||||
} from "react";
|
||||
import React, { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { Spinner } from "react-bootstrap";
|
||||
import { IUIConfig } from "src/core/config";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
|
|
@ -23,6 +16,7 @@ import {
|
|||
useConfigureScraping,
|
||||
useConfigureUI,
|
||||
} from "src/core/StashService";
|
||||
import { useDebounce } from "src/hooks/debounce";
|
||||
import { useToast } from "src/hooks/Toast";
|
||||
import { withoutTypename } from "src/utils/data";
|
||||
import { Icon } from "../Shared/Icon";
|
||||
|
|
@ -76,40 +70,34 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||
const initialRef = useRef(false);
|
||||
|
||||
const [general, setGeneral] = useState<GQL.ConfigGeneralInput>({});
|
||||
const [pendingGeneral, setPendingGeneral] = useState<
|
||||
GQL.ConfigGeneralInput | undefined
|
||||
>();
|
||||
const [pendingGeneral, setPendingGeneral] =
|
||||
useState<GQL.ConfigGeneralInput>();
|
||||
const [updateGeneralConfig] = useConfigureGeneral();
|
||||
|
||||
const [iface, setIface] = useState<GQL.ConfigInterfaceInput>({});
|
||||
const [pendingInterface, setPendingInterface] = useState<
|
||||
GQL.ConfigInterfaceInput | undefined
|
||||
>();
|
||||
const [pendingInterface, setPendingInterface] =
|
||||
useState<GQL.ConfigInterfaceInput>();
|
||||
const [updateInterfaceConfig] = useConfigureInterface();
|
||||
|
||||
const [defaults, setDefaults] = useState<GQL.ConfigDefaultSettingsInput>({});
|
||||
const [pendingDefaults, setPendingDefaults] = useState<
|
||||
GQL.ConfigDefaultSettingsInput | undefined
|
||||
>();
|
||||
const [pendingDefaults, setPendingDefaults] =
|
||||
useState<GQL.ConfigDefaultSettingsInput>();
|
||||
const [updateDefaultsConfig] = useConfigureDefaults();
|
||||
|
||||
const [scraping, setScraping] = useState<GQL.ConfigScrapingInput>({});
|
||||
const [pendingScraping, setPendingScraping] = useState<
|
||||
GQL.ConfigScrapingInput | undefined
|
||||
>();
|
||||
const [pendingScraping, setPendingScraping] =
|
||||
useState<GQL.ConfigScrapingInput>();
|
||||
const [updateScrapingConfig] = useConfigureScraping();
|
||||
|
||||
const [dlna, setDLNA] = useState<GQL.ConfigDlnaInput>({});
|
||||
const [pendingDLNA, setPendingDLNA] = useState<
|
||||
GQL.ConfigDlnaInput | undefined
|
||||
>();
|
||||
const [pendingDLNA, setPendingDLNA] = useState<GQL.ConfigDlnaInput>();
|
||||
const [updateDLNAConfig] = useConfigureDLNA();
|
||||
|
||||
const [ui, setUI] = useState({});
|
||||
const [pendingUI, setPendingUI] = useState<{} | undefined>();
|
||||
const [pendingUI, setPendingUI] = useState<{}>();
|
||||
const [updateUIConfig] = useConfigureUI();
|
||||
|
||||
const [updateSuccess, setUpdateSuccess] = useState<boolean | undefined>();
|
||||
const [updateSuccess, setUpdateSuccess] = useState<boolean>();
|
||||
|
||||
const [apiKey, setApiKey] = useState("");
|
||||
|
||||
|
|
@ -146,13 +134,7 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||
setUI(data.configuration.ui);
|
||||
}, [data, error]);
|
||||
|
||||
const resetSuccess = useMemo(
|
||||
() =>
|
||||
debounce(() => {
|
||||
setUpdateSuccess(undefined);
|
||||
}, 4000),
|
||||
[]
|
||||
);
|
||||
const resetSuccess = useDebounce(() => setUpdateSuccess(undefined), [], 4000);
|
||||
|
||||
const onSuccess = useCallback(() => {
|
||||
setUpdateSuccess(true);
|
||||
|
|
@ -160,24 +142,24 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||
}, [resetSuccess]);
|
||||
|
||||
// saves the configuration if no further changes are made after a half second
|
||||
const saveGeneralConfig = useMemo(
|
||||
() =>
|
||||
debounce(async (input: GQL.ConfigGeneralInput) => {
|
||||
try {
|
||||
setUpdateSuccess(undefined);
|
||||
await updateGeneralConfig({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
const saveGeneralConfig = useDebounce(
|
||||
async (input: GQL.ConfigGeneralInput) => {
|
||||
try {
|
||||
setUpdateSuccess(undefined);
|
||||
await updateGeneralConfig({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
|
||||
setPendingGeneral(undefined);
|
||||
onSuccess();
|
||||
} catch (e) {
|
||||
setSaveError(e);
|
||||
}
|
||||
}, 500),
|
||||
[updateGeneralConfig, onSuccess]
|
||||
setPendingGeneral(undefined);
|
||||
onSuccess();
|
||||
} catch (e) {
|
||||
setSaveError(e);
|
||||
}
|
||||
},
|
||||
[updateGeneralConfig, onSuccess],
|
||||
500
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -210,24 +192,24 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||
}
|
||||
|
||||
// saves the configuration if no further changes are made after a half second
|
||||
const saveInterfaceConfig = useMemo(
|
||||
() =>
|
||||
debounce(async (input: GQL.ConfigInterfaceInput) => {
|
||||
try {
|
||||
setUpdateSuccess(undefined);
|
||||
await updateInterfaceConfig({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
const saveInterfaceConfig = useDebounce(
|
||||
async (input: GQL.ConfigInterfaceInput) => {
|
||||
try {
|
||||
setUpdateSuccess(undefined);
|
||||
await updateInterfaceConfig({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
|
||||
setPendingInterface(undefined);
|
||||
onSuccess();
|
||||
} catch (e) {
|
||||
setSaveError(e);
|
||||
}
|
||||
}, 500),
|
||||
[updateInterfaceConfig, onSuccess]
|
||||
setPendingInterface(undefined);
|
||||
onSuccess();
|
||||
} catch (e) {
|
||||
setSaveError(e);
|
||||
}
|
||||
},
|
||||
[updateInterfaceConfig, onSuccess],
|
||||
500
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -260,24 +242,24 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||
}
|
||||
|
||||
// saves the configuration if no further changes are made after a half second
|
||||
const saveDefaultsConfig = useMemo(
|
||||
() =>
|
||||
debounce(async (input: GQL.ConfigDefaultSettingsInput) => {
|
||||
try {
|
||||
setUpdateSuccess(undefined);
|
||||
await updateDefaultsConfig({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
const saveDefaultsConfig = useDebounce(
|
||||
async (input: GQL.ConfigDefaultSettingsInput) => {
|
||||
try {
|
||||
setUpdateSuccess(undefined);
|
||||
await updateDefaultsConfig({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
|
||||
setPendingDefaults(undefined);
|
||||
onSuccess();
|
||||
} catch (e) {
|
||||
setSaveError(e);
|
||||
}
|
||||
}, 500),
|
||||
[updateDefaultsConfig, onSuccess]
|
||||
setPendingDefaults(undefined);
|
||||
onSuccess();
|
||||
} catch (e) {
|
||||
setSaveError(e);
|
||||
}
|
||||
},
|
||||
[updateDefaultsConfig, onSuccess],
|
||||
500
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -310,24 +292,24 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||
}
|
||||
|
||||
// saves the configuration if no further changes are made after a half second
|
||||
const saveScrapingConfig = useMemo(
|
||||
() =>
|
||||
debounce(async (input: GQL.ConfigScrapingInput) => {
|
||||
try {
|
||||
setUpdateSuccess(undefined);
|
||||
await updateScrapingConfig({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
const saveScrapingConfig = useDebounce(
|
||||
async (input: GQL.ConfigScrapingInput) => {
|
||||
try {
|
||||
setUpdateSuccess(undefined);
|
||||
await updateScrapingConfig({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
|
||||
setPendingScraping(undefined);
|
||||
onSuccess();
|
||||
} catch (e) {
|
||||
setSaveError(e);
|
||||
}
|
||||
}, 500),
|
||||
[updateScrapingConfig, onSuccess]
|
||||
setPendingScraping(undefined);
|
||||
onSuccess();
|
||||
} catch (e) {
|
||||
setSaveError(e);
|
||||
}
|
||||
},
|
||||
[updateScrapingConfig, onSuccess],
|
||||
500
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -360,24 +342,24 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||
}
|
||||
|
||||
// saves the configuration if no further changes are made after a half second
|
||||
const saveDLNAConfig = useMemo(
|
||||
() =>
|
||||
debounce(async (input: GQL.ConfigDlnaInput) => {
|
||||
try {
|
||||
setUpdateSuccess(undefined);
|
||||
await updateDLNAConfig({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
const saveDLNAConfig = useDebounce(
|
||||
async (input: GQL.ConfigDlnaInput) => {
|
||||
try {
|
||||
setUpdateSuccess(undefined);
|
||||
await updateDLNAConfig({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
|
||||
setPendingDLNA(undefined);
|
||||
onSuccess();
|
||||
} catch (e) {
|
||||
setSaveError(e);
|
||||
}
|
||||
}, 500),
|
||||
[updateDLNAConfig, onSuccess]
|
||||
setPendingDLNA(undefined);
|
||||
onSuccess();
|
||||
} catch (e) {
|
||||
setSaveError(e);
|
||||
}
|
||||
},
|
||||
[updateDLNAConfig, onSuccess],
|
||||
500
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -410,24 +392,24 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||
}
|
||||
|
||||
// saves the configuration if no further changes are made after a half second
|
||||
const saveUIConfig = useMemo(
|
||||
() =>
|
||||
debounce(async (input: IUIConfig) => {
|
||||
try {
|
||||
setUpdateSuccess(undefined);
|
||||
await updateUIConfig({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
const saveUIConfig = useDebounce(
|
||||
async (input: IUIConfig) => {
|
||||
try {
|
||||
setUpdateSuccess(undefined);
|
||||
await updateUIConfig({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
|
||||
setPendingUI(undefined);
|
||||
onSuccess();
|
||||
} catch (e) {
|
||||
setSaveError(e);
|
||||
}
|
||||
}, 500),
|
||||
[updateUIConfig, onSuccess]
|
||||
setPendingUI(undefined);
|
||||
onSuccess();
|
||||
} catch (e) {
|
||||
setSaveError(e);
|
||||
}
|
||||
},
|
||||
[updateUIConfig, onSuccess],
|
||||
500
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import React, { useEffect, useState, useMemo } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { Button, InputGroup, Form } from "react-bootstrap";
|
||||
import debounce from "lodash-es/debounce";
|
||||
import { Icon } from "../Icon";
|
||||
import { LoadingIndicator } from "../LoadingIndicator";
|
||||
import { useDirectory } from "src/core/StashService";
|
||||
import { faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useDebouncedSetState } from "src/hooks/debounce";
|
||||
|
||||
interface IProps {
|
||||
currentDirectory: string;
|
||||
|
|
@ -20,22 +20,15 @@ export const FolderSelect: React.FC<IProps> = ({
|
|||
defaultDirectories,
|
||||
appendButton,
|
||||
}) => {
|
||||
const [debouncedDirectory, setDebouncedDirectory] =
|
||||
useState(currentDirectory);
|
||||
const { data, error, loading } = useDirectory(debouncedDirectory);
|
||||
const [directory, setDirectory] = useState(currentDirectory);
|
||||
const { data, error, loading } = useDirectory(directory);
|
||||
const intl = useIntl();
|
||||
|
||||
const selectableDirectories: string[] = currentDirectory
|
||||
? data?.directory.directories ?? defaultDirectories ?? []
|
||||
: defaultDirectories ?? [];
|
||||
|
||||
const debouncedSetDirectory = useMemo(
|
||||
() =>
|
||||
debounce((input: string) => {
|
||||
setDebouncedDirectory(input);
|
||||
}, 250),
|
||||
[]
|
||||
);
|
||||
const debouncedSetDirectory = useDebouncedSetState(setDirectory, 250);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentDirectory === "" && !defaultDirectories && data?.directory.path)
|
||||
|
|
@ -44,7 +37,7 @@ export const FolderSelect: React.FC<IProps> = ({
|
|||
|
||||
function setInstant(value: string) {
|
||||
setCurrentDirectory(value);
|
||||
setDebouncedDirectory(value);
|
||||
setDirectory(value);
|
||||
}
|
||||
|
||||
function setDebounced(value: string) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import Select, {
|
|||
OptionsOrGroups,
|
||||
} from "react-select";
|
||||
import CreatableSelect from "react-select/creatable";
|
||||
import debounce from "lodash-es/debounce";
|
||||
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import {
|
||||
|
|
@ -31,6 +30,7 @@ import { objectTitle } from "src/core/files";
|
|||
import { galleryTitle } from "src/core/galleries";
|
||||
import { TagPopover } from "../Tags/TagPopover";
|
||||
import { defaultMaxOptionsShown, IUIConfig } from "src/core/config";
|
||||
import { useDebouncedSetState } from "src/hooks/debounce";
|
||||
|
||||
export type SelectObject = {
|
||||
id: string;
|
||||
|
|
@ -354,9 +354,7 @@ export const GallerySelect: React.FC<ITitledSelect> = (props) => {
|
|||
value: g.id,
|
||||
}));
|
||||
|
||||
const onInputChange = debounce((input: string) => {
|
||||
setQuery(input);
|
||||
}, 500);
|
||||
const onInputChange = useDebouncedSetState(setQuery, 500);
|
||||
|
||||
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
||||
const selected = getSelectedItems(selectedItems);
|
||||
|
|
@ -407,9 +405,7 @@ export const SceneSelect: React.FC<ITitledSelect> = (props) => {
|
|||
value: s.id,
|
||||
}));
|
||||
|
||||
const onInputChange = debounce((input: string) => {
|
||||
setQuery(input);
|
||||
}, 500);
|
||||
const onInputChange = useDebouncedSetState(setQuery, 500);
|
||||
|
||||
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
||||
const selected = getSelectedItems(selectedItems);
|
||||
|
|
@ -459,9 +455,7 @@ export const ImageSelect: React.FC<ITitledSelect> = (props) => {
|
|||
value: s.id,
|
||||
}));
|
||||
|
||||
const onInputChange = debounce((input: string) => {
|
||||
setQuery(input);
|
||||
}, 500);
|
||||
const onInputChange = useDebouncedSetState(setQuery, 500);
|
||||
|
||||
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
||||
const selected = getSelectedItems(selectedItems);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useRef, useState } from "react";
|
||||
import { Overlay, Tooltip } from "react-bootstrap";
|
||||
import { Placement } from "react-bootstrap/Overlay";
|
||||
import debounce from "lodash-es/debounce";
|
||||
import cx from "classnames";
|
||||
import { useDebounce } from "src/hooks/debounce";
|
||||
|
||||
const CLASSNAME = "TruncatedText";
|
||||
const CLASSNAME_TOOLTIP = `${CLASSNAME}-tooltip`;
|
||||
|
|
@ -25,9 +25,13 @@ export const TruncatedText: React.FC<ITruncatedTextProps> = ({
|
|||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
const target = useRef(null);
|
||||
|
||||
if (!text) return <></>;
|
||||
const startShowingTooltip = useDebounce(
|
||||
() => setShowTooltip(true),
|
||||
[],
|
||||
delay
|
||||
);
|
||||
|
||||
const startShowingTooltip = debounce(() => setShowTooltip(true), delay);
|
||||
if (!text) return <></>;
|
||||
|
||||
const handleFocus = (element: HTMLElement) => {
|
||||
// Check if visible size is smaller than the content size
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import noop from "lodash-es/noop";
|
||||
|
||||
const MIN_VALID_INTERVAL = 1000;
|
||||
|
||||
function noop() {}
|
||||
|
||||
const useInterval = (
|
||||
callback: () => void,
|
||||
delay: number | null = 5000
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {
|
|||
} from "react-bootstrap";
|
||||
import cx from "classnames";
|
||||
import Mousetrap from "mousetrap";
|
||||
import debounce from "lodash-es/debounce";
|
||||
|
||||
import { Icon } from "src/components/Shared/Icon";
|
||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||
|
|
@ -45,6 +44,7 @@ import {
|
|||
faTimes,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
||||
import { useDebounce } from "../debounce";
|
||||
|
||||
const CLASSNAME = "Lightbox";
|
||||
const CLASSNAME_HEADER = `${CLASSNAME}-header`;
|
||||
|
|
@ -197,8 +197,9 @@ export const LightboxComponent: React.FC<IProps> = ({
|
|||
}
|
||||
}, [isSwitchingPage, images, index]);
|
||||
|
||||
const disableInstantTransition = debounce(
|
||||
const disableInstantTransition = useDebounce(
|
||||
() => setInstantTransition(false),
|
||||
[],
|
||||
400
|
||||
);
|
||||
|
||||
|
|
|
|||
23
ui/v2.5/src/hooks/debounce.ts
Normal file
23
ui/v2.5/src/hooks/debounce.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { DebounceSettings } from "lodash-es";
|
||||
import debounce, { DebouncedFunc } from "lodash-es/debounce";
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
export function useDebounce<T extends (...args: any) => any>(
|
||||
fn: T,
|
||||
deps: React.DependencyList,
|
||||
wait?: number,
|
||||
options?: DebounceSettings
|
||||
): DebouncedFunc<T> {
|
||||
return useCallback(debounce(fn, wait, options), [...deps, wait, options]);
|
||||
}
|
||||
|
||||
// Convenience hook for use with state setters
|
||||
export function useDebouncedSetState<S>(
|
||||
fn: React.Dispatch<React.SetStateAction<S>>,
|
||||
wait?: number,
|
||||
options?: DebounceSettings
|
||||
): DebouncedFunc<React.Dispatch<React.SetStateAction<S>>> {
|
||||
return useDebounce(fn, [], wait, options);
|
||||
}
|
||||
Loading…
Reference in a new issue