nonszy-space/src/components/client-windows.tsx
2025-09-04 21:21:32 +07:00

176 lines
5.0 KiB
TypeScript

'use client'
import { FakeWindow, useWindowManager } from "@/hooks/window-manager";
import { Icon } from "@iconify/react";
import clsx from "clsx";
import React, { useState, useRef, useEffect, useReducer } from "react";
export const RestoreWindowsButton = ({ onClick, className, ...props}: React.ComponentProps<"button">) => {
const windowManager = useWindowManager();
const isWindowsDataDirty = !windowManager.windows.find(w =>
w.closed == true
|| w.minimized == true
|| (w.offset.x != 0 && w.offset.y != 0)
)
return (
<button
className={clsx("bg-primary border border-[#FFA826]", className, isWindowsDataDirty && "hidden")}
onClick={windowManager.resetAll}
{...props}
/>
)
}
export const FakeRelativeWindow = ({
windowText,
className,
draggable,
withAnim,
children
}: {
windowText: string,
className?: string,
draggable?: boolean,
withAnim?: boolean,
children: React.ReactNode
}) => {
const windowManager = useWindowManager();
const windowName = windowText.toLocaleLowerCase();
const currentWindow = windowManager.get(windowName);
const [animation, setAnimation] = useState({
window: "animate-window-popup",
content: "animate-[fade-in-half_1s]"
});
const popupRef = useRef<HTMLDivElement>(null);
const pos = useRef({
dragging: false,
mouseX: 0,
mouseY: 0,
x: 0,
y: 0
});
const populateWindow = () => {
windowManager.add({
name: windowName,
closed: false,
minimized: false,
offset: { x: 0, y: 0 }
})
}
useEffect(() => {
if (!windowManager.isLocalDataExists) {
if (!currentWindow) populateWindow();
return () => {
windowManager.remove(windowName);
}
}
}, []);
useEffect(() => {
if (popupRef.current) popupRef.current.style.transform = `translate(${currentWindow?.offset.x || 0}px, ${currentWindow?.offset.y || 0}px)`;
}, [currentWindow?.offset])
useEffect(() => {
if (!withAnim) return;
const node = popupRef.current;
if (!node) return;
const observer = new window.IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setAnimation({
window: "animate-window-popup",
content: "animate-[fade-in-half_1s]"
});
} else {
setAnimation({
window: "scale-x-0 scale-y-[0.03] animate-window-popdown",
content: "animate-[fade-out-half_1s]"
});
}
},
{ threshold: 0.3 }
);
observer.observe(node);
return () => observer.disconnect();
}, []);
const toggleMinimize = () => windowManager.toggleMinimize(windowName);
const handleClose = () => windowManager.close(windowName);
const onMouseDown = (e: React.MouseEvent) => {
if (!draggable || !currentWindow) return;
if (popupRef.current) {
pos.current.dragging = true;
pos.current.mouseX = e.clientX;
pos.current.mouseY = e.clientY;
pos.current.x = currentWindow.offset.x;
pos.current.y = currentWindow.offset.y;
document.body.style.userSelect = "none";
}
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", onMouseUp);
};
const onMouseUp = () => {
pos.current.dragging = false;
document.body.style.userSelect = "";
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", onMouseUp);
};
const onMouseMove = (e: MouseEvent) => {
if (!currentWindow) return;
if (pos.current.dragging && popupRef.current) {
const dx = e.clientX - pos.current.mouseX;
const dy = e.clientY - pos.current.mouseY;
const newX = pos.current.x + dx;
const newY = pos.current.y + dy;
windowManager.move(windowName, {
x: newX,
y: newY
});
}
};
if (!currentWindow || currentWindow?.closed) return
return (
<div className={clsx("absolute hidden lg:block", className)}>
<div className={clsx("mx-auto md:border bg-background border-primary", withAnim && animation.window)} ref={popupRef}>
<div
className="hidden md:flex bg-primary p-2 justify-between text-background windowbar"
onMouseDown={onMouseDown}
>
<div className="ms-1 pointer-events-none">
{currentWindow ? currentWindow.name : "Error!"}
</div>
<div className="flex gap-2">
<button
className="bg-primary border border-[#FFA826] border-outset p-1"
onClick={toggleMinimize}
>
<Icon icon="lucide:minus"/>
</button>
<button
className="bg-primary border border-[#FFA826] border-outset p-1"
onClick={handleClose}
>
<Icon icon="lucide:x"/>
</button>
</div>
</div>
<div className={clsx("m-1 border border-primary", currentWindow?.minimized ? "h-0 overflow-y-clip" : "h-fit")}>
<div className={clsx("md:p-4", withAnim && animation.content)}>
{children}
</div>
</div>
</div>
</div>
)
}