feat(map3d): stabilize globe overlays and hover-highlight sync
This commit is contained in:
부모
b944887430
커밋
05b0c6b881
@ -188,12 +188,17 @@ export function computeFleetCircles(vessels: DerivedLegacyVessel[]): FleetCircle
|
||||
const out: FleetCircle[] = [];
|
||||
for (const [ownerKey, vs] of groups.entries()) {
|
||||
if (vs.length < 3) continue;
|
||||
const ownerLabel =
|
||||
vs.find((v) => v.ownerCn)?.ownerCn ??
|
||||
vs.find((v) => v.ownerRoman)?.ownerRoman ??
|
||||
ownerKey;
|
||||
const lon = vs.reduce((sum, v) => sum + v.lon, 0) / vs.length;
|
||||
const lat = vs.reduce((sum, v) => sum + v.lat, 0) / vs.length;
|
||||
let radiusNm = 0;
|
||||
for (const v of vs) radiusNm = Math.max(radiusNm, haversineNm(lat, lon, v.lat, v.lon));
|
||||
out.push({
|
||||
ownerKey,
|
||||
ownerLabel,
|
||||
center: [lon, lat],
|
||||
radiusNm: Math.max(0.2, radiusNm),
|
||||
count: vs.length,
|
||||
|
||||
@ -56,6 +56,7 @@ export type FcLink = {
|
||||
|
||||
export type FleetCircle = {
|
||||
ownerKey: string;
|
||||
ownerLabel: string;
|
||||
center: [number, number];
|
||||
radiusNm: number;
|
||||
count: number;
|
||||
@ -71,4 +72,3 @@ export type LegacyAlarm = {
|
||||
text: string;
|
||||
relatedMmsi: number[];
|
||||
};
|
||||
|
||||
|
||||
@ -610,6 +610,10 @@ export function DashboardPage() {
|
||||
fleetCircles={fleetCirclesForMap}
|
||||
fleetFocus={fleetFocus}
|
||||
onProjectionLoadingChange={setIsProjectionLoading}
|
||||
onHoverMmsi={(mmsis) => setHoveredMmsiSet(setUniqueSorted(mmsis))}
|
||||
onClearMmsiHover={() => setHoveredMmsiSet((prev) => (prev.length === 0 ? prev : []))}
|
||||
onHoverPair={(pairMmsis) => setHoveredPairMmsiSet(setUniqueSorted(pairMmsis))}
|
||||
onClearPairHover={() => setHoveredPairMmsiSet([])}
|
||||
onHoverFleet={(ownerKey, fleetMmsis) => {
|
||||
setHoveredFleetOwnerKey(ownerKey);
|
||||
setHoveredFleetMmsiSet(setSortedIfChanged(fleetMmsis));
|
||||
|
||||
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
Load Diff
@ -1,17 +1,38 @@
|
||||
import { VESSEL_TYPES } from "../../entities/vessel/model/meta";
|
||||
import type { DerivedLegacyVessel } from "../../features/legacyDashboard/model/types";
|
||||
import type { MouseEvent } from "react";
|
||||
|
||||
type Props = {
|
||||
vessels: DerivedLegacyVessel[];
|
||||
selectedMmsi: number | null;
|
||||
highlightedMmsiSet?: number[];
|
||||
onToggleHighlightMmsi: (mmsi: number) => void;
|
||||
onSelectMmsi: (mmsi: number) => void;
|
||||
onHoverMmsi?: (mmsi: number) => void;
|
||||
onClearHover?: () => void;
|
||||
};
|
||||
|
||||
function isFiniteNumber(x: unknown): x is number {
|
||||
export function VesselList({
|
||||
vessels,
|
||||
selectedMmsi,
|
||||
highlightedMmsiSet = [],
|
||||
onToggleHighlightMmsi,
|
||||
onSelectMmsi,
|
||||
onHoverMmsi,
|
||||
onClearHover,
|
||||
}: Props) {
|
||||
const handlePrimaryAction = (e: MouseEvent, mmsi: number) => {
|
||||
if (e.shiftKey || e.ctrlKey || e.metaKey) {
|
||||
onToggleHighlightMmsi(mmsi);
|
||||
return;
|
||||
}
|
||||
onSelectMmsi(mmsi);
|
||||
};
|
||||
|
||||
function isFiniteNumber(x: unknown): x is number {
|
||||
return typeof x === "number" && Number.isFinite(x);
|
||||
}
|
||||
|
||||
export function VesselList({ vessels, selectedMmsi, onSelectMmsi }: Props) {
|
||||
const sorted = vessels
|
||||
.slice()
|
||||
.sort((a, b) => (isFiniteNumber(b.sog) ? b.sog : -1) - (isFiniteNumber(a.sog) ? a.sog : -1))
|
||||
@ -29,13 +50,15 @@ export function VesselList({ vessels, selectedMmsi, onSelectMmsi }: Props) {
|
||||
const speedColor = inRange ? "#22C55E" : (v.sog ?? 0) > 5 ? "#3B82F6" : "var(--muted)";
|
||||
const hasPair = v.pairPermitNo ? "⛓" : "";
|
||||
const sel = selectedMmsi === v.mmsi;
|
||||
const hl = highlightedMmsiSet.includes(v.mmsi);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={v.mmsi}
|
||||
className="vi"
|
||||
onClick={() => onSelectMmsi(v.mmsi)}
|
||||
style={sel ? { background: "rgba(59,130,246,.12)", border: "1px solid rgba(59,130,246,.45)" } : undefined}
|
||||
className={`vi ${sel ? "sel" : ""} ${hl ? "hl" : ""}`}
|
||||
onClick={(e) => handlePrimaryAction(e, v.mmsi)}
|
||||
onMouseEnter={() => onHoverMmsi?.(v.mmsi)}
|
||||
onMouseLeave={() => onClearHover?.()}
|
||||
title={v.name}
|
||||
>
|
||||
<div className="dot" style={{ background: meta.color, boxShadow: v.state.isFishing ? `0 0 3px ${meta.color}` : undefined }} />
|
||||
@ -56,4 +79,3 @@ export function VesselList({ vessels, selectedMmsi, onSelectMmsi }: Props) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user