mirror of
https://github.com/p-stream/p-stream.git
synced 2026-04-14 14:10:22 +00:00
allow custom playback speed entry
This commit is contained in:
parent
98ec8a1d67
commit
654cb061a4
1 changed files with 153 additions and 20 deletions
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue