ui update!

This commit is contained in:
dvorakmortonxd 2026-09-22 11:41:05 +02:00
commit d2f99c87b9
16 changed files with 3085 additions and 592 deletions

View file

@ -0,0 +1,274 @@
"use client";
import { useRef, useState } from "react";
import { APPS, APP_BY_ID } from "./apps";
import WinWindow, { type WinState } from "./WinWindow";
import Clock from "./Clock";
import { useTheme } from "./useTheme";
import { WALLPAPER } from "../data";
const TASKBAR_HEIGHT = 40;
export default function WindowsDesktop() {
const { theme, toggle } = useTheme();
const [wins, setWins] = useState<WinState[]>(() => [
{
id: 1,
appId: "welcome",
x: 110,
y: 64,
w: 620,
h: 430,
z: 10,
minimized: false,
maximized: false,
},
]);
const [activeId, setActiveId] = useState<number | null>(1);
const [startOpen, setStartOpen] = useState(false);
const [selectedIcon, setSelectedIcon] = useState<string | null>(null);
const idRef = useRef(2);
const zRef = useRef(11);
const spawnRef = useRef(1);
const focus = (id: number) => {
setActiveId(id);
setWins((prev) =>
prev.map((w) => (w.id === id ? { ...w, z: ++zRef.current } : w))
);
};
const openApp = (appId: string) => {
setStartOpen(false);
const existing = wins.find((w) => w.appId === appId);
if (existing) {
setWins((prev) =>
prev.map((w) =>
w.id === existing.id ? { ...w, minimized: false, z: ++zRef.current } : w
)
);
setActiveId(existing.id);
return;
}
const app = APP_BY_ID[appId];
if (!app) return;
const n = spawnRef.current++;
const id = idRef.current++;
const maxX = Math.max(20, (typeof window !== "undefined" ? window.innerWidth : 1280) - app.width - 20);
const maxY = Math.max(20, (typeof window !== "undefined" ? window.innerHeight : 800) - TASKBAR_HEIGHT - app.height - 20);
const x = Math.min(maxX, 90 + (n % 8) * 30);
const y = Math.min(maxY, 56 + (n % 8) * 28);
setWins((prev) => [
...prev,
{
id,
appId,
x,
y,
w: app.width,
h: app.height,
z: ++zRef.current,
minimized: false,
maximized: false,
},
]);
setActiveId(id);
};
const closeWin = (id: number) =>
setWins((prev) => prev.filter((w) => w.id !== id));
const minimizeWin = (id: number) =>
setWins((prev) => prev.map((w) => (w.id === id ? { ...w, minimized: true } : w)));
const toggleMax = (id: number) =>
setWins((prev) =>
prev.map((w) => (w.id === id ? { ...w, maximized: !w.maximized } : w))
);
const moveWin = (id: number, x: number, y: number) => {
const maxX =
(typeof window !== "undefined" ? window.innerWidth : 1280) - 120;
const maxY =
(typeof window !== "undefined" ? window.innerHeight : 800) - TASKBAR_HEIGHT - 20;
setWins((prev) =>
prev.map((w) =>
w.id === id
? { ...w, x: Math.max(-w.w + 120, Math.min(maxX, x)), y: Math.max(0, Math.min(maxY, y)) }
: w
)
);
};
const taskbarClick = (w: WinState) => {
if (w.minimized) {
setWins((prev) =>
prev.map((x) => (x.id === w.id ? { ...x, minimized: false, z: ++zRef.current } : x))
);
setActiveId(w.id);
} else if (activeId === w.id) {
minimizeWin(w.id);
} else {
focus(w.id);
}
};
const wallpaper = `url('${WALLPAPER}')`;
return (
<div
className="win-desktop"
style={{ backgroundImage: wallpaper }}
onPointerDown={(e) => {
if (e.target === e.currentTarget) {
setStartOpen(false);
setSelectedIcon(null);
}
}}
>
<div className="win-wallpaper-tint" aria-hidden="true" />
{/* Desktop icons */}
<div className="win-icons" onPointerDown={(e) => e.stopPropagation()}>
{APPS.map((app) => (
<button
key={app.id}
type="button"
className={"win-icon" + (selectedIcon === app.id ? " selected" : "")}
onClick={() => setSelectedIcon(app.id)}
onDoubleClick={() => openApp(app.id)}
title={`Open ${app.title}`}
>
<span className="win-icon-glyph" style={{ background: app.color }}>
{app.icon}
</span>
<span className="win-icon-label">{app.title}</span>
</button>
))}
</div>
{/* Windows */}
<div className="win-layer">
{wins.map((w) => (
<WinWindow
key={w.id}
win={w}
app={APP_BY_ID[w.appId]}
active={activeId === w.id}
onFocus={() => focus(w.id)}
onClose={() => closeWin(w.id)}
onMinimize={() => minimizeWin(w.id)}
onToggleMaximize={() => toggleMax(w.id)}
onMove={(x, y) => moveWin(w.id, x, y)}
/>
))}
</div>
{/* Start menu */}
{startOpen && (
<div className="win-startmenu" onPointerDown={(e) => e.stopPropagation()}>
<div className="win-startmenu-user">
<span className="win-startmenu-avatar">🧑‍💻</span>
<span>Martin Dvořák</span>
</div>
<div className="win-startmenu-list">
{APPS.map((app) => (
<button
key={app.id}
type="button"
className="win-startmenu-item"
onClick={() => openApp(app.id)}
>
<span className="win-startmenu-icon" style={{ background: app.color }}>
{app.icon}
</span>
{app.title}
</button>
))}
</div>
<div className="win-startmenu-foot">
<button
type="button"
className="win-startmenu-power"
onClick={() => {
toggle();
setStartOpen(false);
}}
>
⏻ Switch to {theme === "skeuo" ? "Terminal" : "Aero"}
</button>
</div>
</div>
)}
{/* Taskbar */}
<div className="win-taskbar" onPointerDown={(e) => e.stopPropagation()}>
<button
type="button"
className={"win-start" + (startOpen ? " open" : "")}
onClick={() => setStartOpen((v) => !v)}
aria-label="Start"
>
<span className="win-start-orb">⊞</span>
</button>
<div className="win-tb-buttons">
{wins.map((w) => {
const app = APP_BY_ID[w.appId];
return (
<button
key={w.id}
type="button"
className={
"win-tb-btn" +
(activeId === w.id && !w.minimized ? " active" : "") +
(w.minimized ? " minimized" : "")
}
onClick={() => taskbarClick(w)}
title={app.title}
>
<span className="win-tb-btn-icon" style={{ background: app.color }}>
{app.icon}
</span>
<span className="win-tb-btn-label">{app.title}</span>
</button>
);
})}
</div>
<div className="win-tray">
<form
className="win-search"
action="https://search.dvorakmortonxd.dev/search"
method="GET"
target="_blank"
rel="noopener noreferrer"
>
<span className="win-search-glyph" aria-hidden="true">
⌕
</span>
<input
type="text"
name="q"
placeholder="Search the web..."
aria-label="Search the web"
/>
</form>
<button
type="button"
className="win-theme-toggle"
onClick={toggle}
title={`Switch to ${theme === "skeuo" ? "terminal" : "Aero"} theme`}
>
<span aria-hidden="true">{theme === "skeuo" ? "▚" : "❖"}</span>
<span>{theme === "skeuo" ? "Terminal" : "Aero"}</span>
</button>
<Clock className="win-clock" />
<button type="button" className="win-showdesktop" aria-label="Show desktop" onClick={() => setWins((prev) => prev.map((w) => ({ ...w, minimized: true })))} />
</div>
</div>
</div>
);
}