allow custom playback speed entry

This commit is contained in:
Pas 2025-07-07 16:18:31 -06:00
parent 98ec8a1d67
commit 654cb061a4

View file

@ -1,8 +1,9 @@
import classNames from "classnames";
import { useCallback, useEffect, useMemo } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Toggle } from "@/components/buttons/Toggle";
import { Icon, Icons } from "@/components/Icon";
import { Menu } from "@/components/player/internals/ContextMenu";
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
import { usePlayerStore } from "@/stores/player/store";
@ -16,27 +17,159 @@ function ButtonList(props: {
onClick: (v: any) => void;
disabled?: boolean;
}) {
const [editingIndex, setEditingIndex] = useState<number | null>(null);
const [customValue, setCustomValue] = useState<string>("");
const [isCustomSpeed, setIsCustomSpeed] = useState(false);
// Check if current speed is a custom value (not in preset options)
useEffect(() => {
if (!props.options.includes(props.selected)) {
setIsCustomSpeed(true);
} else {
setIsCustomSpeed(false);
}
}, [props.selected, props.options]);
const handleButtonClick = useCallback(
(option: number, index: number) => {
if (editingIndex === index) {
// Already in edit mode, do nothing
return;
}
// If clicking the custom speed button, enter edit mode
if (isCustomSpeed && option === props.selected) {
setEditingIndex(0);
setCustomValue(option.toString());
return;
}
props.onClick(option);
setIsCustomSpeed(false);
},
[editingIndex, props, isCustomSpeed],
);
const handleDoubleClick = useCallback(
(option: number, index: number) => {
if (props.disabled) return;
setEditingIndex(index);
setCustomValue(option.toString());
},
[props.disabled],
);
const handleCustomValueChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setCustomValue(e.target.value);
},
[],
);
const handleCustomValueKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
const value = parseFloat(customValue);
if (!Number.isNaN(value) && value > 0 && value <= 5) {
props.onClick(value);
setEditingIndex(null);
setIsCustomSpeed(true);
}
} else if (e.key === "Escape") {
setEditingIndex(null);
}
},
[customValue, props],
);
const handleInputBlur = useCallback(() => {
setEditingIndex(null);
}, []);
const handleResetCustomSpeed = useCallback(() => {
setIsCustomSpeed(false);
props.onClick(1); // Reset to default speed (1x)
}, [props]);
return (
<div className="flex items-center bg-video-context-light/10 p-1 rounded-lg">
{props.options.map((option) => {
return (
<button
type="button"
disabled={props.disabled}
className={classNames(
"w-full px-2 py-1 rounded-md tabbable",
props.selected === option
? "bg-video-context-light/20 text-white"
: null,
props.disabled ? "opacity-50 cursor-not-allowed" : null,
)}
onClick={() => props.onClick(option)}
key={option}
>
{option}x
</button>
);
})}
{isCustomSpeed ? (
// Show only the custom speed button when a custom speed is set
<button
type="button"
disabled={props.disabled}
className={classNames(
"w-full px-2 py-1 rounded-md tabbable relative",
"bg-video-context-light/20 text-white",
props.disabled ? "opacity-50 cursor-not-allowed" : null,
)}
onClick={() => handleButtonClick(props.selected, 0)}
onDoubleClick={() => handleDoubleClick(props.selected, 0)}
key="custom"
>
{editingIndex === 0 ? (
<input
type="text"
value={customValue}
onChange={handleCustomValueChange}
onKeyDown={handleCustomValueKeyDown}
onBlur={handleInputBlur}
className="w-full bg-transparent text-center focus:outline-none"
autoFocus
aria-label="Custom playback speed"
/>
) : (
<>
{`${props.selected}x`}
<button
type="button"
className="absolute right-1 top-1/2 transform -translate-y-1/2 text-xs text-video-context-light/70 hover:text-white"
onClick={handleResetCustomSpeed}
title="Reset to presets"
>
<Icon icon={Icons.X} className="text-sm" />
</button>
</>
)}
</button>
) : (
// Show all preset options when no custom speed is set
props.options.map((option, index) => {
const isEditing = editingIndex === index;
return (
<button
type="button"
disabled={props.disabled}
className={classNames(
"w-full px-2 py-1 rounded-md tabbable relative",
props.selected === option
? "bg-video-context-light/20 text-white"
: null,
props.disabled ? "opacity-50 cursor-not-allowed" : null,
)}
onClick={() => handleButtonClick(option, index)}
onDoubleClick={() => handleDoubleClick(option, index)}
key={option}
>
{isEditing ? (
<input
type="text"
value={customValue}
onChange={handleCustomValueChange}
onKeyDown={handleCustomValueKeyDown}
onBlur={handleInputBlur}
className="w-full bg-transparent text-center focus:outline-none"
autoFocus
aria-label="Custom playback speed"
/>
) : (
`${option}x`
)}
</button>
);
})
)}
</div>
);
}