Files
deusdata--codebase-memory-mcp/graph-ui/src/components/DisplaySettingsMenu.tsx
T
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

150 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useRef, useState } from "react";
import { Button } from "@/components/ui/button";
import {
DEFAULT_DISPLAY_SETTINGS,
DISPLAY_LIMITS,
type DisplaySettings,
} from "../lib/density";
interface DisplaySettingsMenuProps {
settings: DisplaySettings;
onChange: (next: DisplaySettings) => void;
}
interface SliderRowProps {
label: string;
hint: string;
value: number;
min: number;
max: number;
onChange: (value: number) => void;
}
function SliderRow({ label, hint, value, min, max, onChange }: SliderRowProps) {
return (
<label className="block">
<div className="flex items-center justify-between mb-1">
<span className="text-[11px] text-foreground/70">{label}</span>
<span className="text-[10px] font-mono text-cyan-300/70 tabular-nums">
{value.toFixed(2)}×
</span>
</div>
<input
type="range"
min={min}
max={max}
step={0.05}
value={value}
onChange={(e) => onChange(parseFloat(e.target.value))}
className="w-full accent-cyan-400 cursor-pointer"
aria-label={`${label} (${hint})`}
/>
<p className="text-[9px] text-foreground/30 mt-0.5">{hint}</p>
</label>
);
}
/* Contrast / brightness controls for the 3D graph. These ride on top of the
* automatic density compensation — the defaults already adapt to graph size,
* so 1.00× is "auto"; the sliders let the user push it. */
export function DisplaySettingsMenu({
settings,
onChange,
}: DisplaySettingsMenuProps) {
const [open, setOpen] = useState(false);
const rootRef = useRef<HTMLDivElement>(null);
/* Close on outside click / Escape */
useEffect(() => {
if (!open) return;
const onDown = (e: MouseEvent) => {
if (rootRef.current && !rootRef.current.contains(e.target as Node)) {
setOpen(false);
}
};
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") setOpen(false);
};
document.addEventListener("mousedown", onDown);
document.addEventListener("keydown", onKey);
return () => {
document.removeEventListener("mousedown", onDown);
document.removeEventListener("keydown", onKey);
};
}, [open]);
const set = (patch: Partial<DisplaySettings>) =>
onChange({ ...settings, ...patch });
const isDefault =
settings.edgeBrightness === DEFAULT_DISPLAY_SETTINGS.edgeBrightness &&
settings.nodeGlow === DEFAULT_DISPLAY_SETTINGS.nodeGlow &&
settings.bloom === DEFAULT_DISPLAY_SETTINGS.bloom;
return (
<div ref={rootRef} className="relative">
<Button
variant="outline"
size="sm"
onClick={() => setOpen((v) => !v)}
aria-expanded={open}
aria-haspopup="dialog"
title="Contrast & brightness"
>
Display{!isDefault && <span className="ml-1 text-cyan-300"></span>}
</Button>
{open && (
<div
role="dialog"
aria-label="Display settings"
className="absolute top-10 right-0 w-64 p-4 rounded-lg border border-border/60 bg-[#0b1920]/95 backdrop-blur-md shadow-xl z-20 space-y-3.5"
>
<div className="flex items-center justify-between">
<span className="text-[11px] font-medium text-foreground/50 uppercase tracking-widest">
Contrast
</span>
<button
onClick={() => onChange(DEFAULT_DISPLAY_SETTINGS)}
className="text-[10px] text-primary/70 hover:text-primary transition-colors disabled:opacity-30"
disabled={isDefault}
>
Reset
</button>
</div>
<SliderRow
label="Edge brightness"
hint="Dim the web of links on dense graphs"
value={settings.edgeBrightness}
min={DISPLAY_LIMITS.edgeBrightness.min}
max={DISPLAY_LIMITS.edgeBrightness.max}
onChange={(edgeBrightness) => set({ edgeBrightness })}
/>
<SliderRow
label="Node glow"
hint="Halo boost around each node"
value={settings.nodeGlow}
min={DISPLAY_LIMITS.nodeGlow.min}
max={DISPLAY_LIMITS.nodeGlow.max}
onChange={(nodeGlow) => set({ nodeGlow })}
/>
<SliderRow
label="Bloom"
hint="Overall glow bloom strength"
value={settings.bloom}
min={DISPLAY_LIMITS.bloom.min}
max={DISPLAY_LIMITS.bloom.max}
onChange={(bloom) => set({ bloom })}
/>
<p className="text-[9px] text-foreground/30 pt-1 border-t border-border/30">
1.00× follows the automatic density compensation. Lower the
edge/glow/bloom values when a large graph washes out to white.
</p>
</div>
)}
</div>
);
}