02d73ddf81
Add trap overlays with session state, material legend editor/panel, and a dedicated scene editor window wired through IPC and project persistence. Co-authored-by: Cursor <cursoragent@cursor.com>
76 lines
2.3 KiB
TypeScript
76 lines
2.3 KiB
TypeScript
import React, { useRef } from 'react';
|
||
|
||
import type { MaterialLegend, MaterialsOverlayLayout } from '../../../shared/types';
|
||
import { DEFAULT_MATERIALS_OVERLAY_LAYOUT } from '../../../shared/types';
|
||
import { useSceneOverlayView } from '../sceneOverlay/SceneOverlayViewContext';
|
||
|
||
import styles from './MaterialLegendPanel.module.css';
|
||
|
||
type Props = {
|
||
legend: MaterialLegend;
|
||
layout: MaterialsOverlayLayout;
|
||
editable?: boolean;
|
||
onLayoutChange?: (layout: MaterialsOverlayLayout) => void;
|
||
};
|
||
|
||
export function MaterialLegendPanel({
|
||
legend,
|
||
layout,
|
||
editable = false,
|
||
onLayoutChange,
|
||
}: Props) {
|
||
const host = useSceneOverlayView();
|
||
const view = host?.view ?? { w: 1, h: 1 };
|
||
const dragRef = useRef<{ startX: number; startY: number; origin: MaterialsOverlayLayout } | null>(
|
||
null,
|
||
);
|
||
const effective = layout ?? DEFAULT_MATERIALS_OVERLAY_LAYOUT;
|
||
const w = Math.max(180, Math.min(view.w * 0.36, 340) * effective.scale);
|
||
const left = effective.cx * view.w - w / 2;
|
||
const top = effective.cy * view.h - 40;
|
||
|
||
if (!legend.enabled || legend.items.length === 0) return null;
|
||
|
||
return (
|
||
<div
|
||
className={styles.panel}
|
||
style={{ left, top, width: w }}
|
||
onPointerDown={(e) => {
|
||
if (!editable || !onLayoutChange) return;
|
||
e.stopPropagation();
|
||
(e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId);
|
||
dragRef.current = {
|
||
startX: e.clientX,
|
||
startY: e.clientY,
|
||
origin: { ...effective },
|
||
};
|
||
}}
|
||
onPointerMove={(e) => {
|
||
const d = dragRef.current;
|
||
if (!d || !onLayoutChange) return;
|
||
const dx = (e.clientX - d.startX) / Math.max(1, view.w);
|
||
const dy = (e.clientY - d.startY) / Math.max(1, view.h);
|
||
onLayoutChange({
|
||
...d.origin,
|
||
cx: d.origin.cx + dx,
|
||
cy: d.origin.cy + dy,
|
||
});
|
||
}}
|
||
onPointerUp={() => {
|
||
dragRef.current = null;
|
||
}}
|
||
>
|
||
<div className={styles.title}>Легенда</div>
|
||
{legend.items
|
||
.slice()
|
||
.sort((a, b) => a.number - b.number)
|
||
.map((item) => (
|
||
<div key={item.id} className={styles.item}>
|
||
<div className={styles.num}>{item.number}</div>
|
||
<div className={styles.text}>{item.text || '—'}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|