176 lines
5.0 KiB
TypeScript
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>
|
|
)
|
|
} |