working window manager
This commit is contained in:
parent
3ff1248887
commit
9471a13388
@ -6,35 +6,38 @@ import HomeText from "@/components/home-text.mdx"
|
||||
import Link from "next/link";
|
||||
import { FakeWindow, HomeWindows } from "@/components/windows";
|
||||
import Taskbar from "@/components/taskbar";
|
||||
import { WindowManagerProvider } from "@/hooks/window-manager";
|
||||
|
||||
export default function Home() {
|
||||
return (<>
|
||||
<main className="flex items-center pt-16 md:pt-24 pb-12 px-8 md:px-0 overflow-x-hidden">
|
||||
<FakeWindow windowText="Homepage">
|
||||
<HomeWindows />
|
||||
<header className="text-center mb-8">
|
||||
<h1 className="font-bold text-3xl leading-normal">
|
||||
Nonszy Work<span className="text-primary">space</span>
|
||||
</h1>
|
||||
</header>
|
||||
<noscript>
|
||||
<LandingImage />
|
||||
</noscript>
|
||||
<NolaGlitchClientOnly />
|
||||
<Sosmed />
|
||||
<article className="space-y-6 leading-relaxed relative">
|
||||
<HomeText />
|
||||
</article>
|
||||
<section className="my-8">
|
||||
<p>⚡ Powered with <Link href="https://www.cloudflare.com/" target="_blank" className="text-primary underline">Cloudflare</Link> ☁️</p>
|
||||
</section>
|
||||
<footer className="mt-20 text-center">
|
||||
<p>© <span className="text-sm">2025 Nomi Nonszy</span></p>
|
||||
<p className="text-sm">
|
||||
<Link href={"/terms"} className="text-primary underline">Terms</Link> and <Link href={"/privacy"} className="text-primary underline">Privacy</Link>
|
||||
</p>
|
||||
</footer>
|
||||
</FakeWindow>
|
||||
<WindowManagerProvider>
|
||||
<FakeWindow windowText="Homepage">
|
||||
<HomeWindows />
|
||||
<header className="text-center mb-8">
|
||||
<h1 className="font-bold text-3xl leading-normal">
|
||||
Nonszy Work<span className="text-primary">space</span>
|
||||
</h1>
|
||||
</header>
|
||||
<noscript>
|
||||
<LandingImage />
|
||||
</noscript>
|
||||
<NolaGlitchClientOnly />
|
||||
<Sosmed />
|
||||
<article className="space-y-6 leading-relaxed relative">
|
||||
<HomeText />
|
||||
</article>
|
||||
<section className="my-8">
|
||||
<p>⚡ Powered with <Link href="https://www.cloudflare.com/" target="_blank" className="text-primary underline">Cloudflare</Link> ☁️</p>
|
||||
</section>
|
||||
<footer className="mt-20 text-center">
|
||||
<p>© <span className="text-sm">2025 Nomi Nonszy</span></p>
|
||||
<p className="text-sm">
|
||||
<Link href={"/terms"} className="text-primary underline">Terms</Link> and <Link href={"/privacy"} className="text-primary underline">Privacy</Link>
|
||||
</p>
|
||||
</footer>
|
||||
</FakeWindow>
|
||||
</WindowManagerProvider>
|
||||
</main>
|
||||
</>);
|
||||
}
|
||||
|
@ -1,8 +1,23 @@
|
||||
'use client'
|
||||
|
||||
import { FakeWindow, useWindowManager } from "@/hooks/window-manager";
|
||||
import { Icon } from "@iconify/react";
|
||||
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 = ({
|
||||
windowText,
|
||||
@ -17,12 +32,15 @@ export const FakeRelativeWindow = ({
|
||||
withAnim?: boolean,
|
||||
children: React.ReactNode
|
||||
}) => {
|
||||
const [isMinimized, setMinimize] = useState(false);
|
||||
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
||||
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,
|
||||
@ -32,6 +50,32 @@ export const FakeRelativeWindow = ({
|
||||
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(() => {
|
||||
if (!withAnim) return;
|
||||
const node = popupRef.current;
|
||||
@ -56,16 +100,17 @@ export const FakeRelativeWindow = ({
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
const toggleMinimize = () => setMinimize(!isMinimized);
|
||||
const toggleMinimize = () => windowManager.toggleMinimize(windowName);
|
||||
const handleClose = () => windowManager.close(windowName);
|
||||
|
||||
const onMouseDown = (e: React.MouseEvent) => {
|
||||
if (!draggable) return;
|
||||
if (!draggable || !currentWindow) return;
|
||||
if (popupRef.current) {
|
||||
pos.current.dragging = true;
|
||||
pos.current.mouseX = e.clientX;
|
||||
pos.current.mouseY = e.clientY;
|
||||
pos.current.x = offset.x;
|
||||
pos.current.y = offset.y;
|
||||
pos.current.x = currentWindow.offset.x;
|
||||
pos.current.y = currentWindow.offset.y;
|
||||
document.body.style.userSelect = "none";
|
||||
}
|
||||
window.addEventListener("mousemove", onMouseMove);
|
||||
@ -80,16 +125,21 @@ export const FakeRelativeWindow = ({
|
||||
};
|
||||
|
||||
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;
|
||||
popupRef.current.style.transform = `translate(${newX}px, ${newY}px)`;
|
||||
setOffset({ x: newX, y: newY });
|
||||
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}>
|
||||
@ -98,14 +148,24 @@ export const FakeRelativeWindow = ({
|
||||
onMouseDown={onMouseDown}
|
||||
>
|
||||
<div className="ms-1 pointer-events-none">
|
||||
{windowText}
|
||||
{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"><Icon icon="lucide:x"/></button>
|
||||
<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", 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)}>
|
||||
{children}
|
||||
</div>
|
||||
|
@ -8,10 +8,8 @@ export const FloatingLabel = ({
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<PopoverText text={placeholder}>
|
||||
{children}
|
||||
</PopoverText>
|
||||
</div>
|
||||
<PopoverText text={placeholder}>
|
||||
{children}
|
||||
</PopoverText>
|
||||
);
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
import { Icon } from "@iconify/react";
|
||||
import { FloatingLabel } from "@/components/floating-label";
|
||||
import { FakeRelativeWindow } from "./client-windows";
|
||||
import { FakeRelativeWindow, RestoreWindowsButton } from "./client-windows";
|
||||
import Image from "next/image"
|
||||
import Link from "next/link";
|
||||
|
||||
@ -16,12 +16,17 @@ export const FakeWindow = ({
|
||||
<div className="ms-1 pointer-events-none">
|
||||
{windowText}
|
||||
</div>
|
||||
<FloatingLabel placeholder="Useless button btw">
|
||||
<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 className="inline-flex">
|
||||
<RestoreWindowsButton className="border-outset px-2 text-xs me-2">
|
||||
Restore Windows
|
||||
</RestoreWindowsButton>
|
||||
<FloatingLabel placeholder="Useless button btw">
|
||||
<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 className="m-1 md:border border-primary">
|
||||
@ -34,23 +39,6 @@ export const FakeWindow = ({
|
||||
|
||||
export const HomeWindows = () => (
|
||||
<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
|
||||
windowText="nola.png"
|
||||
className="-right-[73%] top-[1300px] z-20"
|
||||
|
@ -1,9 +1,86 @@
|
||||
interface iFakeWindow {
|
||||
'use client'
|
||||
|
||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||
|
||||
export interface FakeWindow {
|
||||
name: string;
|
||||
minimized: boolean;
|
||||
closed: boolean;
|
||||
offset: {
|
||||
x: number
|
||||
y: number
|
||||
x: 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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user