"use client"; import { useEffect, useMemo, useState } from "react"; import { ChevronDown, Crosshair, LocateFixed, RotateCcw, X } from "lucide-react"; import { cn } from "@/lib/utils"; import { LAYER_GROUP_GEOMETRIES, type LayerGroupId, type LayerGroupStylePatch } from "../map/layer-group-style"; import { getNumericFeatureProperties } from "../map/value-label"; import type { FeatureTarget, WorkbenchMapCommands, WorkbenchMapControllerState } from "../map/workbench-map-controller"; import { WATER_NETWORK_SOURCE_IDS, type WaterNetworkSourceId } from "../map/sources"; import type { DetailFeature } from "../types"; type MapDevPanelProps = { commands: WorkbenchMapCommands; controllerState: WorkbenchMapControllerState; detailFeature: DetailFeature | null; onClose: () => void; }; const GROUP_LABELS: Record = { conduits: "管渠", junctions: "检查井", orifices: "孔口", outfalls: "排放口", pumps: "泵", scada: "SCADA" }; const ERROR_LABELS: Record = { MAP_NOT_READY: "地图尚未就绪", FEATURE_NOT_FOUND: "目标要素不存在", WFS_UNAVAILABLE: "WFS 查询暂不可用", FLOW_UNAVAILABLE: "水流图层初始化失败", INVALID_STYLE_PATCH: "参数不符合受控范围" }; export function MapDevPanel({ commands, controllerState, detailFeature, onClose }: MapDevPanelProps) { const [sourceId, setSourceId] = useState("conduits"); const [featureId, setFeatureId] = useState("DWS30265WS2010031117"); const [property, setProperty] = useState("diameter"); const [precision, setPrecision] = useState(0); const [unit, setUnit] = useState("mm"); const [groupId, setGroupId] = useState("conduits"); const [draft, setDraft] = useState>({ color: "#0F766E", fillColor: "#0369A1", strokeColor: "#FFFFFF", opacity: 0.9, widthMultiplier: 1, radiusMultiplier: 1, strokeMultiplier: 1, iconMultiplier: 1, dash: "original" }); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") onClose(); }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [onClose]); const numericProperties = useMemo( () => detailFeature && detailFeature.layer === sourceId && detailFeature.id === featureId ? getNumericFeatureProperties(detailFeature.properties) : [], [detailFeature, featureId, sourceId] ); const target: FeatureTarget = { sourceId, featureId: featureId.trim() }; const geometry = LAYER_GROUP_GEOMETRIES[groupId]; function useSelectedFeature() { if (!detailFeature) return; setSourceId(detailFeature.layer); setFeatureId(detailFeature.id); const firstNumeric = getNumericFeatureProperties(detailFeature.properties)[0]; if (firstNumeric) setProperty(firstNumeric[0]); } function applyDraft() { let patch: LayerGroupStylePatch; if (geometry === "line") { patch = { color: String(draft.color), opacity: Number(draft.opacity), widthMultiplier: Number(draft.widthMultiplier), dash: draft.dash as "original" | "solid" | "dashed" }; } else { patch = { fillColor: String(draft.fillColor), strokeColor: String(draft.strokeColor), opacity: Number(draft.opacity), radiusMultiplier: Number(draft.radiusMultiplier), strokeMultiplier: Number(draft.strokeMultiplier), ...(geometry === "scada" ? { iconMultiplier: Number(draft.iconMultiplier) } : {}) }; } commands.applyLayerGroupStyle(groupId, patch); } return ( ); } function PanelSection({ title, description, children }: { title: string; description: string; children: React.ReactNode }) { return

{title}

{description}

{children}
; } function Field({ label, children }: { label: string; children: React.ReactNode }) { return ; } function ColorField({ label, value, onChange }: { label: string; value: string; onChange: (value: string) => void }) { return
onChange(event.target.value.toUpperCase())} className="h-5 w-7 cursor-pointer border-0 bg-transparent p-0" />{value}
; } function RangeField({ label, min, max, step, value, onChange }: { label: string; min: number; max: number; step: number; value: number; onChange: (value: number) => void }) { return onChange(Number(event.target.value))} className="h-6 w-full accent-blue-600" />; } const inputClassName = "h-9 w-full rounded-lg border border-slate-200 bg-white/80 px-2.5 text-xs text-slate-800 outline-none transition focus:border-blue-400 focus:ring-2 focus:ring-blue-500/15"; const primaryButtonClassName = "inline-flex h-9 items-center justify-center gap-1.5 rounded-lg bg-blue-600 px-3 text-xs font-semibold text-white transition hover:bg-blue-700 active:scale-95 disabled:cursor-not-allowed disabled:scale-100 disabled:opacity-45"; const secondaryButtonClassName = "inline-flex h-9 items-center justify-center gap-1.5 rounded-lg border border-slate-200 bg-white/70 px-3 text-xs font-semibold text-slate-700 transition hover:bg-white active:scale-95 disabled:cursor-not-allowed disabled:scale-100 disabled:opacity-45";