Fix/add npm jsr building (#722)

* fixed github workflows to improve handling of mutl runtimes

* updating readme

* Update packages/core/src/meshDevice.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update packages/core/package.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update packages/transport-http/package.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Dan Ditomaso
2025-07-17 20:47:25 -04:00
committed by GitHub
co-authored by Copilot
parent 0e29639222
commit 8a443e9cad
39 changed files with 8448 additions and 2934 deletions
@@ -1,11 +1,11 @@
import { Avatar } from "@components/UI/Avatar.tsx";
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@components/UI/Command.tsx";
import { usePinnedItems } from "@core/hooks/usePinnedItems.ts";
import { useAppStore } from "@core/stores/appStore.ts";
@@ -14,345 +14,345 @@ import { cn } from "@core/utils/cn.ts";
import { useNavigate } from "@tanstack/react-router";
import { useCommandState } from "cmdk";
import {
ArrowLeftRightIcon,
BoxSelectIcon,
BugIcon,
CloudOff,
EraserIcon,
FactoryIcon,
LayersIcon,
LinkIcon,
type LucideIcon,
MapIcon,
MessageSquareIcon,
Pin,
PlusIcon,
PowerIcon,
QrCodeIcon,
RefreshCwIcon,
SettingsIcon,
SmartphoneIcon,
TrashIcon,
UsersIcon,
ArrowLeftRightIcon,
BoxSelectIcon,
BugIcon,
CloudOff,
EraserIcon,
FactoryIcon,
LayersIcon,
LinkIcon,
type LucideIcon,
MapIcon,
MessageSquareIcon,
Pin,
PlusIcon,
PowerIcon,
QrCodeIcon,
RefreshCwIcon,
SettingsIcon,
SmartphoneIcon,
TrashIcon,
UsersIcon,
} from "lucide-react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
export interface Group {
id: string;
label: string;
icon: LucideIcon;
commands: Command[];
id: string;
label: string;
icon: LucideIcon;
commands: Command[];
}
export interface Command {
label: string;
icon: LucideIcon;
action?: () => void;
subItems?: SubItem[];
tags?: string[];
label: string;
icon: LucideIcon;
action?: () => void;
subItems?: SubItem[];
tags?: string[];
}
export interface SubItem {
label: string;
icon: React.ReactNode;
action: () => void;
label: string;
icon: React.ReactNode;
action: () => void;
}
export const CommandPalette = () => {
const {
commandPaletteOpen,
setCommandPaletteOpen,
setConnectDialogOpen,
setSelectedDevice,
} = useAppStore();
const { getDevices } = useDeviceStore();
const { setDialogOpen, getNode, connection } = useDevice();
const { pinnedItems, togglePinnedItem } = usePinnedItems({
storageName: "pinnedCommandMenuGroups",
});
const { t } = useTranslation("commandPalette");
const navigate = useNavigate({ from: "/" });
const {
commandPaletteOpen,
setCommandPaletteOpen,
setConnectDialogOpen,
setSelectedDevice,
} = useAppStore();
const { getDevices } = useDeviceStore();
const { setDialogOpen, getNode, connection } = useDevice();
const { pinnedItems, togglePinnedItem } = usePinnedItems({
storageName: "pinnedCommandMenuGroups",
});
const { t } = useTranslation("commandPalette");
const navigate = useNavigate({ from: "/" });
const groups: Group[] = [
{
id: "gotoGroup",
label: t("goto.label"),
icon: LinkIcon,
commands: [
{
label: t("goto.command.messages"),
icon: MessageSquareIcon,
action() {
navigate({ to: "/messages" });
},
},
{
label: t("goto.command.map"),
icon: MapIcon,
action() {
navigate({ to: "/map" });
},
},
{
label: t("goto.command.config"),
icon: SettingsIcon,
action() {
navigate({ to: "/config" });
},
tags: ["settings"],
},
{
label: t("goto.command.channels"),
icon: LayersIcon,
action() {
navigate({ to: "/channels" });
},
},
{
label: t("goto.command.nodes"),
icon: UsersIcon,
action() {
navigate({ to: "/nodes" });
},
},
],
},
{
id: "manageGroup",
label: t("manage.label"),
icon: SmartphoneIcon,
commands: [
{
label: t("manage.command.switchNode"),
icon: ArrowLeftRightIcon,
subItems: getDevices().map((device) => ({
label:
getNode(device.hardware.myNodeNum)?.user?.longName ??
t("unknown.shortName"),
icon: (
<Avatar
text={
getNode(device.hardware.myNodeNum)?.user?.shortName ??
t("unknown.shortName")
}
/>
),
action() {
setSelectedDevice(device.id);
},
})),
},
{
label: t("manage.command.connectNewNode"),
icon: PlusIcon,
action() {
setConnectDialogOpen(true);
},
},
],
},
{
id: "contextualGroup",
label: t("contextual.label"),
icon: BoxSelectIcon,
commands: [
{
label: t("contextual.command.qrCode"),
icon: QrCodeIcon,
subItems: [
{
label: t("contextual.command.qrGenerator"),
icon: <QrCodeIcon size={16} />,
action() {
setDialogOpen("QR", true);
},
},
{
label: t("contextual.command.qrImport"),
icon: <QrCodeIcon size={16} />,
action() {
setDialogOpen("import", true);
},
},
],
},
{
label: t("contextual.command.scheduleShutdown"),
icon: PowerIcon,
action() {
setDialogOpen("shutdown", true);
},
},
{
label: t("contextual.command.scheduleReboot"),
icon: RefreshCwIcon,
action() {
setDialogOpen("reboot", true);
},
},
{
label: t("contextual.command.rebootToOtaMode"),
icon: RefreshCwIcon,
action() {
setDialogOpen("rebootOTA", true);
},
},
{
label: t("contextual.command.resetNodeDb"),
icon: TrashIcon,
action() {
connection?.resetNodes();
},
},
{
label: t("contextual.command.disconnect"),
icon: CloudOff,
action() {
connection?.disconnect().catch((error) => {
console.error("Failed to disconnect:", error);
});
},
},
{
label: t("contextual.command.factoryResetDevice"),
icon: FactoryIcon,
action() {
connection?.factoryResetDevice();
},
},
{
label: t("contextual.command.factoryResetConfig"),
icon: FactoryIcon,
action() {
connection?.factoryResetConfig();
},
},
],
},
{
id: "debugGroup",
label: t("debug.label"),
icon: BugIcon,
commands: [
{
label: t("debug.command.reconfigure"),
icon: RefreshCwIcon,
action() {
void connection?.configure();
},
},
{
label: t("debug.command.clearAllStoredMessages"),
icon: EraserIcon,
action() {
setDialogOpen("deleteMessages", true);
},
},
],
},
];
const groups: Group[] = [
{
id: "gotoGroup",
label: t("goto.label"),
icon: LinkIcon,
commands: [
{
label: t("goto.command.messages"),
icon: MessageSquareIcon,
action() {
navigate({ to: "/messages" });
},
},
{
label: t("goto.command.map"),
icon: MapIcon,
action() {
navigate({ to: "/map" });
},
},
{
label: t("goto.command.config"),
icon: SettingsIcon,
action() {
navigate({ to: "/config" });
},
tags: ["settings"],
},
{
label: t("goto.command.channels"),
icon: LayersIcon,
action() {
navigate({ to: "/channels" });
},
},
{
label: t("goto.command.nodes"),
icon: UsersIcon,
action() {
navigate({ to: "/nodes" });
},
},
],
},
{
id: "manageGroup",
label: t("manage.label"),
icon: SmartphoneIcon,
commands: [
{
label: t("manage.command.switchNode"),
icon: ArrowLeftRightIcon,
subItems: getDevices().map((device) => ({
label:
getNode(device.hardware.myNodeNum)?.user?.longName ??
t("unknown.shortName"),
icon: (
<Avatar
text={
getNode(device.hardware.myNodeNum)?.user?.shortName ??
t("unknown.shortName")
}
/>
),
action() {
setSelectedDevice(device.id);
},
})),
},
{
label: t("manage.command.connectNewNode"),
icon: PlusIcon,
action() {
setConnectDialogOpen(true);
},
},
],
},
{
id: "contextualGroup",
label: t("contextual.label"),
icon: BoxSelectIcon,
commands: [
{
label: t("contextual.command.qrCode"),
icon: QrCodeIcon,
subItems: [
{
label: t("contextual.command.qrGenerator"),
icon: <QrCodeIcon size={16} />,
action() {
setDialogOpen("QR", true);
},
},
{
label: t("contextual.command.qrImport"),
icon: <QrCodeIcon size={16} />,
action() {
setDialogOpen("import", true);
},
},
],
},
{
label: t("contextual.command.scheduleShutdown"),
icon: PowerIcon,
action() {
setDialogOpen("shutdown", true);
},
},
{
label: t("contextual.command.scheduleReboot"),
icon: RefreshCwIcon,
action() {
setDialogOpen("reboot", true);
},
},
{
label: t("contextual.command.rebootToOtaMode"),
icon: RefreshCwIcon,
action() {
setDialogOpen("rebootOTA", true);
},
},
{
label: t("contextual.command.resetNodeDb"),
icon: TrashIcon,
action() {
connection?.resetNodes();
},
},
{
label: t("contextual.command.disconnect"),
icon: CloudOff,
action() {
connection?.disconnect().catch((error) => {
console.error("Failed to disconnect:", error);
});
},
},
{
label: t("contextual.command.factoryResetDevice"),
icon: FactoryIcon,
action() {
connection?.factoryResetDevice();
},
},
{
label: t("contextual.command.factoryResetConfig"),
icon: FactoryIcon,
action() {
connection?.factoryResetConfig();
},
},
],
},
{
id: "debugGroup",
label: t("debug.label"),
icon: BugIcon,
commands: [
{
label: t("debug.command.reconfigure"),
icon: RefreshCwIcon,
action() {
void connection?.configure();
},
},
{
label: t("debug.command.clearAllStoredMessages"),
icon: EraserIcon,
action() {
setDialogOpen("deleteMessages", true);
},
},
],
},
];
const sortedGroups = [...groups].sort((a, b) => {
const aPinned = pinnedItems.includes(a.id) ? 1 : 0;
const bPinned = pinnedItems.includes(b.id) ? 1 : 0;
return bPinned - aPinned;
});
const sortedGroups = [...groups].sort((a, b) => {
const aPinned = pinnedItems.includes(a.id) ? 1 : 0;
const bPinned = pinnedItems.includes(b.id) ? 1 : 0;
return bPinned - aPinned;
});
useEffect(() => {
const handleKeydown = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setCommandPaletteOpen(true);
}
};
useEffect(() => {
const handleKeydown = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setCommandPaletteOpen(true);
}
};
globalThis.addEventListener("keydown", handleKeydown);
return () => globalThis.removeEventListener("keydown", handleKeydown);
}, [setCommandPaletteOpen]);
globalThis.addEventListener("keydown", handleKeydown);
return () => globalThis.removeEventListener("keydown", handleKeydown);
}, [setCommandPaletteOpen]);
return (
<CommandDialog
open={commandPaletteOpen}
onOpenChange={setCommandPaletteOpen}
>
<CommandInput placeholder={t("search.commandPalette")} />
<CommandList>
<CommandEmpty>{t("emptyState")}</CommandEmpty>
{sortedGroups.map((group) => (
<CommandGroup
key={group.label}
heading={
<div className="flex items-center justify-between">
<span>{group.label}</span>
<button
type="button"
onClick={() => togglePinnedItem(group.id)}
className={cn(
"transition-all duration-300 scale-100 cursor-pointer p-2 focus:*:data-label:opacity-100",
)}
>
<span
data-label
className="transition-all block absolute w-full mb-auto mt-auto ml-0 mr-0 text-xs left-0 -top-5 opacity-0 rounded-lg"
/>
<Pin
size={16}
className={cn(
"transition-opacity",
pinnedItems.includes(group.id)
? "opacity-100 text-red-500"
: "opacity-40 hover:opacity-70",
)}
/>
</button>
</div>
}
>
{group.commands.map((command) => (
<div key={command.label}>
<CommandItem
onSelect={() => {
command.action?.();
setCommandPaletteOpen(false);
}}
>
<command.icon size={16} className="mr-2" />
{command.label}
</CommandItem>
{command.subItems?.map((subItem) => (
<SubItem
key={subItem.label}
label={subItem.label}
icon={subItem.icon}
action={subItem.action}
/>
))}
</div>
))}
</CommandGroup>
))}
</CommandList>
</CommandDialog>
);
return (
<CommandDialog
open={commandPaletteOpen}
onOpenChange={setCommandPaletteOpen}
>
<CommandInput placeholder={t("search.commandPalette")} />
<CommandList>
<CommandEmpty>{t("emptyState")}</CommandEmpty>
{sortedGroups.map((group) => (
<CommandGroup
key={group.label}
heading={
<div className="flex items-center justify-between">
<span>{group.label}</span>
<button
type="button"
onClick={() => togglePinnedItem(group.id)}
className={cn(
"transition-all duration-300 scale-100 cursor-pointer p-2 focus:*:data-label:opacity-100",
)}
>
<span
data-label
className="transition-all block absolute w-full mb-auto mt-auto ml-0 mr-0 text-xs left-0 -top-5 opacity-0 rounded-lg"
/>
<Pin
size={16}
className={cn(
"transition-opacity",
pinnedItems.includes(group.id)
? "opacity-100 text-red-500"
: "opacity-40 hover:opacity-70",
)}
/>
</button>
</div>
}
>
{group.commands.map((command) => (
<div key={command.label}>
<CommandItem
onSelect={() => {
command.action?.();
setCommandPaletteOpen(false);
}}
>
<command.icon size={16} className="mr-2" />
{command.label}
</CommandItem>
{command.subItems?.map((subItem) => (
<SubItem
key={subItem.label}
label={subItem.label}
icon={subItem.icon}
action={subItem.action}
/>
))}
</div>
))}
</CommandGroup>
))}
</CommandList>
</CommandDialog>
);
};
const SubItem = ({
label,
icon,
action,
label,
icon,
action,
}: {
label: string;
icon: React.ReactNode;
action: () => void;
label: string;
icon: React.ReactNode;
action: () => void;
}) => {
const search = useCommandState((state) => state.search);
if (!search) {
return null;
}
const search = useCommandState((state) => state.search);
if (!search) {
return null;
}
return (
<CommandItem onSelect={action}>
{icon}
{label}
</CommandItem>
);
return (
<CommandItem onSelect={action}>
{icon}
{label}
</CommandItem>
);
};