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,99 @@
|
||||
import { Monitor, Moon, Sun } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTheme } from "../core/hooks/useTheme.ts";
|
||||
import { useToggleVisibility } from "../core/hooks/useToggleVisiblility.ts";
|
||||
import { cn } from "../core/utils/cn.ts";
|
||||
import { Button } from "./UI/Button.tsx";
|
||||
import { Subtle } from "./UI/Typography/Subtle.tsx";
|
||||
|
||||
type ThemePreference = "light" | "dark" | "system";
|
||||
|
||||
interface ThemeSwitcherProps {
|
||||
className?: string;
|
||||
disableHover?: boolean;
|
||||
}
|
||||
|
||||
const TOOLTIP_TIMEOUT = 2000; // 2 seconds
|
||||
|
||||
export default function ThemeSwitcher({
|
||||
className: passedClassName = "",
|
||||
disableHover = false,
|
||||
}: ThemeSwitcherProps) {
|
||||
const [showTooltip, toggleShowTooltip] = useToggleVisibility({
|
||||
timeout: TOOLTIP_TIMEOUT,
|
||||
});
|
||||
|
||||
const { preference, setPreference } = useTheme();
|
||||
const { t } = useTranslation("ui");
|
||||
|
||||
const iconBaseClass =
|
||||
"size-4 flex-shrink-0 text-gray-500 dark:text-gray-400 transition-colors duration-150";
|
||||
const iconHoverClass = !disableHover
|
||||
? "group-hover:text-gray-700 dark:group-hover:text-gray-200"
|
||||
: "";
|
||||
const combinedIconClass = cn(iconBaseClass, iconHoverClass);
|
||||
|
||||
const themeIcons = {
|
||||
light: <Sun className={combinedIconClass} />,
|
||||
dark: <Moon className={combinedIconClass} />,
|
||||
system: <Monitor className={combinedIconClass} />,
|
||||
};
|
||||
|
||||
const toggleTheme = () => {
|
||||
const preferences: ThemePreference[] = ["light", "dark", "system"];
|
||||
const currentIndex = preferences.indexOf(preference);
|
||||
const nextPreference = preferences[(currentIndex + 1) % preferences.length] ?? "system";
|
||||
setPreference(nextPreference);
|
||||
toggleShowTooltip();
|
||||
};
|
||||
|
||||
const preferenceDisplayMap: Record<ThemePreference, string> = {
|
||||
light: t("theme.light"),
|
||||
dark: t("theme.dark"),
|
||||
system: t("theme.system"),
|
||||
};
|
||||
|
||||
const currentDisplayPreference = preferenceDisplayMap[preference];
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={toggleTheme}
|
||||
aria-label={t("theme.changeTheme")}
|
||||
className={cn(
|
||||
"group relative flex justify-start",
|
||||
"gap-2.5 p-1.5 rounded-md transition-colors duration-150",
|
||||
"cursor-pointer",
|
||||
!disableHover && "hover:bg-gray-100 dark:hover:bg-gray-700",
|
||||
"focus:*:data-label:opacity-100",
|
||||
passedClassName,
|
||||
)}
|
||||
>
|
||||
<span
|
||||
data-label="theme-preference-tooltip"
|
||||
className={cn(
|
||||
"transition-opacity duration-150 hidden",
|
||||
"block absolute w-max max-w-xs",
|
||||
"p-1 text-xs text-white dark:text-black bg-black dark:bg-white",
|
||||
"rounded-md shadow-lg",
|
||||
"left-1/2 -translate-x-1/2 -top-8",
|
||||
showTooltip ? "visible" : "hidden opacity-0",
|
||||
)}
|
||||
>
|
||||
{currentDisplayPreference}
|
||||
</span>
|
||||
|
||||
{themeIcons[preference]}
|
||||
<Subtle
|
||||
className={cn(
|
||||
"text-sm",
|
||||
"text-gray-600 dark:text-gray-300",
|
||||
"transition-colors duration-150",
|
||||
!disableHover && "group-hover:text-gray-800 dark:group-hover:text-gray-100",
|
||||
)}
|
||||
>
|
||||
{t("theme.changeTheme")}
|
||||
</Subtle>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user