Skip to content

Commit

Permalink
Thumbnail scrubber improvements (stashapp#4081)
Browse files Browse the repository at this point in the history
* Remove deps from useDebounce hook
* Add useThrottle hook
* Throttle preview scrubber
* Scrubber improvements
  • Loading branch information
DingDongSoLong4 authored Sep 8, 2023
1 parent 7a92143 commit 50c4ac9
Show file tree
Hide file tree
Showing 14 changed files with 127 additions and 130 deletions.
4 changes: 0 additions & 4 deletions ui/v2.5/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@
"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",
Expand Down
4 changes: 2 additions & 2 deletions ui/v2.5/src/components/List/Filters/SelectableFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
import { defineMessages, MessageDescriptor, useIntl } from "react-intl";
import { CriterionModifier } from "src/core/generated-graphql";
import { keyboardClickHandler } from "src/utils/keyboard";
import { useDebouncedSetState } from "src/hooks/debounce";
import { useDebounce } from "src/hooks/debounce";
import useFocus from "src/utils/focus";

interface ISelectedItem {
Expand Down Expand Up @@ -192,7 +192,7 @@ export const ObjectsFilter = <
const [query, setQuery] = useState("");
const [displayQuery, setDisplayQuery] = useState(query);

const debouncedSetQuery = useDebouncedSetState(setQuery, 250);
const debouncedSetQuery = useDebounce(setQuery, 250);
const onQueryChange = useCallback(
(input: string) => {
setDisplayQuery(input);
Expand Down
16 changes: 6 additions & 10 deletions ui/v2.5/src/components/List/ListFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,12 @@ export const ListFilter: React.FC<IListFilterProps> = ({
[filter, onFilterUpdate]
);

const searchCallback = useDebounce(
(value: string) => {
const newFilter = cloneDeep(filter);
newFilter.searchTerm = value;
newFilter.currentPage = 1;
onFilterUpdate(newFilter);
},
[filter, onFilterUpdate],
500
);
const searchCallback = useDebounce((value: string) => {
const newFilter = cloneDeep(filter);
newFilter.searchTerm = value;
newFilter.currentPage = 1;
onFilterUpdate(newFilter);
}, 500);

const intl = useIntl();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +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";
import { useDebounce } from "src/hooks/debounce";

const CLASSNAME = "PerformerScrapeModal";
const CLASSNAME_LIST = `${CLASSNAME}-list`;
Expand All @@ -33,7 +33,7 @@ const PerformerScrapeModal: React.FC<IProps> = ({

const performers = data?.scrapeSinglePerformer ?? [];

const onInputChange = useDebouncedSetState(setQuery, 500);
const onInputChange = useDebounce(setQuery, 500);

useEffect(() => inputRef.current?.focus(), []);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +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";
import { useDebounce } from "src/hooks/debounce";

import { TruncatedText } from "src/components/Shared/TruncatedText";
import { stringToGender } from "src/utils/gender";
Expand Down Expand Up @@ -171,7 +171,7 @@ const PerformerStashBoxModal: React.FC<IProps> = ({

const performers = data?.scrapeSinglePerformer ?? [];

const onInputChange = useDebouncedSetState(setQuery, 500);
const onInputChange = useDebounce(setQuery, 500);

useEffect(() => inputRef.current?.focus(), []);

Expand Down
71 changes: 37 additions & 34 deletions ui/v2.5/src/components/Scenes/PreviewScrubber.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React, { useMemo } from "react";
import { useDebounce } from "src/hooks/debounce";
import React, { useRef, useMemo, useState, useLayoutEffect } from "react";
import { useSpriteInfo } from "src/hooks/sprite";
import { useThrottle } from "src/hooks/throttle";
import TextUtils from "src/utils/text";

interface IHoverScrubber {
totalSprites: number;
activeIndex: number | undefined;
setActiveIndex: (index: number | undefined) => void;
onClick?: (index: number) => void;
onClick?: () => void;
}

const HoverScrubber: React.FC<IHoverScrubber> = ({
Expand All @@ -20,7 +20,12 @@ const HoverScrubber: React.FC<IHoverScrubber> = ({
const { width } = e.currentTarget.getBoundingClientRect();
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>) {
Expand All @@ -43,11 +48,11 @@ const HoverScrubber: React.FC<IHoverScrubber> = ({
if (relatedTarget !== e.target) return;

e.preventDefault();
onClick(getActiveIndex(e));
onClick();
}

const indicatorStyle = useMemo(() => {
if (activeIndex === undefined) return {};
if (activeIndex === undefined || !totalSprites) return {};

const width = (activeIndex / totalSprites) * 100;

Expand Down Expand Up @@ -97,64 +102,62 @@ export const PreviewScrubber: React.FC<IScenePreviewProps> = ({
vttPath,
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(
setActiveIndex,
[setActiveIndex],
1
);
const debounceSetActiveIndex = useThrottle(setActiveIndex, 50);

const spriteInfo = useSpriteInfo(vttPath);

const style = useMemo(() => {
if (!spriteInfo || activeIndex === undefined || !imageParentRef.current) {
return {};
const sprite = useMemo(() => {
if (!spriteInfo || activeIndex === undefined) {
return undefined;
}
return spriteInfo[activeIndex];
}, [activeIndex, spriteInfo]);

useLayoutEffect(() => {
const imageParent = imageParentRef.current;

const sprite = spriteInfo[activeIndex];
if (!sprite || !imageParent) {
return setStyle({});
}

const clientRect = imageParentRef.current?.getBoundingClientRect();
const scale = clientRect ? scaleToFit(sprite, clientRect) : 1;
const clientRect = imageParent.getBoundingClientRect();
const scale = scaleToFit(sprite, clientRect);

return {
setStyle({
backgroundPosition: `${-sprite.x}px ${-sprite.y}px`,
backgroundImage: `url(${sprite.url})`,
width: `${sprite.w}px`,
height: `${sprite.h}px`,
transform: `scale(${scale})`,
};
}, [spriteInfo, activeIndex, imageParentRef]);
});
}, [sprite]);

const currentTime = useMemo(() => {
if (!spriteInfo || activeIndex === undefined) {
return undefined;
}

const sprite = spriteInfo[activeIndex];
if (!sprite) return undefined;

const start = TextUtils.secondsToTimestamp(sprite.start);

return start;
}, [activeIndex, spriteInfo]);
}, [sprite]);

function onScrubberClick(index: number) {
if (!spriteInfo || !onClick) {
function onScrubberClick() {
if (!sprite || !onClick) {
return;
}

const sprite = spriteInfo[index];

onClick(sprite.start);
}

if (!spriteInfo) return null;

return (
<div className="preview-scrubber">
{activeIndex !== undefined && spriteInfo && (
{sprite && (
<div className="scene-card-preview-image" ref={imageParentRef}>
<div className="scrubber-image" style={style}></div>
{currentTime !== undefined && (
Expand All @@ -163,7 +166,7 @@ export const PreviewScrubber: React.FC<IScenePreviewProps> = ({
</div>
)}
<HoverScrubber
totalSprites={81}
totalSprites={spriteInfo.length}
activeIndex={activeIndex}
setActiveIndex={(i) => debounceSetActiveIndex(i)}
onClick={onScrubberClick}
Expand Down
74 changes: 31 additions & 43 deletions ui/v2.5/src/components/Settings/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export const SettingsContext: React.FC = ({ children }) => {
setUI(data.configuration.ui);
}, [data, error]);

const resetSuccess = useDebounce(() => setUpdateSuccess(undefined), [], 4000);
const resetSuccess = useDebounce(() => setUpdateSuccess(undefined), 4000);

const onSuccess = useCallback(() => {
setUpdateSuccess(true);
Expand All @@ -158,7 +158,6 @@ export const SettingsContext: React.FC = ({ children }) => {
setSaveError(e);
}
},
[updateGeneralConfig, onSuccess],
500
);

Expand Down Expand Up @@ -208,7 +207,6 @@ export const SettingsContext: React.FC = ({ children }) => {
setSaveError(e);
}
},
[updateInterfaceConfig, onSuccess],
500
);

Expand Down Expand Up @@ -258,7 +256,6 @@ export const SettingsContext: React.FC = ({ children }) => {
setSaveError(e);
}
},
[updateDefaultsConfig, onSuccess],
500
);

Expand Down Expand Up @@ -308,7 +305,6 @@ export const SettingsContext: React.FC = ({ children }) => {
setSaveError(e);
}
},
[updateScrapingConfig, onSuccess],
500
);

Expand Down Expand Up @@ -342,25 +338,21 @@ export const SettingsContext: React.FC = ({ children }) => {
}

// saves the configuration if no further changes are made after a half second
const saveDLNAConfig = useDebounce(
async (input: GQL.ConfigDlnaInput) => {
try {
setUpdateSuccess(undefined);
await updateDLNAConfig({
variables: {
input,
},
});

setPendingDLNA(undefined);
onSuccess();
} catch (e) {
setSaveError(e);
}
},
[updateDLNAConfig, onSuccess],
500
);
const saveDLNAConfig = useDebounce(async (input: GQL.ConfigDlnaInput) => {
try {
setUpdateSuccess(undefined);
await updateDLNAConfig({
variables: {
input,
},
});

setPendingDLNA(undefined);
onSuccess();
} catch (e) {
setSaveError(e);
}
}, 500);

useEffect(() => {
if (!pendingDLNA) {
Expand Down Expand Up @@ -392,25 +384,21 @@ export const SettingsContext: React.FC = ({ children }) => {
}

// saves the configuration if no further changes are made after a half second
const saveUIConfig = useDebounce(
async (input: IUIConfig) => {
try {
setUpdateSuccess(undefined);
await updateUIConfig({
variables: {
input,
},
});

setPendingUI(undefined);
onSuccess();
} catch (e) {
setSaveError(e);
}
},
[updateUIConfig, onSuccess],
500
);
const saveUIConfig = useDebounce(async (input: IUIConfig) => {
try {
setUpdateSuccess(undefined);
await updateUIConfig({
variables: {
input,
},
});

setPendingUI(undefined);
onSuccess();
} catch (e) {
setSaveError(e);
}
}, 500);

useEffect(() => {
if (!pendingUI) {
Expand Down
10 changes: 3 additions & 7 deletions ui/v2.5/src/components/Shared/FilterSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,9 @@ export const FilterSelectComponent = <
};

const debounceDelay = 100;
const debounceLoadOptions = useDebounce(
(inputValue, callback) => {
loadOptions(inputValue).then(callback);
},
[loadOptions],
debounceDelay
);
const debounceLoadOptions = useDebounce((inputValue, callback) => {
loadOptions(inputValue).then(callback);
}, debounceDelay);

return (
<SelectComponent<T, IsMulti>
Expand Down
4 changes: 2 additions & 2 deletions ui/v2.5/src/components/Shared/FolderSelect/FolderSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Icon } from "../Icon";
import { LoadingIndicator } from "../LoadingIndicator";
import { useDirectory } from "src/core/StashService";
import { faEllipsis, faTimes } from "@fortawesome/free-solid-svg-icons";
import { useDebouncedSetState } from "src/hooks/debounce";
import { useDebounce } from "src/hooks/debounce";

interface IProps {
currentDirectory: string;
Expand Down Expand Up @@ -44,7 +44,7 @@ export const FolderSelect: React.FC<IProps> = ({
(error && hideError ? [] : defaultDirectoriesOrEmpty)
: defaultDirectoriesOrEmpty;

const debouncedSetDirectory = useDebouncedSetState(setDirectory, 250);
const debouncedSetDirectory = useDebounce(setDirectory, 250);

useEffect(() => {
if (currentDirectory !== directory) {
Expand Down
Loading

0 comments on commit 50c4ac9

Please sign in to comment.