feat(v2): remove old dashboard, add vitae + resizable windows

- Removed old Recharts dashboard view entirely (no more viewMode
  toggle, DashboardView lazy import, Ctrl+D shortcut)
- Recharts chunk eliminated from build — bundle size reduced
- Player Dashboard window: added Vitae column (red when > 0%)
- ALL windows now resizable: drag bottom-right corner handle
  (min 300×200px). Subtle diagonal line grip indicator.
- Sidebar: removed 📊 Dashboard toggle link, removed broken
  /quest-status.html external link (replaced by 📜 Quests window)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-14 15:33:07 +02:00
parent 938421999a
commit a5bd659876
37 changed files with 168 additions and 178 deletions

View file

@ -1,4 +1,4 @@
import React, { useRef, useCallback, useEffect } from 'react';
import React, { useRef, useCallback, useEffect, useState } from 'react';
import { useWindowManager } from '../../contexts/WindowManagerContext';
interface Props {
@ -14,7 +14,9 @@ export const DraggableWindow: React.FC<Props> = ({ id, title, zIndex, width = 70
const { closeWindow, bringToFront } = useWindowManager();
const winRef = useRef<HTMLDivElement>(null);
const dragRef = useRef({ dragging: false, sx: 0, sy: 0, ox: 0, oy: 0 });
const resizeRef = useRef({ resizing: false, sx: 0, sy: 0, sw: 0, sh: 0 });
const posRef = useRef({ x: 420, y: 10 + Math.random() * 40 });
const [size, setSize] = useState({ w: width, h: height });
const onHeaderDown = useCallback((e: React.MouseEvent) => {
e.preventDefault();
@ -24,16 +26,34 @@ export const DraggableWindow: React.FC<Props> = ({ id, title, zIndex, width = 70
dragRef.current = { dragging: true, sx: e.clientX, sy: e.clientY, ox: rect.left, oy: rect.top };
}, [id, bringToFront]);
const onResizeDown = useCallback((e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
resizeRef.current = { resizing: true, sx: e.clientX, sy: e.clientY, sw: size.w, sh: size.h };
}, [size.w, size.h]);
useEffect(() => {
const onMove = (e: MouseEvent) => {
// Drag
const d = dragRef.current;
if (!d.dragging || !winRef.current) return;
posRef.current.x = d.ox + (e.clientX - d.sx);
posRef.current.y = d.oy + (e.clientY - d.sy);
winRef.current.style.left = `${posRef.current.x}px`;
winRef.current.style.top = `${posRef.current.y}px`;
if (d.dragging && winRef.current) {
posRef.current.x = d.ox + (e.clientX - d.sx);
posRef.current.y = d.oy + (e.clientY - d.sy);
winRef.current.style.left = `${posRef.current.x}px`;
winRef.current.style.top = `${posRef.current.y}px`;
}
// Resize
const r = resizeRef.current;
if (r.resizing) {
const newW = Math.max(300, r.sw + (e.clientX - r.sx));
const newH = Math.max(200, r.sh + (e.clientY - r.sy));
setSize({ w: newW, h: newH });
}
};
const onUp = () => {
dragRef.current.dragging = false;
resizeRef.current.resizing = false;
};
const onUp = () => { dragRef.current.dragging = false; };
window.addEventListener('mousemove', onMove);
window.addEventListener('mouseup', onUp);
return () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
@ -43,7 +63,7 @@ export const DraggableWindow: React.FC<Props> = ({ id, title, zIndex, width = 70
<div
ref={winRef}
className="ml-window"
style={{ zIndex, width, height, left: posRef.current.x, top: posRef.current.y }}
style={{ zIndex, width: size.w, height: size.h, left: posRef.current.x, top: posRef.current.y }}
onMouseDown={() => bringToFront(id)}
>
<div className="ml-window-header" onMouseDown={onHeaderDown}>
@ -53,6 +73,8 @@ export const DraggableWindow: React.FC<Props> = ({ id, title, zIndex, width = 70
<div className="ml-window-content">
{children}
</div>
{/* Resize handle */}
<div className="ml-window-resize" onMouseDown={onResizeDown} />
</div>
);
};

View file

@ -26,6 +26,7 @@ export const PlayerDashboardWindow: React.FC<Props> = ({ id, zIndex, characters
state: t.vt_state ?? 'idle',
tapers: parseInt(t.prismatic_taper_count as string) || 0,
hp: c.vitals?.health_percentage ?? 0,
vitae: c.vitals?.vitae ?? 0,
};
});
@ -74,6 +75,7 @@ export const PlayerDashboardWindow: React.FC<Props> = ({ id, zIndex, characters
<th style={{ ...thStyle('deaths'), textAlign: 'right' }} onClick={() => toggleSort('deaths')}>Deaths{arrow('deaths')}</th>
<th style={{ ...thStyle('uptime'), textAlign: 'right' }} onClick={() => toggleSort('uptime')}>Uptime{arrow('uptime')}</th>
<th style={{ textAlign: 'right', padding: '4px 6px', color: '#888', fontSize: '0.65rem', fontWeight: 600, borderBottom: '1px solid #444' }}>HP%</th>
<th style={{ textAlign: 'right', padding: '4px 6px', color: '#888', fontSize: '0.65rem', fontWeight: 600, borderBottom: '1px solid #444' }}>Vitae</th>
<th style={{ textAlign: 'right', padding: '4px 6px', color: '#888', fontSize: '0.65rem', fontWeight: 600, borderBottom: '1px solid #444' }}>Tapers</th>
</tr>
</thead>
@ -98,6 +100,8 @@ export const PlayerDashboardWindow: React.FC<Props> = ({ id, zIndex, characters
<td style={{ textAlign: 'right', padding: '3px 6px', color: '#888', fontVariantNumeric: 'tabular-nums' }}>{p.uptime}</td>
<td style={{ textAlign: 'right', padding: '3px 6px', fontVariantNumeric: 'tabular-nums',
color: p.hp > 80 ? '#4c4' : p.hp > 40 ? '#ca0' : '#c44' }}>{p.hp.toFixed(0)}%</td>
<td style={{ textAlign: 'right', padding: '3px 6px', fontVariantNumeric: 'tabular-nums',
color: p.vitae > 0 ? '#f66' : '#333' }}>{p.vitae > 0 ? `${p.vitae}%` : ''}</td>
<td style={{ textAlign: 'right', padding: '3px 6px', color: '#888', fontVariantNumeric: 'tabular-nums' }}>{p.tapers.toLocaleString()}</td>
</tr>
);