Change auto_memory to be layer_offloading and allow you to set the amount to unload
This commit is contained in:
@@ -296,3 +296,147 @@ export const FormGroup: React.FC<FormGroupProps> = props => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface SliderInputProps extends InputProps {
|
||||
value: number;
|
||||
onChange: (value: number) => void;
|
||||
min: number;
|
||||
max: number;
|
||||
step?: number;
|
||||
disabled?: boolean;
|
||||
showValue?: boolean;
|
||||
}
|
||||
|
||||
export const SliderInput: React.FC<SliderInputProps> = props => {
|
||||
const { label, value, onChange, min, max, step = 1, disabled, className, docKey = null, showValue = true } = props;
|
||||
let { doc } = props;
|
||||
if (!doc && docKey) {
|
||||
doc = getDoc(docKey);
|
||||
}
|
||||
|
||||
const trackRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const [dragging, setDragging] = React.useState(false);
|
||||
|
||||
const clamp = (v: number) => (v < min ? min : v > max ? max : v);
|
||||
const snapToStep = (v: number) => {
|
||||
if (!Number.isFinite(v)) return min;
|
||||
const steps = Math.round((v - min) / step);
|
||||
const snapped = min + steps * step;
|
||||
return clamp(Number(snapped.toFixed(6)));
|
||||
};
|
||||
|
||||
const percent = React.useMemo(() => {
|
||||
if (max === min) return 0;
|
||||
const p = ((value - min) / (max - min)) * 100;
|
||||
return p < 0 ? 0 : p > 100 ? 100 : p;
|
||||
}, [value, min, max]);
|
||||
|
||||
const calcFromClientX = React.useCallback(
|
||||
(clientX: number) => {
|
||||
const el = trackRef.current;
|
||||
if (!el || !Number.isFinite(clientX)) return;
|
||||
const rect = el.getBoundingClientRect();
|
||||
const width = rect.right - rect.left;
|
||||
if (!(width > 0)) return;
|
||||
|
||||
// Clamp ratio to [0, 1] so it can never flip ends.
|
||||
const ratioRaw = (clientX - rect.left) / width;
|
||||
const ratio = ratioRaw <= 0 ? 0 : ratioRaw >= 1 ? 1 : ratioRaw;
|
||||
|
||||
const raw = min + ratio * (max - min);
|
||||
onChange(snapToStep(raw));
|
||||
},
|
||||
[min, max, step, onChange],
|
||||
);
|
||||
|
||||
// Mouse/touch pointer drag
|
||||
const onPointerDown = (e: React.PointerEvent) => {
|
||||
if (disabled) return;
|
||||
e.preventDefault();
|
||||
|
||||
// Capture the pointer so moves outside the element are still tracked correctly
|
||||
try {
|
||||
(e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId);
|
||||
} catch {}
|
||||
|
||||
setDragging(true);
|
||||
calcFromClientX(e.clientX);
|
||||
|
||||
const handleMove = (ev: PointerEvent) => {
|
||||
ev.preventDefault();
|
||||
calcFromClientX(ev.clientX);
|
||||
};
|
||||
const handleUp = (ev: PointerEvent) => {
|
||||
setDragging(false);
|
||||
// release capture if we got it
|
||||
try {
|
||||
(e.currentTarget as HTMLElement).releasePointerCapture?.(e.pointerId);
|
||||
} catch {}
|
||||
window.removeEventListener('pointermove', handleMove);
|
||||
window.removeEventListener('pointerup', handleUp);
|
||||
};
|
||||
|
||||
window.addEventListener('pointermove', handleMove);
|
||||
window.addEventListener('pointerup', handleUp);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(className, disabled ? 'opacity-30 cursor-not-allowed' : '')}>
|
||||
{label && (
|
||||
<label className={labelClasses}>
|
||||
{label}{' '}
|
||||
{doc && (
|
||||
<div className="inline-block ml-1 text-xs text-gray-500 cursor-pointer" onClick={() => openDoc(doc)}>
|
||||
<CircleHelp className="inline-block w-4 h-4 cursor-pointer" />
|
||||
</div>
|
||||
)}
|
||||
</label>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex-1">
|
||||
<div
|
||||
ref={trackRef}
|
||||
onPointerDown={onPointerDown}
|
||||
className={classNames(
|
||||
'relative w-full h-6 select-none outline-none',
|
||||
disabled ? 'pointer-events-none' : 'cursor-pointer',
|
||||
)}
|
||||
>
|
||||
{/* Thicker track */}
|
||||
<div className="pointer-events-none absolute left-0 right-0 top-1/2 -translate-y-1/2 h-3 rounded-sm bg-gray-800 border border-gray-700" />
|
||||
|
||||
{/* Fill */}
|
||||
<div
|
||||
className="pointer-events-none absolute left-0 top-1/2 -translate-y-1/2 h-3 rounded-sm bg-blue-600"
|
||||
style={{ width: `${percent}%` }}
|
||||
/>
|
||||
|
||||
{/* Thumb */}
|
||||
<div
|
||||
onPointerDown={onPointerDown}
|
||||
className={classNames(
|
||||
'absolute top-1/2 -translate-y-1/2 -ml-2',
|
||||
'h-4 w-4 rounded-full bg-white shadow border border-gray-300 cursor-pointer',
|
||||
'after:content-[""] after:absolute after:inset-[-6px] after:rounded-full after:bg-transparent', // expands hit area
|
||||
dragging ? 'ring-2 ring-blue-600' : '',
|
||||
)}
|
||||
style={{ left: `calc(${percent}% )` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between text-xs text-gray-500 mt-0.5 select-none">
|
||||
<span>{min}</span>
|
||||
<span>{max}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showValue && (
|
||||
<div className="min-w-[3.5rem] text-right text-sm px-3 py-1 bg-gray-800 border border-gray-700 rounded-sm">
|
||||
{Number.isFinite(value) ? value : ''}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user