feat: resizable tool settings window

This commit is contained in:
trafficlunar 2024-12-31 19:39:52 +00:00
parent c8cccd8bf7
commit acfd638d2c
4 changed files with 77 additions and 16 deletions

View file

@ -267,24 +267,30 @@ function Canvas() {
}; };
useEffect(() => { useEffect(() => {
const container = stageContainerRef.current;
if (!container) return;
const resizeCanvas = () => { const resizeCanvas = () => {
if (stageContainerRef.current) { setStageSize({
setStageSize({ width: container.offsetWidth,
width: stageContainerRef.current.offsetWidth, height: container.offsetHeight,
height: stageContainerRef.current.offsetHeight, });
});
}
}; };
const resizeObserver = new ResizeObserver(resizeCanvas);
resizeObserver.observe(container);
resizeCanvas(); resizeCanvas();
return () => resizeObserver.disconnect();
}, [stageContainerRef]);
useEffect(() => {
setBlocks(welcomeBlocksData); setBlocks(welcomeBlocksData);
window.addEventListener("resize", resizeCanvas);
window.addEventListener("keydown", onKeyDown); window.addEventListener("keydown", onKeyDown);
window.addEventListener("keyup", onKeyUp); window.addEventListener("keyup", onKeyUp);
return () => { return () => {
window.removeEventListener("resize", resizeCanvas);
window.removeEventListener("keydown", onKeyDown); window.removeEventListener("keydown", onKeyDown);
window.removeEventListener("keyup", onKeyUp); window.removeEventListener("keyup", onKeyUp);
}; };

View file

@ -56,7 +56,7 @@ function BlockSelector({ stageWidth, searchInput }: Props) {
return ( return (
<Stage <Stage
width={stageWidth} width={stageWidth}
height={Math.ceil(Object.keys(blockData).length / blocksPerColumn) * (32 + 2)} height={Math.ceil(Object.keys(blockData).length / blocksPerColumn) * (32 + 2) + 8}
options={{ backgroundAlpha: 0 }} options={{ backgroundAlpha: 0 }}
onMouseLeave={() => setHoverPosition(null)} onMouseLeave={() => setHoverPosition(null)}
> >

View file

@ -4,29 +4,84 @@ import { SettingsContext } from "@/context/Settings";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { ScrollArea } from "@/components/ui/scroll-area";
import ColorPicker from "./ColorPicker"; import ColorPicker from "./ColorPicker";
import Replace from "./Replace"; import Replace from "./Replace";
import Radius from "./Radius"; import Radius from "./Radius";
import BlockSelector from "./BlockSelector"; import BlockSelector from "./BlockSelector";
import { ScrollArea } from "../ui/scroll-area"; import { GripVerticalIcon } from "lucide-react";
function ToolSettings() { function ToolSettings() {
const { settings } = useContext(SettingsContext); const { settings } = useContext(SettingsContext);
const [width, setWidth] = useState(288);
const [resizing, setResizing] = useState(false);
const divRef = useRef<HTMLDivElement>(null); const divRef = useRef<HTMLDivElement>(null);
const [stageWidth, setStageWidth] = useState(0); const [stageWidth, setStageWidth] = useState(0);
const [searchInput, setSearchInput] = useState(""); const [searchInput, setSearchInput] = useState("");
const onMouseDown = () => {
setResizing(true);
document.body.style.cursor = "ew-resize";
};
const onMouseUp = () => {
setResizing(false);
document.body.style.cursor = "auto";
};
const onMouseMove = (e: MouseEvent) => {
if (!resizing) return;
setWidth(() => Math.min(Math.max(window.innerWidth - e.clientX, 200), 1000));
};
useEffect(() => { useEffect(() => {
if (!divRef.current) return; if (resizing) {
setStageWidth(divRef.current.clientWidth); document.addEventListener("mousemove", onMouseMove);
}, []); document.addEventListener("mouseup", onMouseUp);
} else {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
}
return () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
}, [resizing]);
useEffect(() => {
const div = divRef.current;
if (!div) return;
const set = () => setStageWidth(div.clientWidth);
const resizeObserver = new ResizeObserver(set);
resizeObserver.observe(div);
set();
return () => {
resizeObserver.disconnect();
};
}, [divRef]);
return ( return (
<> <>
{(settings.colorPicker || settings.blockReplacer || settings.radiusChanger || settings.blockSelector) && ( {(settings.colorPicker || settings.blockReplacer || settings.radiusChanger || settings.blockSelector) && (
<div className="w-72 border-l border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-2 pb-0 flex flex-col h-full gap-2"> <div
style={{ width: `${width}px` }}
className="w-72 border-l border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-2 pb-0 flex flex-col h-full gap-2 relative"
>
<div
onMouseDown={onMouseDown}
className="absolute top-0 -left-2 h-full w-4 bg-zinc-300 dark:bg-zinc-800 flex justify-center items-center cursor-e-resize opacity-0 transition-opacity duration-300 delay-100 hover:opacity-100"
>
<GripVerticalIcon />
</div>
{settings.colorPicker && ( {settings.colorPicker && (
<> <>
<ColorPicker /> <ColorPicker />
@ -51,7 +106,7 @@ function ToolSettings() {
{settings.blockSelector && ( {settings.blockSelector && (
<> <>
<Input placeholder="Search for blocks..." value={searchInput} onChange={(e) => setSearchInput(e.target.value)} /> <Input placeholder="Search for blocks..." value={searchInput} onChange={(e) => setSearchInput(e.target.value)} />
<ScrollArea ref={divRef} className="w-full flex-1 pb-2"> <ScrollArea ref={divRef} className="w-full flex-1 pb-0">
<BlockSelector stageWidth={stageWidth} searchInput={searchInput} /> <BlockSelector stageWidth={stageWidth} searchInput={searchInput} />
</ScrollArea> </ScrollArea>
</> </>

View file

@ -18,7 +18,7 @@ function AppPage() {
<SettingsProvider> <SettingsProvider>
<TexturesProvider> <TexturesProvider>
<ToolProvider> <ToolProvider>
<main className="h-screen grid grid-rows-[2.5rem_minmax(0,1fr)] grid-cols-[2.5rem_1fr_auto]"> <main className="h-screen grid grid-rows-[2.5rem_minmax(0,1fr)] grid-cols-[2.5rem_minmax(0,1fr)_auto]">
<Menubar /> <Menubar />
<Toolbar /> <Toolbar />
<Canvas /> <Canvas />