mobile: iOS-style widget + animations, 2-click desktop icons, new wallpapers
Mobile: - iOS-style open/close animation for app views (grows from the tapped icon, always slides straight down out of view and shrinks on close) - digital 2x1 clock widget in an iOS-style frosted-glass panel - new phone wallpaper (phonewallpaper.jpg) - larger notch Desktop: - desktop icons and shortcuts now launch on the second click - open/close/launcher animations - windows keep their state when toggling Gruvbox/Desktop themes, and the Welcome window only appears on the first page load Other: - update wallpaper credits to wallpapercave - allow LAN hosts in dev (allowedDevOrigins) so phones can load the dev bundle instead of getting a 403 and an inert page
This commit is contained in:
parent
784334d4be
commit
6b645ac636
11 changed files with 397 additions and 159 deletions
|
|
@ -54,6 +54,7 @@ export default function WindowsDesktop() {
|
|||
const [activeId, setActiveId] = useState<number | null>(1);
|
||||
const [startOpen, setStartOpen] = useState(false);
|
||||
const [selectedIcon, setSelectedIcon] = useState<string | null>(null);
|
||||
const [closingIds, setClosingIds] = useState<number[]>([]);
|
||||
|
||||
const idRef = useRef(2);
|
||||
const zRef = useRef(11);
|
||||
|
|
@ -65,6 +66,17 @@ export default function WindowsDesktop() {
|
|||
);
|
||||
};
|
||||
|
||||
// Desktop icons behave like a real desktop: the first click selects the
|
||||
// icon, a second click on the same icon launches it.
|
||||
const activateIcon = (id: string, action: () => void) => {
|
||||
if (selectedIcon === id) {
|
||||
setSelectedIcon(null);
|
||||
action();
|
||||
} else {
|
||||
setSelectedIcon(id);
|
||||
}
|
||||
};
|
||||
|
||||
const openApp = (appId: string) => {
|
||||
setStartOpen(false);
|
||||
const existing = wins.find((w) => w.appId === appId);
|
||||
|
|
@ -105,8 +117,13 @@ export default function WindowsDesktop() {
|
|||
setActiveId(id);
|
||||
};
|
||||
|
||||
// Animate the window out first, then drop it from state.
|
||||
const closeWin = (id: number) =>
|
||||
setClosingIds((prev) => (prev.includes(id) ? prev : [...prev, id]));
|
||||
const finishClose = (id: number) => {
|
||||
setWins((prev) => prev.filter((w) => w.id !== id));
|
||||
setClosingIds((prev) => prev.filter((x) => x !== id));
|
||||
};
|
||||
const minimizeWin = (id: number) =>
|
||||
setWins((prev) => prev.map((w) => (w.id === id ? { ...w, minimized: true } : w)));
|
||||
const toggleMax = (id: number) =>
|
||||
|
|
@ -165,10 +182,7 @@ export default function WindowsDesktop() {
|
|||
key={app.id}
|
||||
type="button"
|
||||
className={"win-icon" + (selectedIcon === app.id ? " selected" : "")}
|
||||
onClick={() => {
|
||||
setSelectedIcon(app.id);
|
||||
openApp(app.id);
|
||||
}}
|
||||
onClick={() => activateIcon(app.id, () => openApp(app.id))}
|
||||
title={`Open ${app.title}`}
|
||||
>
|
||||
<span className="win-icon-glyph" style={{ background: app.color }}>
|
||||
|
|
@ -184,32 +198,24 @@ export default function WindowsDesktop() {
|
|||
className="win-icons win-icons-tr"
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
{SERVICE_SHORTCUTS.map((s) =>
|
||||
s.appId ? (
|
||||
<button
|
||||
key={s.id}
|
||||
type="button"
|
||||
className="win-icon"
|
||||
title={s.label}
|
||||
onClick={() => openApp(s.appId as string)}
|
||||
>
|
||||
<span className="win-icon-glyph">{s.icon}</span>
|
||||
<span className="win-icon-label">{s.label}</span>
|
||||
</button>
|
||||
) : (
|
||||
<a
|
||||
key={s.id}
|
||||
href={s.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="win-icon"
|
||||
title={s.label}
|
||||
>
|
||||
<span className="win-icon-glyph">{s.icon}</span>
|
||||
<span className="win-icon-label">{s.label}</span>
|
||||
</a>
|
||||
)
|
||||
)}
|
||||
{SERVICE_SHORTCUTS.map((s) => (
|
||||
<button
|
||||
key={s.id}
|
||||
type="button"
|
||||
className={"win-icon" + (selectedIcon === s.id ? " selected" : "")}
|
||||
title={s.label}
|
||||
onClick={() =>
|
||||
activateIcon(s.id, () =>
|
||||
s.appId
|
||||
? openApp(s.appId)
|
||||
: window.open(s.url, "_blank", "noopener,noreferrer")
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="win-icon-glyph">{s.icon}</span>
|
||||
<span className="win-icon-label">{s.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Friends - bottom right */}
|
||||
|
|
@ -218,19 +224,22 @@ export default function WindowsDesktop() {
|
|||
onPointerDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
{FRIEND_SHORTCUTS.map((f) => (
|
||||
<a
|
||||
<button
|
||||
key={f.id}
|
||||
href={f.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="win-icon"
|
||||
type="button"
|
||||
className={"win-icon" + (selectedIcon === f.id ? " selected" : "")}
|
||||
title={f.label}
|
||||
onClick={() =>
|
||||
activateIcon(f.id, () =>
|
||||
window.open(f.url, "_blank", "noopener,noreferrer")
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="win-icon-glyph">
|
||||
<Image src={f.img} alt={f.label} width={38} height={38} className="win-icon-img" />
|
||||
</span>
|
||||
<span className="win-icon-label">{f.label}</span>
|
||||
</a>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
|
@ -242,8 +251,10 @@ export default function WindowsDesktop() {
|
|||
win={w}
|
||||
app={APP_BY_ID[w.appId]}
|
||||
active={activeId === w.id}
|
||||
closing={closingIds.includes(w.id)}
|
||||
onFocus={() => focus(w.id)}
|
||||
onClose={() => closeWin(w.id)}
|
||||
onCloseAnimationEnd={() => finishClose(w.id)}
|
||||
onMinimize={() => minimizeWin(w.id)}
|
||||
onToggleMaximize={() => toggleMax(w.id)}
|
||||
onMove={(x, y) => moveWin(w.id, x, y)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue