feat(protobufs): sync to firmware-current and consume workspace package (#1097)
* feat(protobufs): sync to firmware-current and consume workspace package Sync the vendored .proto sources to firmware-current (v2.7.25+48), regenerate the v2 TS bindings, and consume the workspace @meshtastic/protobufs (workspace:*) in place of the stale JSR 2.7.20 — finishing the monorepo migration (core was already workspace:*). Includes the one required breaking-change fix: admin nodedb_reset changed int32 to bool, so resetNodes() now sends value: true. * build(protobufs): vendor generated bindings for workspace consumers The package is consumed via workspace:* — its exports point at the TS source, which imports ./dist/meshtastic/*_pb.ts — so the generated output must exist at build time. CI builds web/core with no codegen step and the runners have no buf CLI, so the bindings are vendored here (kept gitignored; lint/format skip them). Regenerate with: pnpm --filter @meshtastic/protobufs gen * fix(protobufs): clean script removes the actual generated output dir buf writes bindings to packages/ts/dist, but clean was removing a non-existent root dist — so it never cleaned stale output. Addresses Copilot review feedback. * refactor: move web app packages/web -> apps/web Aligns the web app with the apps/web layout (matching the Vercel web-test Root Directory and the SDK-migration direction). Pure directory move plus root config: pnpm-workspace (adds apps/*), vitest projects, root tsconfig reference, and the pr/release-web/nightly workflows. vercel.json moved with the app. Build + 36 validation tests green. * feat: config fields, module pages, key verification, telemetry capture Incorporates the firmware-current feature work onto the protobuf foundation: new config fields (Display message bubbles; LoRa fem_lna_mode + serial_hal_only; Telemetry air_quality_screen_enabled); 4 new ModuleConfig pages (TrafficManagement, StatusMessage, TAK, RemoteHardware); the manual Key Verification flow (sendKeyVerification + ClientNotificationDialog stages + Verify Key button); live telemetry capture (nodeDB addDeviceMetrics) and admin hardening (toggleMutedNode, graceful PortNum default); plus the sdk-preview ConfigEditor demo and store/config tests. Build + lint + format + 131 tests green. * chore: drop #1062 (unsaved-change-detection) to match upstream revert #1062 was merged to main by accident (per @danditomaso) and is being reverted. Reverse-applied its diff here via 3-way so #1097 stays consistent with where main is headed, while keeping the feature changes layered on the same files (deviceStore/changeRegistry). Build + 131 tests + lint + format green. * fix(nodes): clean up SNR display in node table and map popup SNR is a ratio measured in dB, not dBm (which is absolute power); the node table and map popup both mislabeled it and crammed three values together: '0dBm/50%/50raw'. The trailing '%/raw' pair was the same heuristic ((snr+10)*5) shown twice — once clamped, once not. Render SNR in dB rounded to one decimal, color-coded by a 0-100% signal-quality heuristic (green/yellow/red), with the quality percentage as a muted secondary. Drop the redundant raw value. Adds unit.db; this matches the existing SNRTooltip, which already renders dB.
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
import type { HeatmapMode } from "@components/PageComponents/Map/Layers/HeatmapLayer.tsx";
|
||||
import { Checkbox } from "@components/UI/Checkbox/index.tsx";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@components/UI/Popover.tsx";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import { LayersIcon } from "lucide-react";
|
||||
import { type ReactNode, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export interface VisibilityState {
|
||||
nodeMarkers: boolean;
|
||||
directNeighbors: boolean;
|
||||
remoteNeighbors: boolean;
|
||||
positionPrecision: boolean;
|
||||
traceroutes: boolean;
|
||||
waypoints: boolean;
|
||||
heatmap: boolean;
|
||||
}
|
||||
|
||||
export const defaultVisibilityState: VisibilityState = {
|
||||
nodeMarkers: true,
|
||||
directNeighbors: false,
|
||||
remoteNeighbors: false,
|
||||
positionPrecision: false,
|
||||
traceroutes: false,
|
||||
waypoints: true,
|
||||
heatmap: false,
|
||||
};
|
||||
|
||||
interface MapLayerToolProps {
|
||||
visibilityState: VisibilityState;
|
||||
setVisibilityState: (state: VisibilityState) => void;
|
||||
heatmapMode: HeatmapMode;
|
||||
setHeatmapMode: (mode: HeatmapMode) => void;
|
||||
}
|
||||
|
||||
interface CheckboxProps {
|
||||
label: string;
|
||||
checked: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const CheckboxItem = ({ label, checked, onChange, className }: CheckboxProps) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
className={cn("flex items-center gap-2", className)}
|
||||
>
|
||||
<span className="dark:text-slate-200">{label}</span>
|
||||
</Checkbox>
|
||||
);
|
||||
};
|
||||
|
||||
export function MapLayerTool({
|
||||
visibilityState,
|
||||
setVisibilityState,
|
||||
heatmapMode,
|
||||
setHeatmapMode,
|
||||
}: MapLayerToolProps): ReactNode {
|
||||
const { t } = useTranslation("map");
|
||||
|
||||
const enabledCount = useMemo(() => {
|
||||
return Object.values(visibilityState).filter(Boolean).length;
|
||||
}, [visibilityState]);
|
||||
|
||||
const handleCheckboxChange = (key: keyof VisibilityState) => {
|
||||
if (key === "heatmap" && !visibilityState.heatmap) {
|
||||
// If turning heatmap on, turn everything else off so the layer is visible
|
||||
setVisibilityState({
|
||||
nodeMarkers: false,
|
||||
directNeighbors: false,
|
||||
remoteNeighbors: false,
|
||||
positionPrecision: false,
|
||||
traceroutes: false,
|
||||
waypoints: false,
|
||||
heatmap: true,
|
||||
});
|
||||
} else {
|
||||
setVisibilityState({
|
||||
...visibilityState,
|
||||
[key]: !visibilityState[key],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const layers = useMemo(
|
||||
() => [
|
||||
{ key: "nodeMarkers", label: t("layerTool.nodeMarkers") },
|
||||
{ key: "waypoints", label: t("layerTool.waypoints") },
|
||||
{ key: "directNeighbors", label: t("layerTool.directNeighbors") },
|
||||
{ key: "remoteNeighbors", label: t("layerTool.remoteNeighbors") },
|
||||
{ key: "positionPrecision", label: t("layerTool.positionPrecision") },
|
||||
{ key: "heatmap", label: t("layerTool.heatmap") },
|
||||
// { key: "traceroutes", label: t("layerTool.traceroutes") },
|
||||
],
|
||||
[t],
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"relative rounded align-center",
|
||||
"w-[29px] px-1 py-1 shadow-l outline-[2px] outline-stone-600/20",
|
||||
"bg-stone-50 hover:bg-stone-200 dark:bg-stone-200 dark:hover:bg-stone-300 ",
|
||||
"text-slate-600 hover:text-slate-700 active:bg-slate-300",
|
||||
"dark:text-slate-600 hover:dark:text-slate-700",
|
||||
)}
|
||||
aria-label={t("mapMenu.layersAria")}
|
||||
>
|
||||
<LayersIcon className="w-[21px]" />
|
||||
{enabledCount > 0 && (
|
||||
<span
|
||||
className={cn(
|
||||
"absolute -bottom-2 -right-2",
|
||||
"min-w-4 h-4 px-[3px]",
|
||||
"rounded-full text-[10px] leading-4",
|
||||
"bg-blue-500 text-white",
|
||||
"flex items-center justify-center",
|
||||
"ring-2 ring-white dark:ring-stone-200",
|
||||
)}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{enabledCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className={cn("dark:text-slate-300 flex flex-col space-y-2 py-4")}
|
||||
side={"bottom"}
|
||||
align="end"
|
||||
sideOffset={7}
|
||||
>
|
||||
{layers.map(({ key, label }) => (
|
||||
<div key={key}>
|
||||
<CheckboxItem
|
||||
label={label}
|
||||
checked={visibilityState[key as keyof VisibilityState]}
|
||||
onChange={() => handleCheckboxChange(key as keyof VisibilityState)}
|
||||
/>
|
||||
{key === "heatmap" && visibilityState.heatmap && (
|
||||
<div className="pl-6 pt-2 flex flex-col gap-1">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
className="accent-blue-500"
|
||||
checked={heatmapMode === "density"}
|
||||
onChange={() => setHeatmapMode("density")}
|
||||
/>
|
||||
<span className="text-sm dark:text-slate-300">{t("layerTool.density")}</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
className="accent-blue-500"
|
||||
checked={heatmapMode === "snr"}
|
||||
onChange={() => setHeatmapMode("snr")}
|
||||
/>
|
||||
<span className="text-sm dark:text-slate-300">SNR</span>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{/*<CheckboxItem
|
||||
key="traceroutes"
|
||||
label={t("layerTool.traceroutes")}
|
||||
checked={visibilityState.traceroutes}
|
||||
onChange={(checked) => {
|
||||
setVisibilityState({ ...visibilityState, traceroutes: checked });
|
||||
}}
|
||||
/>*/}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user