MosswartOverlord/frontend/src/contexts/WindowManagerContext.tsx
Erik de7b547349 feat(v2): Phases 2-6 — trails, heatmap, portals, windows, effects
Phase 2 — Map overlays:
- TrailsSVG: SVG polylines per character from /trails, polled 2s
- HeatmapCanvas: canvas radial gradients from /spawns/heatmap
- PortalMarkers: emoji markers from /portals
- Sidebar toggles for heatmap and portals

Phase 3 — Draggable windows:
- WindowManagerContext: z-index stack for open windows
- DraggableWindow: generic shell with drag-header, close btn, z-stack
- ChatWindow: color-coded messages + input form (1000 msg buffer)
- CharacterWindow: combat stats with monster damage table
- InventoryWindow: item table with material/set/AL/dmg/workmanship
- WindowRenderer: reads context, renders all open windows
- Action buttons (Chat/Stats/Inv/Char/Radar) now open windows

Phase 4 — Window types share same DraggableWindow shell with
character-specific content. Combat stats and inventory via API.

Phase 5 — Effects:
- RareNotification: slide-in/slide-out banner with gold border
- Fireworks: 30-particle explosion with CSS custom property animation
- Notification queue with 6s display + 0.5s exit animation

Phase 6 — Polish:
- Window header uses modern blue gradient (not solid purple)
- Chat uses monospace font
- All overlay layers properly stacked (heatmap → trails → dots → portals)
- Mobile: sidebar stacks above map at 768px breakpoint
- Chat messages tracked per-character in useLiveData

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 15:58:58 +02:00

49 lines
1.5 KiB
TypeScript

import React, { createContext, useContext, useState, useCallback, useRef } from 'react';
interface WindowState {
id: string;
title: string;
charName?: string;
zIndex: number;
}
interface WindowManagerValue {
windows: WindowState[];
openWindow: (id: string, title: string, charName?: string) => void;
closeWindow: (id: string) => void;
bringToFront: (id: string) => void;
}
const Ctx = createContext<WindowManagerValue>({
windows: [],
openWindow: () => {},
closeWindow: () => {},
bringToFront: () => {},
});
export const WindowManagerProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [windows, setWindows] = useState<WindowState[]>([]);
const zRef = useRef(10000);
const openWindow = useCallback((id: string, title: string, charName?: string) => {
setWindows(prev => {
const existing = prev.find(w => w.id === id);
if (existing) {
return prev.map(w => w.id === id ? { ...w, zIndex: ++zRef.current } : w);
}
return [...prev, { id, title, charName, zIndex: ++zRef.current }];
});
}, []);
const closeWindow = useCallback((id: string) => {
setWindows(prev => prev.filter(w => w.id !== id));
}, []);
const bringToFront = useCallback((id: string) => {
setWindows(prev => prev.map(w => w.id === id ? { ...w, zIndex: ++zRef.current } : w));
}, []);
return <Ctx.Provider value={{ windows, openWindow, closeWindow, bringToFront }}>{children}</Ctx.Provider>;
};
export const useWindowManager = () => useContext(Ctx);