Merge pull request #385 from danditomaso/fix/update-project-deps-to-latest
fix: update packages to latest version
This commit is contained in:
+43
-44
@@ -27,65 +27,64 @@
|
|||||||
"homepage": "https://meshtastic.org",
|
"homepage": "https://meshtastic.org",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bufbuild/protobuf": "^1.10.0",
|
"@bufbuild/protobuf": "^1.10.0",
|
||||||
"@emeraldpay/hashicon-react": "^0.5.2",
|
|
||||||
"@meshtastic/js": "2.3.7-5",
|
"@meshtastic/js": "2.3.7-5",
|
||||||
"@noble/curves": "^1.5.0",
|
"@noble/curves": "^1.8.1",
|
||||||
"@radix-ui/react-accordion": "^1.2.0",
|
"@radix-ui/react-accordion": "^1.2.2",
|
||||||
"@radix-ui/react-checkbox": "^1.1.0",
|
"@radix-ui/react-checkbox": "^1.1.3",
|
||||||
"@radix-ui/react-dialog": "^1.1.1",
|
"@radix-ui/react-dialog": "^1.1.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
"@radix-ui/react-dropdown-menu": "^2.1.5",
|
||||||
"@radix-ui/react-label": "^2.1.0",
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
"@radix-ui/react-menubar": "^1.1.1",
|
"@radix-ui/react-menubar": "^1.1.5",
|
||||||
"@radix-ui/react-popover": "^1.1.1",
|
"@radix-ui/react-popover": "^1.1.5",
|
||||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
"@radix-ui/react-scroll-area": "^1.2.2",
|
||||||
"@radix-ui/react-select": "^2.1.1",
|
"@radix-ui/react-select": "^2.1.5",
|
||||||
"@radix-ui/react-separator": "^1.1.0",
|
"@radix-ui/react-separator": "^1.1.1",
|
||||||
"@radix-ui/react-switch": "^1.1.0",
|
"@radix-ui/react-switch": "^1.1.2",
|
||||||
"@radix-ui/react-tabs": "^1.1.0",
|
"@radix-ui/react-tabs": "^1.1.2",
|
||||||
"@radix-ui/react-toast": "^1.2.1",
|
"@radix-ui/react-toast": "^1.2.5",
|
||||||
"@radix-ui/react-tooltip": "^1.1.1",
|
"@radix-ui/react-tooltip": "^1.1.7",
|
||||||
"@turf/turf": "^6.5.0",
|
"@turf/turf": "^7.2.0",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.14.1",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.4",
|
||||||
"crypto-random-string": "^5.0.0",
|
"crypto-random-string": "^5.0.0",
|
||||||
"immer": "^10.1.1",
|
"immer": "^10.1.1",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"lucide-react": "^0.363.0",
|
"lucide-react": "^0.474.0",
|
||||||
"mapbox-gl": "^3.6.0",
|
"mapbox-gl": "^3.9.4",
|
||||||
"maplibre-gl": "4.1.2",
|
"maplibre-gl": "4.1.2",
|
||||||
"react": "^18.3.1",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.52.0",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-map-gl": "7.1.7",
|
"react-map-gl": "7.1.9",
|
||||||
"react-qrcode-logo": "^2.10.0",
|
"react-qrcode-logo": "^3.0.0",
|
||||||
"rfc4648": "^1.5.3",
|
"rfc4648": "^1.5.4",
|
||||||
"timeago-react": "^3.0.6",
|
"timeago-react": "^3.0.6",
|
||||||
"vite-plugin-node-polyfills": "^0.22.0",
|
"vite-plugin-node-polyfills": "^0.23.0",
|
||||||
"zustand": "4.5.2"
|
"zustand": "5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.8.2",
|
"@biomejs/biome": "^1.9.4",
|
||||||
"@rsbuild/core": "^1.0.10",
|
"@rsbuild/core": "^1.2.3",
|
||||||
"@rsbuild/plugin-react": "^1.0.3",
|
"@rsbuild/plugin-react": "^1.1.0",
|
||||||
"@types/chrome": "^0.0.263",
|
"@types/chrome": "^0.0.299",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/node": "^20.14.9",
|
"@types/node": "^22.12.0",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^19.0.8",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^19.0.3",
|
||||||
"@types/w3c-web-serial": "^1.0.6",
|
"@types/w3c-web-serial": "^1.0.7",
|
||||||
"@types/web-bluetooth": "^0.0.20",
|
"@types/web-bluetooth": "^0.0.20",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.20",
|
||||||
"gzipper": "^7.2.0",
|
"gzipper": "^8.2.0",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.5.1",
|
||||||
"simple-git-hooks": "^2.11.1",
|
"simple-git-hooks": "^2.11.1",
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss": "^3.4.4",
|
"tailwindcss": "^3.4.17",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tar": "^6.2.1",
|
"tar": "^7.4.3",
|
||||||
"typescript": "^5.5.2"
|
"typescript": "^5.7.3"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.15.4"
|
"packageManager": "pnpm@9.15.4"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2200
-2541
File diff suppressed because it is too large
Load Diff
@@ -7,10 +7,7 @@ export interface DeviceWrapperProps {
|
|||||||
device?: Device;
|
device?: Device;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeviceWrapper = ({
|
export const DeviceWrapper = ({ children, device }: DeviceWrapperProps) => {
|
||||||
children,
|
|
||||||
device,
|
|
||||||
}: DeviceWrapperProps): JSX.Element => {
|
|
||||||
return (
|
return (
|
||||||
<DeviceContext.Provider value={device}>{children}</DeviceContext.Provider>
|
<DeviceContext.Provider value={device}>{children}</DeviceContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
+6
-6
@@ -1,11 +1,11 @@
|
|||||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||||
import { ChannelsPage } from "@pages/Channels.tsx";
|
import ChannelsPage from "@pages/Channels.tsx";
|
||||||
import { ConfigPage } from "@pages/Config/index.tsx";
|
import ConfigPage from "@pages/Config/index.tsx";
|
||||||
import { MapPage } from "@pages/Map.tsx";
|
import MapPage from "@pages/Map.tsx";
|
||||||
import { MessagesPage } from "@pages/Messages.tsx";
|
import MessagesPage from "@pages/Messages.tsx";
|
||||||
import { NodesPage } from "@pages/Nodes.tsx";
|
import NodesPage from "@pages/Nodes.tsx";
|
||||||
|
|
||||||
export const PageRouter = (): JSX.Element => {
|
export const PageRouter = () => {
|
||||||
const { activePage } = useDevice();
|
const { activePage } = useDevice();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Avatar } from "@components/UI/Avatar";
|
||||||
import {
|
import {
|
||||||
CommandDialog,
|
CommandDialog,
|
||||||
CommandEmpty,
|
CommandEmpty,
|
||||||
@@ -8,7 +9,6 @@ import {
|
|||||||
} from "@components/UI/Command.tsx";
|
} from "@components/UI/Command.tsx";
|
||||||
import { useAppStore } from "@core/stores/appStore.ts";
|
import { useAppStore } from "@core/stores/appStore.ts";
|
||||||
import { useDevice, useDeviceStore } from "@core/stores/deviceStore.ts";
|
import { useDevice, useDeviceStore } from "@core/stores/deviceStore.ts";
|
||||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
|
||||||
import { useCommandState } from "cmdk";
|
import { useCommandState } from "cmdk";
|
||||||
import {
|
import {
|
||||||
ArrowLeftRightIcon,
|
ArrowLeftRightIcon,
|
||||||
@@ -51,11 +51,11 @@ export interface Command {
|
|||||||
|
|
||||||
export interface SubItem {
|
export interface SubItem {
|
||||||
label: string;
|
label: string;
|
||||||
icon: JSX.Element;
|
icon: React.ReactNode;
|
||||||
action: () => void;
|
action: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CommandPalette = (): JSX.Element => {
|
export const CommandPalette = () => {
|
||||||
const {
|
const {
|
||||||
commandPaletteOpen,
|
commandPaletteOpen,
|
||||||
setCommandPaletteOpen,
|
setCommandPaletteOpen,
|
||||||
@@ -125,9 +125,11 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
device.nodes.get(device.hardware.myNodeNum)?.user?.longName ??
|
device.nodes.get(device.hardware.myNodeNum)?.user?.longName ??
|
||||||
device.hardware.myNodeNum.toString(),
|
device.hardware.myNodeNum.toString(),
|
||||||
icon: (
|
icon: (
|
||||||
<Hashicon
|
<Avatar
|
||||||
size={16}
|
text={
|
||||||
value={device.hardware.myNodeNum.toString()}
|
device.nodes.get(device.hardware.myNodeNum)?.user
|
||||||
|
?.shortName ?? device.hardware.myNodeNum.toString()
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
action() {
|
action() {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Separator } from "@components/UI/Seperator.tsx";
|
|||||||
import { Code } from "@components/UI/Typography/Code.tsx";
|
import { Code } from "@components/UI/Typography/Code.tsx";
|
||||||
import { useAppStore } from "@core/stores/appStore.ts";
|
import { useAppStore } from "@core/stores/appStore.ts";
|
||||||
import { useDeviceStore } from "@core/stores/deviceStore.ts";
|
import { useDeviceStore } from "@core/stores/deviceStore.ts";
|
||||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
|
||||||
import {
|
import {
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
LanguagesIcon,
|
LanguagesIcon,
|
||||||
@@ -12,6 +11,8 @@ import {
|
|||||||
SearchIcon,
|
SearchIcon,
|
||||||
SunIcon,
|
SunIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import type { JSX } from "react";
|
||||||
|
import { Avatar } from "./UI/Avatar";
|
||||||
|
|
||||||
export const DeviceSelector = (): JSX.Element => {
|
export const DeviceSelector = (): JSX.Element => {
|
||||||
const { getDevices } = useDeviceStore();
|
const { getDevices } = useDeviceStore();
|
||||||
@@ -44,9 +45,12 @@ export const DeviceSelector = (): JSX.Element => {
|
|||||||
}}
|
}}
|
||||||
active={selectedDevice === device.id}
|
active={selectedDevice === device.id}
|
||||||
>
|
>
|
||||||
<Hashicon
|
<Avatar
|
||||||
size={24}
|
text={
|
||||||
value={device.hardware.myNodeNum.toString()}
|
device.nodes
|
||||||
|
.get(device.hardware.myNodeNum)
|
||||||
|
?.user?.shortName.toString() ?? "UNK"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</DeviceSelectorButton>
|
</DeviceSelectorButton>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ export const DeviceSelectorButton = ({
|
|||||||
active,
|
active,
|
||||||
onClick,
|
onClick,
|
||||||
children,
|
children,
|
||||||
}: DeviceSelectorButtonProps): JSX.Element => (
|
}: DeviceSelectorButtonProps) => (
|
||||||
<li
|
<li
|
||||||
className="aspect-w-1 aspect-h-1 relative w-full"
|
className="aspect-w-1 aspect-h-1 relative w-full"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onKeyDown={onClick}
|
onKeyDown={onClick}
|
||||||
>
|
>
|
||||||
{active && (
|
{/* {active && (
|
||||||
<div className="absolute -left-2 h-10 w-1.5 rounded-full bg-accent" />
|
<div className="absolute -left-2 h-10 w-1.5 rounded-full bg-accent" />
|
||||||
)}
|
)} */}
|
||||||
<div className="flex aspect-square cursor-pointer flex-col items-center justify-center">
|
<div className="flex aspect-square cursor-pointer flex-col items-center justify-center">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function SelectInput<T extends FieldValues>({
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{optionsEnumValues.map(([name, value]) => (
|
{optionsEnumValues.map(([name, value]) => (
|
||||||
<SelectItem key={name} value={value.toString()}>
|
<SelectItem key={name + value} value={value.toString()}>
|
||||||
{formatEnumName
|
{formatEnumName
|
||||||
? name
|
? name
|
||||||
.replace(/_/g, " ")
|
.replace(/_/g, " ")
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ export const FieldWrapper = ({
|
|||||||
children,
|
children,
|
||||||
valid,
|
valid,
|
||||||
validationText,
|
validationText,
|
||||||
}: FieldWrapperProps): JSX.Element => (
|
}: FieldWrapperProps) => (
|
||||||
<div className="pt-6 sm:pt-5">
|
<div className="pt-6 sm:pt-5">
|
||||||
<div role="group" aria-labelledby="label-notifications">
|
<fieldset aria-labelledby="label-notifications">
|
||||||
<div className="sm:grid sm:grid-cols-3 sm:items-baseline sm:gap-4">
|
<div className="sm:grid sm:grid-cols-3 sm:items-baseline sm:gap-4">
|
||||||
<Label>{label}</Label>
|
<Label>{label}</Label>
|
||||||
<div className="sm:col-span-2">
|
<div className="sm:col-span-2">
|
||||||
@@ -32,6 +32,6 @@ export const FieldWrapper = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Separator } from "@app/components/UI/Seperator";
|
import { Separator } from "@app/components/UI/Seperator";
|
||||||
import { H5 } from "@app/components/UI/Typography/H5.tsx";
|
import { H5 } from "@app/components/UI/Typography/H5.tsx";
|
||||||
import { Subtle } from "@app/components/UI/Typography/Subtle.tsx";
|
import { Subtle } from "@app/components/UI/Typography/Subtle.tsx";
|
||||||
|
import { Avatar } from "@components/UI/Avatar";
|
||||||
import { Mono } from "@components/generic/Mono.tsx";
|
import { Mono } from "@components/generic/Mono.tsx";
|
||||||
import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.tsx";
|
import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.tsx";
|
||||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
|
||||||
import { Protobuf } from "@meshtastic/js";
|
import { Protobuf } from "@meshtastic/js";
|
||||||
import type { Protobuf as ProtobufType } from "@meshtastic/js";
|
import type { Protobuf as ProtobufType } from "@meshtastic/js";
|
||||||
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
|
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
|
||||||
@@ -23,7 +23,7 @@ export interface NodeDetailProps {
|
|||||||
node: ProtobufType.Mesh.NodeInfo;
|
node: ProtobufType.Mesh.NodeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NodeDetail = ({ node }: NodeDetailProps): JSX.Element => {
|
export const NodeDetail = ({ node }: NodeDetailProps) => {
|
||||||
const name = node.user?.longName || `!${numberToHexUnpadded(node.num)}`;
|
const name = node.user?.longName || `!${numberToHexUnpadded(node.num)}`;
|
||||||
const hardwareType = Protobuf.Mesh.HardwareModel[
|
const hardwareType = Protobuf.Mesh.HardwareModel[
|
||||||
node.user?.hwModel ?? 0
|
node.user?.hwModel ?? 0
|
||||||
@@ -33,7 +33,7 @@ export const NodeDetail = ({ node }: NodeDetailProps): JSX.Element => {
|
|||||||
<div className="dark:text-black">
|
<div className="dark:text-black">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="flex flex-col items-center gap-2 min-w-6 pt-1">
|
<div className="flex flex-col items-center gap-2 min-w-6 pt-1">
|
||||||
<Hashicon value={node.num.toString()} size={22} />
|
<Avatar text={node.user?.shortName} />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{node.user?.publicKey && node.user?.publicKey.length > 0 ? (
|
{node.user?.publicKey && node.user?.publicKey.length > 0 ? (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { MessageWithState } from "@app/core/stores/deviceStore.ts";
|
import type { MessageWithState } from "@app/core/stores/deviceStore.ts";
|
||||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
import { Avatar } from "@components/UI/Avatar";
|
||||||
import type { Protobuf } from "@meshtastic/js";
|
import type { Protobuf } from "@meshtastic/js";
|
||||||
import {
|
import {
|
||||||
AlertCircleIcon,
|
AlertCircleIcon,
|
||||||
@@ -13,11 +13,7 @@ export interface MessageProps {
|
|||||||
sender?: Protobuf.Mesh.NodeInfo;
|
sender?: Protobuf.Mesh.NodeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Message = ({
|
export const Message = ({ lastMsgSameUser, message, sender }: MessageProps) => {
|
||||||
lastMsgSameUser,
|
|
||||||
message,
|
|
||||||
sender,
|
|
||||||
}: MessageProps): JSX.Element => {
|
|
||||||
return lastMsgSameUser ? (
|
return lastMsgSameUser ? (
|
||||||
<div className="ml-5 flex">
|
<div className="ml-5 flex">
|
||||||
{message.state === "ack" ? (
|
{message.state === "ack" ? (
|
||||||
@@ -39,7 +35,7 @@ export const Message = ({
|
|||||||
<div className="mx-4 mt-2 gap-2">
|
<div className="mx-4 mt-2 gap-2">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="w-6 cursor-pointer">
|
<div className="w-6 cursor-pointer">
|
||||||
<Hashicon value={(sender?.num ?? 0).toString()} size={32} />
|
<Avatar text={sender?.user?.shortName ?? "UNK"} />
|
||||||
</div>
|
</div>
|
||||||
<span className="cursor-pointer font-medium text-textPrimary">
|
<span className="cursor-pointer font-medium text-textPrimary">
|
||||||
{sender?.user?.longName ?? "UNK"}
|
{sender?.user?.longName ?? "UNK"}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { cn } from "@app/core/utils/cn";
|
||||||
|
import type React from "react";
|
||||||
|
|
||||||
|
type RGBColor = {
|
||||||
|
r: number;
|
||||||
|
g: number;
|
||||||
|
b: number;
|
||||||
|
a: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface AvatarProps {
|
||||||
|
text: string;
|
||||||
|
size?: "sm" | "lg";
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// biome-ignore lint/complexity/noStaticOnlyClass: stop being annoying Biome
|
||||||
|
class ColorUtils {
|
||||||
|
static hexToRgb(hex: number): RGBColor {
|
||||||
|
return {
|
||||||
|
r: (hex & 0xff0000) >> 16,
|
||||||
|
g: (hex & 0x00ff00) >> 8,
|
||||||
|
b: hex & 0x0000ff,
|
||||||
|
a: 255,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static rgbToHex(color: RGBColor): number {
|
||||||
|
return (
|
||||||
|
(Math.round(color.a) << 24) |
|
||||||
|
(Math.round(color.r) << 16) |
|
||||||
|
(Math.round(color.g) << 8) |
|
||||||
|
Math.round(color.b)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static isLight(color: RGBColor): boolean {
|
||||||
|
const brightness = (color.r * 299 + color.g * 587 + color.b * 114) / 1000;
|
||||||
|
return brightness > 127.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Avatar: React.FC<AvatarProps> = ({
|
||||||
|
text,
|
||||||
|
size = "sm",
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
const sizes = {
|
||||||
|
sm: "size-11 text-xs",
|
||||||
|
lg: "size-16 text-lg",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pick a color based on the text provided to function
|
||||||
|
const getColorFromText = (text: string): RGBColor => {
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < text.length; i++) {
|
||||||
|
hash = text.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
r: (hash & 0xff0000) >> 16,
|
||||||
|
g: (hash & 0x00ff00) >> 8,
|
||||||
|
b: hash & 0x0000ff,
|
||||||
|
a: 255,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const bgColor = getColorFromText(text ?? "UNK");
|
||||||
|
const isLight = ColorUtils.isLight(bgColor);
|
||||||
|
const textColor = isLight ? "#000000" : "#FFFFFF";
|
||||||
|
const initials = text?.toUpperCase().slice(0, 4) ?? "UNK";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
`
|
||||||
|
rounded-full
|
||||||
|
flex
|
||||||
|
items-center
|
||||||
|
justify-center
|
||||||
|
size-11
|
||||||
|
font-semibold`,
|
||||||
|
sizes[size],
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
backgroundColor: `rgb(${bgColor.r}, ${bgColor.g}, ${bgColor.b})`,
|
||||||
|
color: textColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p className="p-1">{initials}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -9,9 +9,9 @@ export interface SidebarSectionProps {
|
|||||||
export const SidebarSection = ({
|
export const SidebarSection = ({
|
||||||
label: title,
|
label: title,
|
||||||
children,
|
children,
|
||||||
}: SidebarSectionProps): JSX.Element => (
|
}: SidebarSectionProps) => (
|
||||||
<div className="px-4 py-2">
|
<div className="px-4 py-2">
|
||||||
<H4 className="mb-2 ml-2">{title}</H4>
|
<H4 className="mb-3 ml-2">{title}</H4>
|
||||||
<div className="space-y-1">{children}</div>
|
<div className="space-y-1">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -74,15 +74,12 @@ export const Table = ({ headings, rows }: TableProps): JSX.Element => {
|
|||||||
>
|
>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{heading.title}
|
{heading.title}
|
||||||
{sortColumn === heading.title && (
|
{sortColumn === heading.title &&
|
||||||
<>
|
(sortOrder === "asc" ? (
|
||||||
{sortOrder === "asc" ? (
|
<ChevronUpIcon size={16} />
|
||||||
<ChevronUpIcon size={16} />
|
) : (
|
||||||
) : (
|
<ChevronDownIcon size={16} />
|
||||||
<ChevronDownIcon size={16} />
|
))}
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -97,6 +97,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
-drag: none;
|
|
||||||
-webkit-user-drag: none;
|
-webkit-user-drag: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const getChannelName = (channel: Protobuf.Channel.Channel) =>
|
|||||||
? "Primary"
|
? "Primary"
|
||||||
: `Ch ${channel.index}`;
|
: `Ch ${channel.index}`;
|
||||||
|
|
||||||
export const ChannelsPage = (): JSX.Element => {
|
const ChannelsPage = () => {
|
||||||
const { channels, setDialogOpen } = useDevice();
|
const { channels, setDialogOpen } = useDevice();
|
||||||
const [activeChannel, setActiveChannel] = useState<Types.ChannelNumber>(
|
const [activeChannel, setActiveChannel] = useState<Types.ChannelNumber>(
|
||||||
Types.ChannelNumber.Primary,
|
Types.ChannelNumber.Primary,
|
||||||
@@ -69,3 +69,4 @@ export const ChannelsPage = (): JSX.Element => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
export default ChannelsPage;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { ModuleConfig } from "@pages/Config/ModuleConfig.tsx";
|
|||||||
import { BoxesIcon, SaveIcon, SettingsIcon } from "lucide-react";
|
import { BoxesIcon, SaveIcon, SettingsIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export const ConfigPage = (): JSX.Element => {
|
const ConfigPage = (): JSX.Element => {
|
||||||
const { workingConfig, workingModuleConfig, connection } = useDevice();
|
const { workingConfig, workingModuleConfig, connection } = useDevice();
|
||||||
const [activeConfigSection, setActiveConfigSection] = useState<
|
const [activeConfigSection, setActiveConfigSection] = useState<
|
||||||
"device" | "module"
|
"device" | "module"
|
||||||
@@ -72,3 +72,5 @@ export const ConfigPage = (): JSX.Element => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default ConfigPage;
|
||||||
|
|||||||
+15
-10
@@ -1,4 +1,5 @@
|
|||||||
import { NodeDetail } from "@app/components/PageComponents/Map/NodeDetail";
|
import { NodeDetail } from "@app/components/PageComponents/Map/NodeDetail";
|
||||||
|
import { Avatar } from "@app/components/UI/Avatar";
|
||||||
import { Subtle } from "@app/components/UI/Typography/Subtle.tsx";
|
import { Subtle } from "@app/components/UI/Typography/Subtle.tsx";
|
||||||
import { cn } from "@app/core/utils/cn.ts";
|
import { cn } from "@app/core/utils/cn.ts";
|
||||||
import { PageLayout } from "@components/PageLayout.tsx";
|
import { PageLayout } from "@components/PageLayout.tsx";
|
||||||
@@ -7,7 +8,6 @@ import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx";
|
|||||||
import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx";
|
import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx";
|
||||||
import { useAppStore } from "@core/stores/appStore.ts";
|
import { useAppStore } from "@core/stores/appStore.ts";
|
||||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
|
||||||
import type { Protobuf } from "@meshtastic/js";
|
import type { Protobuf } from "@meshtastic/js";
|
||||||
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
|
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
|
||||||
import { bbox, lineString } from "@turf/turf";
|
import { bbox, lineString } from "@turf/turf";
|
||||||
@@ -17,11 +17,11 @@ import {
|
|||||||
ZoomInIcon,
|
ZoomInIcon,
|
||||||
ZoomOutIcon,
|
ZoomOutIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { type JSX, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { AttributionControl, Marker, Popup, useMap } from "react-map-gl";
|
import { AttributionControl, Marker, Popup, useMap } from "react-map-gl";
|
||||||
import MapGl from "react-map-gl/maplibre";
|
import MapGl from "react-map-gl/maplibre";
|
||||||
|
|
||||||
export const MapPage = (): JSX.Element => {
|
const MapPage = (): JSX.Element => {
|
||||||
const { nodes, waypoints } = useDevice();
|
const { nodes, waypoints } = useDevice();
|
||||||
const { rasterSources, darkMode } = useAppStore();
|
const { rasterSources, darkMode } = useAppStore();
|
||||||
const { default: map } = useMap();
|
const { default: map } = useMap();
|
||||||
@@ -30,7 +30,7 @@ export const MapPage = (): JSX.Element => {
|
|||||||
const [selectedNode, setSelectedNode] =
|
const [selectedNode, setSelectedNode] =
|
||||||
useState<Protobuf.Mesh.NodeInfo | null>(null);
|
useState<Protobuf.Mesh.NodeInfo | null>(null);
|
||||||
|
|
||||||
const allNodes = Array.from(nodes.values());
|
const allNodes = useMemo(() => Array.from(nodes.values()), [nodes]);
|
||||||
|
|
||||||
const getBBox = useCallback(() => {
|
const getBBox = useCallback(() => {
|
||||||
if (!map) {
|
if (!map) {
|
||||||
@@ -135,9 +135,7 @@ export const MapPage = (): JSX.Element => {
|
|||||||
renderWorldCopies={false}
|
renderWorldCopies={false}
|
||||||
maxPitch={0}
|
maxPitch={0}
|
||||||
style={{
|
style={{
|
||||||
filter: darkMode
|
filter: darkMode ? "brightness(0.8)" : "",
|
||||||
? "brightness(0.6) invert(1) contrast(3) hue-rotate(200deg) saturate(0.3) brightness(0.7)"
|
|
||||||
: "",
|
|
||||||
}}
|
}}
|
||||||
dragRotate={false}
|
dragRotate={false}
|
||||||
touchZoomRotate={false}
|
touchZoomRotate={false}
|
||||||
@@ -177,7 +175,7 @@ export const MapPage = (): JSX.Element => {
|
|||||||
key={node.num}
|
key={node.num}
|
||||||
longitude={(node.position.longitudeI ?? 0) / 1e7}
|
longitude={(node.position.longitudeI ?? 0) / 1e7}
|
||||||
latitude={(node.position.latitudeI ?? 0) / 1e7}
|
latitude={(node.position.latitudeI ?? 0) / 1e7}
|
||||||
style={{ filter: darkMode ? "invert(1)" : "" }}
|
// style={{ filter: darkMode ? "invert(1)" : "" }}
|
||||||
anchor="bottom"
|
anchor="bottom"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedNode(node);
|
setSelectedNode(node);
|
||||||
@@ -190,8 +188,13 @@ export const MapPage = (): JSX.Element => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex cursor-pointer gap-2 rounded-md border bg-backgroundPrimary p-1.5">
|
<div className="flex cursor-pointer gap-2 rounded-md bg-transparent p-1.5">
|
||||||
<Hashicon value={node.num.toString()} size={22} />
|
<Avatar
|
||||||
|
text={
|
||||||
|
node.user?.shortName.toString() ?? node.num.toString()
|
||||||
|
}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
<Subtle className={cn(zoom < 12 && "hidden")}>
|
<Subtle className={cn(zoom < 12 && "hidden")}>
|
||||||
{node.user?.longName ||
|
{node.user?.longName ||
|
||||||
`!${numberToHexUnpadded(node.num)}`}
|
`!${numberToHexUnpadded(node.num)}`}
|
||||||
@@ -217,3 +220,5 @@ export const MapPage = (): JSX.Element => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default MapPage;
|
||||||
|
|||||||
+27
-16
@@ -1,18 +1,18 @@
|
|||||||
import { ChannelChat } from "@components/PageComponents/Messages/ChannelChat.tsx";
|
import { ChannelChat } from "@components/PageComponents/Messages/ChannelChat.tsx";
|
||||||
import { PageLayout } from "@components/PageLayout.tsx";
|
import { PageLayout } from "@components/PageLayout.tsx";
|
||||||
import { Sidebar } from "@components/Sidebar.tsx";
|
import { Sidebar } from "@components/Sidebar.tsx";
|
||||||
|
import { Avatar } from "@components/UI/Avatar.tsx";
|
||||||
import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx";
|
import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx";
|
||||||
import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx";
|
import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx";
|
||||||
import { useToast } from "@core/hooks/useToast.ts";
|
import { useToast } from "@core/hooks/useToast.ts";
|
||||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
import { Device, useDevice, useDeviceStore } from "@core/stores/deviceStore.ts";
|
||||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
|
||||||
import { Protobuf, Types } from "@meshtastic/js";
|
import { Protobuf, Types } from "@meshtastic/js";
|
||||||
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
|
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
|
||||||
import { getChannelName } from "@pages/Channels.tsx";
|
import { getChannelName } from "@pages/Channels.tsx";
|
||||||
import { HashIcon, LockIcon, LockOpenIcon, WaypointsIcon } from "lucide-react";
|
import { HashIcon, LockIcon, LockOpenIcon, WaypointsIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export const MessagesPage = (): JSX.Element => {
|
const MessagesPage = () => {
|
||||||
const { channels, nodes, hardware, messages, traceroutes, connection } =
|
const { channels, nodes, hardware, messages, traceroutes, connection } =
|
||||||
useDevice();
|
useDevice();
|
||||||
const [chatType, setChatType] =
|
const [chatType, setChatType] =
|
||||||
@@ -56,18 +56,27 @@ export const MessagesPage = (): JSX.Element => {
|
|||||||
))}
|
))}
|
||||||
</SidebarSection>
|
</SidebarSection>
|
||||||
<SidebarSection label="Nodes">
|
<SidebarSection label="Nodes">
|
||||||
{filteredNodes.map((node) => (
|
<div className="flex flex-col gap-4">
|
||||||
<SidebarButton
|
{filteredNodes.map((node) => (
|
||||||
key={node.num}
|
<SidebarButton
|
||||||
label={node.user?.longName ?? `!${numberToHexUnpadded(node.num)}`}
|
key={node.num}
|
||||||
active={activeChat === node.num}
|
label={
|
||||||
onClick={() => {
|
node.user?.longName ?? `!${numberToHexUnpadded(node.num)}`
|
||||||
setChatType("direct");
|
}
|
||||||
setActiveChat(node.num);
|
active={activeChat === node.num}
|
||||||
}}
|
onClick={() => {
|
||||||
element={<Hashicon size={20} value={node.num.toString()} />}
|
setChatType("direct");
|
||||||
/>
|
setActiveChat(node.num);
|
||||||
))}
|
}}
|
||||||
|
element={
|
||||||
|
<Avatar
|
||||||
|
text={node.user?.shortName ?? node.num.toString()}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</SidebarSection>
|
</SidebarSection>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
<div className="flex flex-col flex-grow">
|
<div className="flex flex-col flex-grow">
|
||||||
@@ -76,7 +85,7 @@ export const MessagesPage = (): JSX.Element => {
|
|||||||
chatType === "broadcast" && currentChannel
|
chatType === "broadcast" && currentChannel
|
||||||
? getChannelName(currentChannel)
|
? getChannelName(currentChannel)
|
||||||
: chatType === "direct" && nodes.get(activeChat)
|
: chatType === "direct" && nodes.get(activeChat)
|
||||||
? nodes.get(activeChat)?.user?.longName ?? nodeHex
|
? (nodes.get(activeChat)?.user?.longName ?? nodeHex)
|
||||||
: "Loading..."
|
: "Loading..."
|
||||||
}`}
|
}`}
|
||||||
actions={
|
actions={
|
||||||
@@ -146,3 +155,5 @@ export const MessagesPage = (): JSX.Element => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default MessagesPage;
|
||||||
|
|||||||
+9
-4
@@ -6,11 +6,10 @@ import { Mono } from "@components/generic/Mono.tsx";
|
|||||||
import { Table } from "@components/generic/Table/index.tsx";
|
import { Table } from "@components/generic/Table/index.tsx";
|
||||||
import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.tsx";
|
import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.tsx";
|
||||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
|
||||||
import { Protobuf } from "@meshtastic/js";
|
import { Protobuf } from "@meshtastic/js";
|
||||||
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
|
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
|
||||||
import { LockIcon, LockOpenIcon, TrashIcon } from "lucide-react";
|
import { LockIcon, LockOpenIcon, TrashIcon } from "lucide-react";
|
||||||
import { Fragment } from "react";
|
import { Fragment, type JSX } from "react";
|
||||||
import { base16 } from "rfc4648";
|
import { base16 } from "rfc4648";
|
||||||
|
|
||||||
export interface DeleteNoteDialogProps {
|
export interface DeleteNoteDialogProps {
|
||||||
@@ -18,7 +17,7 @@ export interface DeleteNoteDialogProps {
|
|||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NodesPage = (): JSX.Element => {
|
const NodesPage = (): JSX.Element => {
|
||||||
const { nodes, hardware, setDialogOpen } = useDevice();
|
const { nodes, hardware, setDialogOpen } = useDevice();
|
||||||
const { setNodeNumToBeRemoved } = useAppStore();
|
const { setNodeNumToBeRemoved } = useAppStore();
|
||||||
|
|
||||||
@@ -44,7 +43,11 @@ export const NodesPage = (): JSX.Element => {
|
|||||||
{ title: "Remove", type: "normal", sortable: false },
|
{ title: "Remove", type: "normal", sortable: false },
|
||||||
]}
|
]}
|
||||||
rows={filteredNodes.map((node) => [
|
rows={filteredNodes.map((node) => [
|
||||||
<Hashicon key="icon" size={24} value={node.num.toString()} />,
|
<span
|
||||||
|
key={node.num}
|
||||||
|
className="h-3 w-3 rounded-full bg-accent"
|
||||||
|
/>,
|
||||||
|
|
||||||
<h1 key="header">
|
<h1 key="header">
|
||||||
{node.user?.longName ??
|
{node.user?.longName ??
|
||||||
(node.user?.macaddr
|
(node.user?.macaddr
|
||||||
@@ -111,3 +114,5 @@ export const NodesPage = (): JSX.Element => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default NodesPage;
|
||||||
|
|||||||
Reference in New Issue
Block a user