mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
@@ -53,6 +53,10 @@
|
|||||||
"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",
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import debounce from "lodash-es/debounce";
|
|
||||||
import cloneDeep from "lodash-es/cloneDeep";
|
import cloneDeep from "lodash-es/cloneDeep";
|
||||||
import React, {
|
import React, {
|
||||||
HTMLAttributes,
|
HTMLAttributes,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
@@ -40,6 +38,7 @@ import {
|
|||||||
faRandom,
|
faRandom,
|
||||||
faTimes,
|
faTimes,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { useDebounce } from "src/hooks/debounce";
|
||||||
|
|
||||||
const maxPageSize = 1000;
|
const maxPageSize = 1000;
|
||||||
interface IListFilterProps {
|
interface IListFilterProps {
|
||||||
@@ -79,13 +78,15 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
|||||||
[filter, onFilterUpdate]
|
[filter, onFilterUpdate]
|
||||||
);
|
);
|
||||||
|
|
||||||
// useMemo to prevent debounce from being recreated on every render
|
const searchCallback = useDebounce(
|
||||||
const debouncedSearchQueryUpdated = useMemo(
|
(value: string) => {
|
||||||
() =>
|
const newFilter = cloneDeep(filter);
|
||||||
debounce((value: string) => {
|
newFilter.searchTerm = value;
|
||||||
searchQueryUpdated(value);
|
newFilter.currentPage = 1;
|
||||||
}, 500),
|
onFilterUpdate(newFilter);
|
||||||
[searchQueryUpdated]
|
},
|
||||||
|
[filter, onFilterUpdate],
|
||||||
|
500
|
||||||
);
|
);
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -145,7 +146,7 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onChangeQuery(event: React.FormEvent<HTMLInputElement>) {
|
function onChangeQuery(event: React.FormEvent<HTMLInputElement>) {
|
||||||
debouncedSearchQueryUpdated(event.currentTarget.value);
|
searchCallback(event.currentTarget.value);
|
||||||
setQueryClearShowing(!!event.currentTarget.value);
|
setQueryClearShowing(!!event.currentTarget.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import debounce from "lodash-es/debounce";
|
|
||||||
import { Button, Form } from "react-bootstrap";
|
import { Button, Form } from "react-bootstrap";
|
||||||
import { useIntl } from "react-intl";
|
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 { 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";
|
||||||
|
|
||||||
const CLASSNAME = "PerformerScrapeModal";
|
const CLASSNAME = "PerformerScrapeModal";
|
||||||
const CLASSNAME_LIST = `${CLASSNAME}-list`;
|
const CLASSNAME_LIST = `${CLASSNAME}-list`;
|
||||||
@@ -33,9 +33,7 @@ const PerformerScrapeModal: React.FC<IProps> = ({
|
|||||||
|
|
||||||
const performers = data?.scrapeSinglePerformer ?? [];
|
const performers = data?.scrapeSinglePerformer ?? [];
|
||||||
|
|
||||||
const onInputChange = debounce((input: string) => {
|
const onInputChange = useDebouncedSetState(setQuery, 500);
|
||||||
setQuery(input);
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
useEffect(() => inputRef.current?.focus(), []);
|
useEffect(() => inputRef.current?.focus(), []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import debounce from "lodash-es/debounce";
|
|
||||||
import { Button, Form } from "react-bootstrap";
|
import { Button, Form } from "react-bootstrap";
|
||||||
import { useIntl } from "react-intl";
|
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 { 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";
|
||||||
|
|
||||||
const CLASSNAME = "PerformerScrapeModal";
|
const CLASSNAME = "PerformerScrapeModal";
|
||||||
const CLASSNAME_LIST = `${CLASSNAME}-list`;
|
const CLASSNAME_LIST = `${CLASSNAME}-list`;
|
||||||
@@ -44,9 +44,7 @@ const PerformerStashBoxModal: React.FC<IProps> = ({
|
|||||||
|
|
||||||
const performers = data?.scrapeSinglePerformer ?? [];
|
const performers = data?.scrapeSinglePerformer ?? [];
|
||||||
|
|
||||||
const onInputChange = debounce((input: string) => {
|
const onInputChange = useDebouncedSetState(setQuery, 500);
|
||||||
setQuery(input);
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
useEffect(() => inputRef.current?.focus(), []);
|
useEffect(() => inputRef.current?.focus(), []);
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,7 @@ import {
|
|||||||
faCheckCircle,
|
faCheckCircle,
|
||||||
faTimesCircle,
|
faTimesCircle,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import debounce from "lodash-es/debounce";
|
import React, { useState, useEffect, useCallback, useRef } from "react";
|
||||||
import React, {
|
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useCallback,
|
|
||||||
useRef,
|
|
||||||
} from "react";
|
|
||||||
import { Spinner } from "react-bootstrap";
|
import { Spinner } from "react-bootstrap";
|
||||||
import { IUIConfig } from "src/core/config";
|
import { IUIConfig } from "src/core/config";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
@@ -23,6 +16,7 @@ import {
|
|||||||
useConfigureScraping,
|
useConfigureScraping,
|
||||||
useConfigureUI,
|
useConfigureUI,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
|
import { useDebounce } from "src/hooks/debounce";
|
||||||
import { useToast } from "src/hooks/Toast";
|
import { useToast } from "src/hooks/Toast";
|
||||||
import { withoutTypename } from "src/utils/data";
|
import { withoutTypename } from "src/utils/data";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
@@ -76,40 +70,34 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||||||
const initialRef = useRef(false);
|
const initialRef = useRef(false);
|
||||||
|
|
||||||
const [general, setGeneral] = useState<GQL.ConfigGeneralInput>({});
|
const [general, setGeneral] = useState<GQL.ConfigGeneralInput>({});
|
||||||
const [pendingGeneral, setPendingGeneral] = useState<
|
const [pendingGeneral, setPendingGeneral] =
|
||||||
GQL.ConfigGeneralInput | undefined
|
useState<GQL.ConfigGeneralInput>();
|
||||||
>();
|
|
||||||
const [updateGeneralConfig] = useConfigureGeneral();
|
const [updateGeneralConfig] = useConfigureGeneral();
|
||||||
|
|
||||||
const [iface, setIface] = useState<GQL.ConfigInterfaceInput>({});
|
const [iface, setIface] = useState<GQL.ConfigInterfaceInput>({});
|
||||||
const [pendingInterface, setPendingInterface] = useState<
|
const [pendingInterface, setPendingInterface] =
|
||||||
GQL.ConfigInterfaceInput | undefined
|
useState<GQL.ConfigInterfaceInput>();
|
||||||
>();
|
|
||||||
const [updateInterfaceConfig] = useConfigureInterface();
|
const [updateInterfaceConfig] = useConfigureInterface();
|
||||||
|
|
||||||
const [defaults, setDefaults] = useState<GQL.ConfigDefaultSettingsInput>({});
|
const [defaults, setDefaults] = useState<GQL.ConfigDefaultSettingsInput>({});
|
||||||
const [pendingDefaults, setPendingDefaults] = useState<
|
const [pendingDefaults, setPendingDefaults] =
|
||||||
GQL.ConfigDefaultSettingsInput | undefined
|
useState<GQL.ConfigDefaultSettingsInput>();
|
||||||
>();
|
|
||||||
const [updateDefaultsConfig] = useConfigureDefaults();
|
const [updateDefaultsConfig] = useConfigureDefaults();
|
||||||
|
|
||||||
const [scraping, setScraping] = useState<GQL.ConfigScrapingInput>({});
|
const [scraping, setScraping] = useState<GQL.ConfigScrapingInput>({});
|
||||||
const [pendingScraping, setPendingScraping] = useState<
|
const [pendingScraping, setPendingScraping] =
|
||||||
GQL.ConfigScrapingInput | undefined
|
useState<GQL.ConfigScrapingInput>();
|
||||||
>();
|
|
||||||
const [updateScrapingConfig] = useConfigureScraping();
|
const [updateScrapingConfig] = useConfigureScraping();
|
||||||
|
|
||||||
const [dlna, setDLNA] = useState<GQL.ConfigDlnaInput>({});
|
const [dlna, setDLNA] = useState<GQL.ConfigDlnaInput>({});
|
||||||
const [pendingDLNA, setPendingDLNA] = useState<
|
const [pendingDLNA, setPendingDLNA] = useState<GQL.ConfigDlnaInput>();
|
||||||
GQL.ConfigDlnaInput | undefined
|
|
||||||
>();
|
|
||||||
const [updateDLNAConfig] = useConfigureDLNA();
|
const [updateDLNAConfig] = useConfigureDLNA();
|
||||||
|
|
||||||
const [ui, setUI] = useState({});
|
const [ui, setUI] = useState({});
|
||||||
const [pendingUI, setPendingUI] = useState<{} | undefined>();
|
const [pendingUI, setPendingUI] = useState<{}>();
|
||||||
const [updateUIConfig] = useConfigureUI();
|
const [updateUIConfig] = useConfigureUI();
|
||||||
|
|
||||||
const [updateSuccess, setUpdateSuccess] = useState<boolean | undefined>();
|
const [updateSuccess, setUpdateSuccess] = useState<boolean>();
|
||||||
|
|
||||||
const [apiKey, setApiKey] = useState("");
|
const [apiKey, setApiKey] = useState("");
|
||||||
|
|
||||||
@@ -146,13 +134,7 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||||||
setUI(data.configuration.ui);
|
setUI(data.configuration.ui);
|
||||||
}, [data, error]);
|
}, [data, error]);
|
||||||
|
|
||||||
const resetSuccess = useMemo(
|
const resetSuccess = useDebounce(() => setUpdateSuccess(undefined), [], 4000);
|
||||||
() =>
|
|
||||||
debounce(() => {
|
|
||||||
setUpdateSuccess(undefined);
|
|
||||||
}, 4000),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onSuccess = useCallback(() => {
|
const onSuccess = useCallback(() => {
|
||||||
setUpdateSuccess(true);
|
setUpdateSuccess(true);
|
||||||
@@ -160,24 +142,24 @@ export const SettingsContext: React.FC = ({ children }) => {
|
|||||||
}, [resetSuccess]);
|
}, [resetSuccess]);
|
||||||
|
|
||||||
// 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 saveGeneralConfig = useMemo(
|
const saveGeneralConfig = useDebounce(
|
||||||
() =>
|
async (input: GQL.ConfigGeneralInput) => {
|
||||||
debounce(async (input: GQL.ConfigGeneralInput) => {
|
try {
|
||||||
try {
|
setUpdateSuccess(undefined);
|
||||||
setUpdateSuccess(undefined);
|
await updateGeneralConfig({
|
||||||
await updateGeneralConfig({
|
variables: {
|
||||||
variables: {
|
input,
|
||||||
input,
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
setPendingGeneral(undefined);
|
setPendingGeneral(undefined);
|
||||||
onSuccess();
|
onSuccess();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setSaveError(e);
|
setSaveError(e);
|
||||||
}
|
}
|
||||||
}, 500),
|
},
|
||||||
[updateGeneralConfig, onSuccess]
|
[updateGeneralConfig, onSuccess],
|
||||||
|
500
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -210,24 +192,24 @@ 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 saveInterfaceConfig = useMemo(
|
const saveInterfaceConfig = useDebounce(
|
||||||
() =>
|
async (input: GQL.ConfigInterfaceInput) => {
|
||||||
debounce(async (input: GQL.ConfigInterfaceInput) => {
|
try {
|
||||||
try {
|
setUpdateSuccess(undefined);
|
||||||
setUpdateSuccess(undefined);
|
await updateInterfaceConfig({
|
||||||
await updateInterfaceConfig({
|
variables: {
|
||||||
variables: {
|
input,
|
||||||
input,
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
setPendingInterface(undefined);
|
setPendingInterface(undefined);
|
||||||
onSuccess();
|
onSuccess();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setSaveError(e);
|
setSaveError(e);
|
||||||
}
|
}
|
||||||
}, 500),
|
},
|
||||||
[updateInterfaceConfig, onSuccess]
|
[updateInterfaceConfig, onSuccess],
|
||||||
|
500
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -260,24 +242,24 @@ 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 saveDefaultsConfig = useMemo(
|
const saveDefaultsConfig = useDebounce(
|
||||||
() =>
|
async (input: GQL.ConfigDefaultSettingsInput) => {
|
||||||
debounce(async (input: GQL.ConfigDefaultSettingsInput) => {
|
try {
|
||||||
try {
|
setUpdateSuccess(undefined);
|
||||||
setUpdateSuccess(undefined);
|
await updateDefaultsConfig({
|
||||||
await updateDefaultsConfig({
|
variables: {
|
||||||
variables: {
|
input,
|
||||||
input,
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
setPendingDefaults(undefined);
|
setPendingDefaults(undefined);
|
||||||
onSuccess();
|
onSuccess();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setSaveError(e);
|
setSaveError(e);
|
||||||
}
|
}
|
||||||
}, 500),
|
},
|
||||||
[updateDefaultsConfig, onSuccess]
|
[updateDefaultsConfig, onSuccess],
|
||||||
|
500
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -310,24 +292,24 @@ 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 saveScrapingConfig = useMemo(
|
const saveScrapingConfig = useDebounce(
|
||||||
() =>
|
async (input: GQL.ConfigScrapingInput) => {
|
||||||
debounce(async (input: GQL.ConfigScrapingInput) => {
|
try {
|
||||||
try {
|
setUpdateSuccess(undefined);
|
||||||
setUpdateSuccess(undefined);
|
await updateScrapingConfig({
|
||||||
await updateScrapingConfig({
|
variables: {
|
||||||
variables: {
|
input,
|
||||||
input,
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
setPendingScraping(undefined);
|
setPendingScraping(undefined);
|
||||||
onSuccess();
|
onSuccess();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setSaveError(e);
|
setSaveError(e);
|
||||||
}
|
}
|
||||||
}, 500),
|
},
|
||||||
[updateScrapingConfig, onSuccess]
|
[updateScrapingConfig, onSuccess],
|
||||||
|
500
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -360,24 +342,24 @@ 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 = useMemo(
|
const saveDLNAConfig = useDebounce(
|
||||||
() =>
|
async (input: GQL.ConfigDlnaInput) => {
|
||||||
debounce(async (input: GQL.ConfigDlnaInput) => {
|
try {
|
||||||
try {
|
setUpdateSuccess(undefined);
|
||||||
setUpdateSuccess(undefined);
|
await updateDLNAConfig({
|
||||||
await updateDLNAConfig({
|
variables: {
|
||||||
variables: {
|
input,
|
||||||
input,
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
setPendingDLNA(undefined);
|
setPendingDLNA(undefined);
|
||||||
onSuccess();
|
onSuccess();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setSaveError(e);
|
setSaveError(e);
|
||||||
}
|
}
|
||||||
}, 500),
|
},
|
||||||
[updateDLNAConfig, onSuccess]
|
[updateDLNAConfig, onSuccess],
|
||||||
|
500
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -410,24 +392,24 @@ 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 = useMemo(
|
const saveUIConfig = useDebounce(
|
||||||
() =>
|
async (input: IUIConfig) => {
|
||||||
debounce(async (input: IUIConfig) => {
|
try {
|
||||||
try {
|
setUpdateSuccess(undefined);
|
||||||
setUpdateSuccess(undefined);
|
await updateUIConfig({
|
||||||
await updateUIConfig({
|
variables: {
|
||||||
variables: {
|
input,
|
||||||
input,
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
setPendingUI(undefined);
|
setPendingUI(undefined);
|
||||||
onSuccess();
|
onSuccess();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setSaveError(e);
|
setSaveError(e);
|
||||||
}
|
}
|
||||||
}, 500),
|
},
|
||||||
[updateUIConfig, onSuccess]
|
[updateUIConfig, onSuccess],
|
||||||
|
500
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
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 { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { Button, InputGroup, Form } from "react-bootstrap";
|
import { Button, InputGroup, Form } from "react-bootstrap";
|
||||||
import debounce from "lodash-es/debounce";
|
|
||||||
import { Icon } from "../Icon";
|
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 { faTimes } from "@fortawesome/free-solid-svg-icons";
|
import { faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { useDebouncedSetState } from "src/hooks/debounce";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
currentDirectory: string;
|
currentDirectory: string;
|
||||||
@@ -20,22 +20,15 @@ export const FolderSelect: React.FC<IProps> = ({
|
|||||||
defaultDirectories,
|
defaultDirectories,
|
||||||
appendButton,
|
appendButton,
|
||||||
}) => {
|
}) => {
|
||||||
const [debouncedDirectory, setDebouncedDirectory] =
|
const [directory, setDirectory] = useState(currentDirectory);
|
||||||
useState(currentDirectory);
|
const { data, error, loading } = useDirectory(directory);
|
||||||
const { data, error, loading } = useDirectory(debouncedDirectory);
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const selectableDirectories: string[] = currentDirectory
|
const selectableDirectories: string[] = currentDirectory
|
||||||
? data?.directory.directories ?? defaultDirectories ?? []
|
? data?.directory.directories ?? defaultDirectories ?? []
|
||||||
: defaultDirectories ?? [];
|
: defaultDirectories ?? [];
|
||||||
|
|
||||||
const debouncedSetDirectory = useMemo(
|
const debouncedSetDirectory = useDebouncedSetState(setDirectory, 250);
|
||||||
() =>
|
|
||||||
debounce((input: string) => {
|
|
||||||
setDebouncedDirectory(input);
|
|
||||||
}, 250),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentDirectory === "" && !defaultDirectories && data?.directory.path)
|
if (currentDirectory === "" && !defaultDirectories && data?.directory.path)
|
||||||
@@ -44,7 +37,7 @@ export const FolderSelect: React.FC<IProps> = ({
|
|||||||
|
|
||||||
function setInstant(value: string) {
|
function setInstant(value: string) {
|
||||||
setCurrentDirectory(value);
|
setCurrentDirectory(value);
|
||||||
setDebouncedDirectory(value);
|
setDirectory(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDebounced(value: string) {
|
function setDebounced(value: string) {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import Select, {
|
|||||||
OptionsOrGroups,
|
OptionsOrGroups,
|
||||||
} from "react-select";
|
} from "react-select";
|
||||||
import CreatableSelect from "react-select/creatable";
|
import CreatableSelect from "react-select/creatable";
|
||||||
import debounce from "lodash-es/debounce";
|
|
||||||
|
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import {
|
import {
|
||||||
@@ -31,6 +30,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";
|
||||||
|
|
||||||
export type SelectObject = {
|
export type SelectObject = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -354,9 +354,7 @@ export const GallerySelect: React.FC<ITitledSelect> = (props) => {
|
|||||||
value: g.id,
|
value: g.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const onInputChange = debounce((input: string) => {
|
const onInputChange = useDebouncedSetState(setQuery, 500);
|
||||||
setQuery(input);
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
||||||
const selected = getSelectedItems(selectedItems);
|
const selected = getSelectedItems(selectedItems);
|
||||||
@@ -407,9 +405,7 @@ export const SceneSelect: React.FC<ITitledSelect> = (props) => {
|
|||||||
value: s.id,
|
value: s.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const onInputChange = debounce((input: string) => {
|
const onInputChange = useDebouncedSetState(setQuery, 500);
|
||||||
setQuery(input);
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
||||||
const selected = getSelectedItems(selectedItems);
|
const selected = getSelectedItems(selectedItems);
|
||||||
@@ -459,9 +455,7 @@ export const ImageSelect: React.FC<ITitledSelect> = (props) => {
|
|||||||
value: s.id,
|
value: s.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const onInputChange = debounce((input: string) => {
|
const onInputChange = useDebouncedSetState(setQuery, 500);
|
||||||
setQuery(input);
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
||||||
const selected = getSelectedItems(selectedItems);
|
const selected = getSelectedItems(selectedItems);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useRef, useState } from "react";
|
import React, { useRef, useState } from "react";
|
||||||
import { Overlay, Tooltip } from "react-bootstrap";
|
import { Overlay, Tooltip } from "react-bootstrap";
|
||||||
import { Placement } from "react-bootstrap/Overlay";
|
import { Placement } from "react-bootstrap/Overlay";
|
||||||
import debounce from "lodash-es/debounce";
|
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
|
import { useDebounce } from "src/hooks/debounce";
|
||||||
|
|
||||||
const CLASSNAME = "TruncatedText";
|
const CLASSNAME = "TruncatedText";
|
||||||
const CLASSNAME_TOOLTIP = `${CLASSNAME}-tooltip`;
|
const CLASSNAME_TOOLTIP = `${CLASSNAME}-tooltip`;
|
||||||
@@ -25,9 +25,13 @@ export const TruncatedText: React.FC<ITruncatedTextProps> = ({
|
|||||||
const [showTooltip, setShowTooltip] = useState(false);
|
const [showTooltip, setShowTooltip] = useState(false);
|
||||||
const target = useRef(null);
|
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) => {
|
const handleFocus = (element: HTMLElement) => {
|
||||||
// Check if visible size is smaller than the content size
|
// Check if visible size is smaller than the content size
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import noop from "lodash-es/noop";
|
|
||||||
|
|
||||||
const MIN_VALID_INTERVAL = 1000;
|
const MIN_VALID_INTERVAL = 1000;
|
||||||
|
|
||||||
|
function noop() {}
|
||||||
|
|
||||||
const useInterval = (
|
const useInterval = (
|
||||||
callback: () => void,
|
callback: () => void,
|
||||||
delay: number | null = 5000
|
delay: number | null = 5000
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
} from "react-bootstrap";
|
} from "react-bootstrap";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
import debounce from "lodash-es/debounce";
|
|
||||||
|
|
||||||
import { Icon } from "src/components/Shared/Icon";
|
import { Icon } from "src/components/Shared/Icon";
|
||||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||||
@@ -45,6 +44,7 @@ import {
|
|||||||
faTimes,
|
faTimes,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
||||||
|
import { useDebounce } from "../debounce";
|
||||||
|
|
||||||
const CLASSNAME = "Lightbox";
|
const CLASSNAME = "Lightbox";
|
||||||
const CLASSNAME_HEADER = `${CLASSNAME}-header`;
|
const CLASSNAME_HEADER = `${CLASSNAME}-header`;
|
||||||
@@ -197,8 +197,9 @@ export const LightboxComponent: React.FC<IProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isSwitchingPage, images, index]);
|
}, [isSwitchingPage, images, index]);
|
||||||
|
|
||||||
const disableInstantTransition = debounce(
|
const disableInstantTransition = useDebounce(
|
||||||
() => setInstantTransition(false),
|
() => setInstantTransition(false),
|
||||||
|
[],
|
||||||
400
|
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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user