chore: update packages to latest version. feat: replace hashicon lib with vanilla component

This commit is contained in:
Dan Ditomaso
2025-01-28 12:15:04 -05:00
parent 2808b6f26a
commit 9b843f6483
20 changed files with 2437 additions and 2666 deletions
+1 -4
View File
@@ -7,10 +7,7 @@ export interface DeviceWrapperProps {
device?: Device;
}
export const DeviceWrapper = ({
children,
device,
}: DeviceWrapperProps): JSX.Element => {
export const DeviceWrapper = ({ children, device }: DeviceWrapperProps) => {
return (
<DeviceContext.Provider value={device}>{children}</DeviceContext.Provider>
);
+6 -6
View File
@@ -1,11 +1,11 @@
import { useDevice } from "@core/stores/deviceStore.ts";
import { ChannelsPage } from "@pages/Channels.tsx";
import { ConfigPage } from "@pages/Config/index.tsx";
import { MapPage } from "@pages/Map.tsx";
import { MessagesPage } from "@pages/Messages.tsx";
import { NodesPage } from "@pages/Nodes.tsx";
import ChannelsPage from "@pages/Channels.tsx";
import ConfigPage from "@pages/Config/index.tsx";
import MapPage from "@pages/Map.tsx";
import MessagesPage from "@pages/Messages.tsx";
import NodesPage from "@pages/Nodes.tsx";
export const PageRouter = (): JSX.Element => {
export const PageRouter = () => {
const { activePage } = useDevice();
return (
<>
+8 -6
View File
@@ -1,3 +1,4 @@
import { Avatar } from "@components/UI/Avatar";
import {
CommandDialog,
CommandEmpty,
@@ -8,7 +9,6 @@ import {
} from "@components/UI/Command.tsx";
import { useAppStore } from "@core/stores/appStore.ts";
import { useDevice, useDeviceStore } from "@core/stores/deviceStore.ts";
import { Hashicon } from "@emeraldpay/hashicon-react";
import { useCommandState } from "cmdk";
import {
ArrowLeftRightIcon,
@@ -51,11 +51,11 @@ export interface Command {
export interface SubItem {
label: string;
icon: JSX.Element;
icon: React.ReactNode;
action: () => void;
}
export const CommandPalette = (): JSX.Element => {
export const CommandPalette = () => {
const {
commandPaletteOpen,
setCommandPaletteOpen,
@@ -125,9 +125,11 @@ export const CommandPalette = (): JSX.Element => {
device.nodes.get(device.hardware.myNodeNum)?.user?.longName ??
device.hardware.myNodeNum.toString(),
icon: (
<Hashicon
size={16}
value={device.hardware.myNodeNum.toString()}
<Avatar
text={
device.nodes.get(device.hardware.myNodeNum)?.user
?.shortName ?? device.hardware.myNodeNum.toString()
}
/>
),
action() {
+8 -4
View File
@@ -3,7 +3,6 @@ import { Separator } from "@components/UI/Seperator.tsx";
import { Code } from "@components/UI/Typography/Code.tsx";
import { useAppStore } from "@core/stores/appStore.ts";
import { useDeviceStore } from "@core/stores/deviceStore.ts";
import { Hashicon } from "@emeraldpay/hashicon-react";
import {
HomeIcon,
LanguagesIcon,
@@ -12,6 +11,8 @@ import {
SearchIcon,
SunIcon,
} from "lucide-react";
import type { JSX } from "react";
import { Avatar } from "./UI/Avatar";
export const DeviceSelector = (): JSX.Element => {
const { getDevices } = useDeviceStore();
@@ -44,9 +45,12 @@ export const DeviceSelector = (): JSX.Element => {
}}
active={selectedDevice === device.id}
>
<Hashicon
size={24}
value={device.hardware.myNodeNum.toString()}
<Avatar
text={
device.nodes
.get(device.hardware.myNodeNum)
?.user?.shortName.toString() ?? "UNK"
}
/>
</DeviceSelectorButton>
))}
+3 -3
View File
@@ -8,15 +8,15 @@ export const DeviceSelectorButton = ({
active,
onClick,
children,
}: DeviceSelectorButtonProps): JSX.Element => (
}: DeviceSelectorButtonProps) => (
<li
className="aspect-w-1 aspect-h-1 relative w-full"
onClick={onClick}
onKeyDown={onClick}
>
{active && (
{/* {active && (
<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">
{children}
</div>
+1 -1
View File
@@ -51,7 +51,7 @@ export function SelectInput<T extends FieldValues>({
</SelectTrigger>
<SelectContent>
{optionsEnumValues.map(([name, value]) => (
<SelectItem key={name} value={value.toString()}>
<SelectItem key={name + value} value={value.toString()}>
{formatEnumName
? name
.replace(/_/g, " ")
+3 -3
View File
@@ -15,9 +15,9 @@ export const FieldWrapper = ({
children,
valid,
validationText,
}: FieldWrapperProps): JSX.Element => (
}: FieldWrapperProps) => (
<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">
<Label>{label}</Label>
<div className="sm:col-span-2">
@@ -32,6 +32,6 @@ export const FieldWrapper = ({
</div>
</div>
</div>
</div>
</fieldset>
</div>
);
@@ -1,9 +1,9 @@
import { Separator } from "@app/components/UI/Seperator";
import { H5 } from "@app/components/UI/Typography/H5.tsx";
import { Subtle } from "@app/components/UI/Typography/Subtle.tsx";
import { Avatar } from "@components/UI/Avatar";
import { Mono } from "@components/generic/Mono.tsx";
import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.tsx";
import { Hashicon } from "@emeraldpay/hashicon-react";
import { Protobuf } from "@meshtastic/js";
import type { Protobuf as ProtobufType } from "@meshtastic/js";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
@@ -23,7 +23,7 @@ export interface NodeDetailProps {
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 hardwareType = Protobuf.Mesh.HardwareModel[
node.user?.hwModel ?? 0
@@ -33,7 +33,7 @@ export const NodeDetail = ({ node }: NodeDetailProps): JSX.Element => {
<div className="dark:text-black">
<div className="flex gap-2">
<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>
{node.user?.publicKey && node.user?.publicKey.length > 0 ? (
@@ -1,5 +1,5 @@
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 {
AlertCircleIcon,
@@ -13,11 +13,7 @@ export interface MessageProps {
sender?: Protobuf.Mesh.NodeInfo;
}
export const Message = ({
lastMsgSameUser,
message,
sender,
}: MessageProps): JSX.Element => {
export const Message = ({ lastMsgSameUser, message, sender }: MessageProps) => {
return lastMsgSameUser ? (
<div className="ml-5 flex">
{message.state === "ack" ? (
@@ -39,7 +35,7 @@ export const Message = ({
<div className="mx-4 mt-2 gap-2">
<div className="flex gap-2">
<div className="w-6 cursor-pointer">
<Hashicon value={(sender?.num ?? 0).toString()} size={32} />
<Avatar text={sender?.user?.shortName ?? "UNK"} />
</div>
<span className="cursor-pointer font-medium text-textPrimary">
{sender?.user?.longName ?? "UNK"}
+94
View File
@@ -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>
);
};
+2 -2
View File
@@ -9,9 +9,9 @@ export interface SidebarSectionProps {
export const SidebarSection = ({
label: title,
children,
}: SidebarSectionProps): JSX.Element => (
}: SidebarSectionProps) => (
<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>
);
+6 -9
View File
@@ -74,15 +74,12 @@ export const Table = ({ headings, rows }: TableProps): JSX.Element => {
>
<div className="flex gap-2">
{heading.title}
{sortColumn === heading.title && (
<>
{sortOrder === "asc" ? (
<ChevronUpIcon size={16} />
) : (
<ChevronDownIcon size={16} />
)}
</>
)}
{sortColumn === heading.title &&
(sortOrder === "asc" ? (
<ChevronUpIcon size={16} />
) : (
<ChevronDownIcon size={16} />
))}
</div>
</th>
))}
-1
View File
@@ -97,6 +97,5 @@
}
img {
-drag: none;
-webkit-user-drag: none;
}
+2 -1
View File
@@ -20,7 +20,7 @@ export const getChannelName = (channel: Protobuf.Channel.Channel) =>
? "Primary"
: `Ch ${channel.index}`;
export const ChannelsPage = (): JSX.Element => {
const ChannelsPage = () => {
const { channels, setDialogOpen } = useDevice();
const [activeChannel, setActiveChannel] = useState<Types.ChannelNumber>(
Types.ChannelNumber.Primary,
@@ -69,3 +69,4 @@ export const ChannelsPage = (): JSX.Element => {
</>
);
};
export default ChannelsPage;
+3 -1
View File
@@ -9,7 +9,7 @@ import { ModuleConfig } from "@pages/Config/ModuleConfig.tsx";
import { BoxesIcon, SaveIcon, SettingsIcon } from "lucide-react";
import { useState } from "react";
export const ConfigPage = (): JSX.Element => {
const ConfigPage = (): JSX.Element => {
const { workingConfig, workingModuleConfig, connection } = useDevice();
const [activeConfigSection, setActiveConfigSection] = useState<
"device" | "module"
@@ -72,3 +72,5 @@ export const ConfigPage = (): JSX.Element => {
</>
);
};
export default ConfigPage;
+15 -10
View File
@@ -1,4 +1,5 @@
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 { cn } from "@app/core/utils/cn.ts";
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 { useAppStore } from "@core/stores/appStore.ts";
import { useDevice } from "@core/stores/deviceStore.ts";
import { Hashicon } from "@emeraldpay/hashicon-react";
import type { Protobuf } from "@meshtastic/js";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { bbox, lineString } from "@turf/turf";
@@ -17,11 +17,11 @@ import {
ZoomInIcon,
ZoomOutIcon,
} 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 MapGl from "react-map-gl/maplibre";
export const MapPage = (): JSX.Element => {
const MapPage = (): JSX.Element => {
const { nodes, waypoints } = useDevice();
const { rasterSources, darkMode } = useAppStore();
const { default: map } = useMap();
@@ -30,7 +30,7 @@ export const MapPage = (): JSX.Element => {
const [selectedNode, setSelectedNode] =
useState<Protobuf.Mesh.NodeInfo | null>(null);
const allNodes = Array.from(nodes.values());
const allNodes = useMemo(() => Array.from(nodes.values()), [nodes]);
const getBBox = useCallback(() => {
if (!map) {
@@ -135,9 +135,7 @@ export const MapPage = (): JSX.Element => {
renderWorldCopies={false}
maxPitch={0}
style={{
filter: darkMode
? "brightness(0.6) invert(1) contrast(3) hue-rotate(200deg) saturate(0.3) brightness(0.7)"
: "",
filter: darkMode ? "brightness(0.8)" : "",
}}
dragRotate={false}
touchZoomRotate={false}
@@ -177,7 +175,7 @@ export const MapPage = (): JSX.Element => {
key={node.num}
longitude={(node.position.longitudeI ?? 0) / 1e7}
latitude={(node.position.latitudeI ?? 0) / 1e7}
style={{ filter: darkMode ? "invert(1)" : "" }}
// style={{ filter: darkMode ? "invert(1)" : "" }}
anchor="bottom"
onClick={() => {
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">
<Hashicon value={node.num.toString()} size={22} />
<div className="flex cursor-pointer gap-2 rounded-md bg-transparent p-1.5">
<Avatar
text={
node.user?.shortName.toString() ?? node.num.toString()
}
size="sm"
/>
<Subtle className={cn(zoom < 12 && "hidden")}>
{node.user?.longName ||
`!${numberToHexUnpadded(node.num)}`}
@@ -217,3 +220,5 @@ export const MapPage = (): JSX.Element => {
</>
);
};
export default MapPage;
+27 -16
View File
@@ -1,18 +1,18 @@
import { ChannelChat } from "@components/PageComponents/Messages/ChannelChat.tsx";
import { PageLayout } from "@components/PageLayout.tsx";
import { Sidebar } from "@components/Sidebar.tsx";
import { Avatar } from "@components/UI/Avatar.tsx";
import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx";
import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx";
import { useToast } from "@core/hooks/useToast.ts";
import { useDevice } from "@core/stores/deviceStore.ts";
import { Hashicon } from "@emeraldpay/hashicon-react";
import { Device, useDevice, useDeviceStore } from "@core/stores/deviceStore.ts";
import { Protobuf, Types } from "@meshtastic/js";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { getChannelName } from "@pages/Channels.tsx";
import { HashIcon, LockIcon, LockOpenIcon, WaypointsIcon } from "lucide-react";
import { useState } from "react";
export const MessagesPage = (): JSX.Element => {
const MessagesPage = () => {
const { channels, nodes, hardware, messages, traceroutes, connection } =
useDevice();
const [chatType, setChatType] =
@@ -56,18 +56,27 @@ export const MessagesPage = (): JSX.Element => {
))}
</SidebarSection>
<SidebarSection label="Nodes">
{filteredNodes.map((node) => (
<SidebarButton
key={node.num}
label={node.user?.longName ?? `!${numberToHexUnpadded(node.num)}`}
active={activeChat === node.num}
onClick={() => {
setChatType("direct");
setActiveChat(node.num);
}}
element={<Hashicon size={20} value={node.num.toString()} />}
/>
))}
<div className="flex flex-col gap-4">
{filteredNodes.map((node) => (
<SidebarButton
key={node.num}
label={
node.user?.longName ?? `!${numberToHexUnpadded(node.num)}`
}
active={activeChat === node.num}
onClick={() => {
setChatType("direct");
setActiveChat(node.num);
}}
element={
<Avatar
text={node.user?.shortName ?? node.num.toString()}
size="sm"
/>
}
/>
))}
</div>
</SidebarSection>
</Sidebar>
<div className="flex flex-col flex-grow">
@@ -76,7 +85,7 @@ export const MessagesPage = (): JSX.Element => {
chatType === "broadcast" && currentChannel
? getChannelName(currentChannel)
: chatType === "direct" && nodes.get(activeChat)
? nodes.get(activeChat)?.user?.longName ?? nodeHex
? (nodes.get(activeChat)?.user?.longName ?? nodeHex)
: "Loading..."
}`}
actions={
@@ -146,3 +155,5 @@ export const MessagesPage = (): JSX.Element => {
</>
);
};
export default MessagesPage;
+9 -4
View File
@@ -6,11 +6,10 @@ import { Mono } from "@components/generic/Mono.tsx";
import { Table } from "@components/generic/Table/index.tsx";
import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.tsx";
import { useDevice } from "@core/stores/deviceStore.ts";
import { Hashicon } from "@emeraldpay/hashicon-react";
import { Protobuf } from "@meshtastic/js";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { LockIcon, LockOpenIcon, TrashIcon } from "lucide-react";
import { Fragment } from "react";
import { Fragment, type JSX } from "react";
import { base16 } from "rfc4648";
export interface DeleteNoteDialogProps {
@@ -18,7 +17,7 @@ export interface DeleteNoteDialogProps {
onOpenChange: (open: boolean) => void;
}
export const NodesPage = (): JSX.Element => {
const NodesPage = (): JSX.Element => {
const { nodes, hardware, setDialogOpen } = useDevice();
const { setNodeNumToBeRemoved } = useAppStore();
@@ -44,7 +43,11 @@ export const NodesPage = (): JSX.Element => {
{ title: "Remove", type: "normal", sortable: false },
]}
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">
{node.user?.longName ??
(node.user?.macaddr
@@ -111,3 +114,5 @@ export const NodesPage = (): JSX.Element => {
</>
);
};
export default NodesPage;