feat(v2): Phase 1 — map-first layout matching v1
Rebuilds the v1 map-centric experience in React: Layout: - 400px sidebar on left, interactive map on right (flex, 100vh) - Exact same proportions and dark theme as v1 Sidebar (top→bottom): - Header with active player count + Dashboard toggle button - Server status dot (Coldeve online/offline with pulse) - Aggregate counters: Rares (gold), Server KPH (blue glow), Kills (red) - 6 sort buttons (Name, KPH, S.Kills, S.Rares, T.Kills, KPR) - Player name filter - Scrollable player list with per-row: - Name + coordinates - HP/Stamina/Mana vital bars (red/orange/blue gradients) - Session kills, total kills, KPH - Session rares, total rares, VTank meta state pill - Online time, deaths, prismatic tapers - Color-coded left border per player Map: - dereth.png with CSS transform pan (drag) + zoom (wheel, 1.1x factor, max 20x) - Player dots (6px circles, color-matched to sidebar) - Hover tooltip (name, coords, kph, kills) - World coordinate display at cursor position - Fit-to-window on first load View toggle: Map View ↔ Dashboard with localStorage persistence. All v1 CSS ported under ml-* prefix, scoped via map-layout.css. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3791c01bf3
commit
2c4b8d3afb
16 changed files with 995 additions and 151 deletions
47
frontend/src/contexts/MapTransformContext.tsx
Normal file
47
frontend/src/contexts/MapTransformContext.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import React, { createContext, useContext, useReducer, type Dispatch } from 'react';
|
||||
|
||||
interface MapTransform {
|
||||
scale: number;
|
||||
offX: number;
|
||||
offY: number;
|
||||
}
|
||||
|
||||
type Action =
|
||||
| { type: 'SET'; scale: number; offX: number; offY: number }
|
||||
| { type: 'ZOOM'; factor: number; cx: number; cy: number }
|
||||
| { type: 'PAN'; dx: number; dy: number };
|
||||
|
||||
const MAX_ZOOM = 20;
|
||||
const MIN_ZOOM = 0.3;
|
||||
|
||||
function reducer(state: MapTransform, action: Action): MapTransform {
|
||||
switch (action.type) {
|
||||
case 'SET':
|
||||
return { scale: action.scale, offX: action.offX, offY: action.offY };
|
||||
case 'ZOOM': {
|
||||
const newScale = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, state.scale * action.factor));
|
||||
const ratio = newScale / state.scale;
|
||||
return {
|
||||
scale: newScale,
|
||||
offX: action.cx - (action.cx - state.offX) * ratio,
|
||||
offY: action.cy - (action.cy - state.offY) * ratio,
|
||||
};
|
||||
}
|
||||
case 'PAN':
|
||||
return { ...state, offX: state.offX + action.dx, offY: state.offY + action.dy };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
const Ctx = createContext<{ transform: MapTransform; dispatch: Dispatch<Action> }>({
|
||||
transform: { scale: 1, offX: 0, offY: 0 },
|
||||
dispatch: () => {},
|
||||
});
|
||||
|
||||
export const MapTransformProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [transform, dispatch] = useReducer(reducer, { scale: 1, offX: 0, offY: 0 });
|
||||
return <Ctx.Provider value={{ transform, dispatch }}>{children}</Ctx.Provider>;
|
||||
};
|
||||
|
||||
export const useMapTransform = () => useContext(Ctx);
|
||||
Loading…
Add table
Add a link
Reference in a new issue