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>
49 lines
1.5 KiB
TypeScript
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);
|