working window manager

This commit is contained in:
Nomi Nonsense (Nonszy) 2025-09-03 12:09:00 +07:00
parent 3ff1248887
commit 9471a13388
5 changed files with 196 additions and 70 deletions

View File

@ -6,35 +6,38 @@ import HomeText from "@/components/home-text.mdx"
import Link from "next/link"; import Link from "next/link";
import { FakeWindow, HomeWindows } from "@/components/windows"; import { FakeWindow, HomeWindows } from "@/components/windows";
import Taskbar from "@/components/taskbar"; import Taskbar from "@/components/taskbar";
import { WindowManagerProvider } from "@/hooks/window-manager";
export default function Home() { export default function Home() {
return (<> return (<>
<main className="flex items-center pt-16 md:pt-24 pb-12 px-8 md:px-0 overflow-x-hidden"> <main className="flex items-center pt-16 md:pt-24 pb-12 px-8 md:px-0 overflow-x-hidden">
<FakeWindow windowText="Homepage"> <WindowManagerProvider>
<HomeWindows /> <FakeWindow windowText="Homepage">
<header className="text-center mb-8"> <HomeWindows />
<h1 className="font-bold text-3xl leading-normal"> <header className="text-center mb-8">
Nonszy Work<span className="text-primary">space</span> <h1 className="font-bold text-3xl leading-normal">
</h1> Nonszy Work<span className="text-primary">space</span>
</header> </h1>
<noscript> </header>
<LandingImage /> <noscript>
</noscript> <LandingImage />
<NolaGlitchClientOnly /> </noscript>
<Sosmed /> <NolaGlitchClientOnly />
<article className="space-y-6 leading-relaxed relative"> <Sosmed />
<HomeText /> <article className="space-y-6 leading-relaxed relative">
</article> <HomeText />
<section className="my-8"> </article>
<p> Powered with <Link href="https://www.cloudflare.com/" target="_blank" className="text-primary underline">Cloudflare</Link> </p> <section className="my-8">
</section> <p> Powered with <Link href="https://www.cloudflare.com/" target="_blank" className="text-primary underline">Cloudflare</Link> </p>
<footer className="mt-20 text-center"> </section>
<p>&copy; <span className="text-sm">2025 Nomi Nonszy</span></p> <footer className="mt-20 text-center">
<p className="text-sm"> <p>&copy; <span className="text-sm">2025 Nomi Nonszy</span></p>
<Link href={"/terms"} className="text-primary underline">Terms</Link> and <Link href={"/privacy"} className="text-primary underline">Privacy</Link> <p className="text-sm">
</p> <Link href={"/terms"} className="text-primary underline">Terms</Link> and <Link href={"/privacy"} className="text-primary underline">Privacy</Link>
</footer> </p>
</FakeWindow> </footer>
</FakeWindow>
</WindowManagerProvider>
</main> </main>
</>); </>);
} }

View File

@ -1,8 +1,23 @@
'use client' 'use client'
import { FakeWindow, useWindowManager } from "@/hooks/window-manager";
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import clsx from "clsx"; import clsx from "clsx";
import { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect, useReducer } from "react";
export const RestoreWindowsButton = ({ onClick, className, ...props}: React.ComponentProps<"button">) => {
const windowManager = useWindowManager();
const isAnyWindowsClosed = !windowManager.windows.find(w => w.closed == true);
return (
<button
className={clsx("bg-primary border border-[#FFA826]", className, isAnyWindowsClosed && "hidden")}
onClick={windowManager.openAll}
{...props}
/>
)
}
export const FakeRelativeWindow = ({ export const FakeRelativeWindow = ({
windowText, windowText,
@ -17,12 +32,15 @@ export const FakeRelativeWindow = ({
withAnim?: boolean, withAnim?: boolean,
children: React.ReactNode children: React.ReactNode
}) => { }) => {
const [isMinimized, setMinimize] = useState(false); const windowManager = useWindowManager();
const [offset, setOffset] = useState({ x: 0, y: 0 }); const windowName = windowText.toLocaleLowerCase();
const currentWindow = windowManager.get(windowName);
const [animation, setAnimation] = useState({ const [animation, setAnimation] = useState({
window: "animate-window-popup", window: "animate-window-popup",
content: "animate-[fade-in-half_1s]" content: "animate-[fade-in-half_1s]"
}); });
const popupRef = useRef<HTMLDivElement>(null); const popupRef = useRef<HTMLDivElement>(null);
const pos = useRef({ const pos = useRef({
dragging: false, dragging: false,
@ -32,6 +50,32 @@ export const FakeRelativeWindow = ({
y: 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(() => {
console.log(windowManager.windows)
}, [windowManager.windows]);
useEffect(() => {
if (popupRef.current) popupRef.current.style.transform = `translate(${currentWindow?.offset.x || 0}px, ${currentWindow?.offset.y || 0}px)`;
}, [currentWindow?.offset])
useEffect(() => { useEffect(() => {
if (!withAnim) return; if (!withAnim) return;
const node = popupRef.current; const node = popupRef.current;
@ -56,16 +100,17 @@ export const FakeRelativeWindow = ({
return () => observer.disconnect(); return () => observer.disconnect();
}, []); }, []);
const toggleMinimize = () => setMinimize(!isMinimized); const toggleMinimize = () => windowManager.toggleMinimize(windowName);
const handleClose = () => windowManager.close(windowName);
const onMouseDown = (e: React.MouseEvent) => { const onMouseDown = (e: React.MouseEvent) => {
if (!draggable) return; if (!draggable || !currentWindow) return;
if (popupRef.current) { if (popupRef.current) {
pos.current.dragging = true; pos.current.dragging = true;
pos.current.mouseX = e.clientX; pos.current.mouseX = e.clientX;
pos.current.mouseY = e.clientY; pos.current.mouseY = e.clientY;
pos.current.x = offset.x; pos.current.x = currentWindow.offset.x;
pos.current.y = offset.y; pos.current.y = currentWindow.offset.y;
document.body.style.userSelect = "none"; document.body.style.userSelect = "none";
} }
window.addEventListener("mousemove", onMouseMove); window.addEventListener("mousemove", onMouseMove);
@ -80,16 +125,21 @@ export const FakeRelativeWindow = ({
}; };
const onMouseMove = (e: MouseEvent) => { const onMouseMove = (e: MouseEvent) => {
if (!currentWindow) return;
if (pos.current.dragging && popupRef.current) { if (pos.current.dragging && popupRef.current) {
const dx = e.clientX - pos.current.mouseX; const dx = e.clientX - pos.current.mouseX;
const dy = e.clientY - pos.current.mouseY; const dy = e.clientY - pos.current.mouseY;
const newX = pos.current.x + dx; const newX = pos.current.x + dx;
const newY = pos.current.y + dy; const newY = pos.current.y + dy;
popupRef.current.style.transform = `translate(${newX}px, ${newY}px)`; windowManager.move(windowName, {
setOffset({ x: newX, y: newY }); x: newX,
y: newY
});
} }
}; };
if (!currentWindow || currentWindow?.closed) return
return ( return (
<div className={clsx("absolute hidden lg:block", className)}> <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={clsx("mx-auto md:border bg-background border-primary", withAnim && animation.window)} ref={popupRef}>
@ -98,14 +148,24 @@ export const FakeRelativeWindow = ({
onMouseDown={onMouseDown} onMouseDown={onMouseDown}
> >
<div className="ms-1 pointer-events-none"> <div className="ms-1 pointer-events-none">
{windowText} {currentWindow ? currentWindow.name : "Error!"}
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button className="bg-primary border border-[#FFA826] border-outset p-1" onClick={toggleMinimize}><Icon icon="lucide:minus"/></button> <button
<button className="bg-primary border border-[#FFA826] border-outset p-1"><Icon icon="lucide:x"/></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> </div>
<div className={clsx("m-1 border border-primary", isMinimized ? "h-0 overflow-y-clip" : "h-fit")}> <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)}> <div className={clsx("md:p-4", withAnim && animation.content)}>
{children} {children}
</div> </div>

View File

@ -8,10 +8,8 @@ export const FloatingLabel = ({
children: React.ReactNode; children: React.ReactNode;
}) => { }) => {
return ( return (
<div> <PopoverText text={placeholder}>
<PopoverText text={placeholder}> {children}
{children} </PopoverText>
</PopoverText>
</div>
); );
}; };

View File

@ -1,6 +1,6 @@
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import { FloatingLabel } from "@/components/floating-label"; import { FloatingLabel } from "@/components/floating-label";
import { FakeRelativeWindow } from "./client-windows"; import { FakeRelativeWindow, RestoreWindowsButton } from "./client-windows";
import Image from "next/image" import Image from "next/image"
import Link from "next/link"; import Link from "next/link";
@ -16,12 +16,17 @@ export const FakeWindow = ({
<div className="ms-1 pointer-events-none"> <div className="ms-1 pointer-events-none">
{windowText} {windowText}
</div> </div>
<FloatingLabel placeholder="Useless button btw"> <div className="inline-flex">
<div className="flex gap-2"> <RestoreWindowsButton className="border-outset px-2 text-xs me-2">
<button className="bg-primary border border-[#FFA826] border-outset p-1"><Icon icon="lucide:minus"/></button> Restore Windows
<button className="bg-primary border border-[#FFA826] border-outset p-1"><Icon icon="lucide:x"/></button> </RestoreWindowsButton>
</div> <FloatingLabel placeholder="Useless button btw">
</FloatingLabel> <div className="flex gap-2">
<button className="bg-primary border border-[#FFA826] border-outset p-1"><Icon icon="lucide:minus"/></button>
<button className="bg-primary border border-[#FFA826] border-outset p-1"><Icon icon="lucide:x"/></button>
</div>
</FloatingLabel>
</div>
</div> </div>
</div> </div>
<div className="m-1 md:border border-primary"> <div className="m-1 md:border border-primary">
@ -34,23 +39,6 @@ export const FakeWindow = ({
export const HomeWindows = () => ( export const HomeWindows = () => (
<div className="relative"> <div className="relative">
{/* <FakeRelativeWindow
windowText="featured.exe"
className="left-[540px] top-0 z-20"
draggable
>
<div className="space-y-2" style={{ width: 300 }}>
<Image
className=""
alt="Left 4 Dead"
src="https://shared.fastly.steamstatic.com/store_item_assets/steam/apps/550/header.jpg?t=1745368562"
width={300}
height={200}
unoptimized
/>
<p>Join my Left 4 Dead 2 server!</p>
</div>
</FakeRelativeWindow> */}
<FakeRelativeWindow <FakeRelativeWindow
windowText="nola.png" windowText="nola.png"
className="-right-[73%] top-[1300px] z-20" className="-right-[73%] top-[1300px] z-20"

View File

@ -1,9 +1,86 @@
interface iFakeWindow { 'use client'
import React, { createContext, useContext, useEffect, useState } from "react";
export interface FakeWindow {
name: string; name: string;
minimized: boolean; minimized: boolean;
closed: boolean; closed: boolean;
offset: { offset: {
x: number x: number;
y: number y: number;
} }
} }
interface WindowManagerContextType {
windows: FakeWindow[];
isLocalDataExists: boolean;
get: (name: string) => FakeWindow | undefined;
add: (window: FakeWindow) => void;
updateWindow: (name: string, changes: Partial<FakeWindow>) => void;
toggleMinimize: (name: string) => void;
close: (name: string) => void;
remove: (name: string) => void;
move: (name: string, offset: { x: number; y: number }) => void;
openAll: () => void;
}
const WindowManagerContext = createContext<WindowManagerContextType | undefined>(undefined);
export const WindowManagerProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [windows, setWindows] = useState<FakeWindow[]>([]);
const [isInit, setInit] = useState(false);
const [isLocalDataExists, setExists] = useState(false);
useEffect(() => {
const data = localStorage.getItem("fake-windows-data");
if (data) {
setWindows(JSON.parse(data));
setExists(true);
}
setInit(true);
}, []);
useEffect(() => {
if (isInit && typeof window !== 'undefined' && windows.length > 0) {
window.localStorage.setItem('fake-windows-data', JSON.stringify(windows));
}
}, [windows]);
const get = (name: string) => windows.find(window => window.name == name);
const add = (window: FakeWindow) => setWindows(w => [...w, window]);
const updateWindow = (name: string, changes: Partial<FakeWindow>) =>
setWindows(w =>
w.map(win => win.name === name ? { ...win, ...changes } : win)
);
const toggleMinimize = (name: string) =>
setWindows(w =>
w.map(win => win.name === name ? { ...win, minimized: !win.minimized } : win)
);
const open = (name: string) => updateWindow(name, { closed: false });
const close = (name: string) => updateWindow(name, { closed: true });
const move = (name: string, offset: { x: number; y: number }) =>
updateWindow(name, { offset });
const remove = (name: string) => setWindows(w => w.filter(win => win.name !== name));
const openAll = () => setWindows(w =>
w.map(win => ({ ...win, closed: false }))
);
return (
<WindowManagerContext.Provider value={{
windows,
isLocalDataExists,
get, add, updateWindow, toggleMinimize, close, move, remove, openAll
}}>
{children}
</WindowManagerContext.Provider>
);
};
export function useWindowManager() {
const ctx = useContext(WindowManagerContext);
if (!ctx) throw new Error("useWindowManager must be used within WindowManagerProvider");
return ctx;
}