ui update!
This commit is contained in:
parent
cadbe8caa9
commit
d2f99c87b9
16 changed files with 3085 additions and 592 deletions
274
src/app/components/WindowsDesktop.tsx
Normal file
274
src/app/components/WindowsDesktop.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue