Add maps, Bluetooth config & fixes
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type React from "react";
|
||||
|
||||
import { Device, DeviceContext } from "./core/stores/deviceStore.js";
|
||||
import { DeviceContext } from "./core/providers/useDevice.js";
|
||||
import type { Device } from "./core/stores/deviceStore.js";
|
||||
|
||||
export interface DeviceProps {
|
||||
children: React.ReactNode;
|
||||
|
||||
+4
-2
@@ -1,10 +1,12 @@
|
||||
import type React from "react";
|
||||
|
||||
import { useDevice } from "./core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
|
||||
import { ChannelsPage } from "./pages/Channels/index.js";
|
||||
import { ConfigPage } from "./pages/Config/index.js";
|
||||
import { ExtensionsPage } from "./pages/Extensions/Index.js";
|
||||
import { InfoPage } from "./pages/Info/index.js";
|
||||
import { MapPage } from "./pages/Map/index.js";
|
||||
import { MessagesPage } from "./pages/Messages/index.js";
|
||||
|
||||
export const PageRouter = (): JSX.Element => {
|
||||
@@ -12,7 +14,7 @@ export const PageRouter = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
{activePage === "messages" && <MessagesPage />}
|
||||
{/* {activePage === "map" && <MapPage />} */}
|
||||
{activePage === "map" && <MapPage />}
|
||||
{activePage === "extensions" && <ExtensionsPage />}
|
||||
{activePage === "config" && <ConfigPage />}
|
||||
{activePage === "channels" && <ChannelsPage />}
|
||||
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
Tooltip,
|
||||
} from "evergreen-ui";
|
||||
|
||||
import { useDevice } from "@app/core/stores/deviceStore.js";
|
||||
import { toMGRS } from "@app/core/utils/toMGRS.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import type React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { FormField, SelectField, Switch, TextInputField } from "evergreen-ui";
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
|
||||
import { BluetoothValidation } from "@app/validation/config/bluetooth.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { renderOptions } from "@core/utils/selectEnumOptions.js";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
export const Bluetooth = (): JSX.Element => {
|
||||
const { config, connection } = useDevice();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isDirty },
|
||||
control,
|
||||
reset,
|
||||
} = useForm<BluetoothValidation>({
|
||||
defaultValues: config.bluetooth,
|
||||
resolver: classValidatorResolver(BluetoothValidation),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
reset(config.bluetooth);
|
||||
}, [reset, config.bluetooth]);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
setLoading(true);
|
||||
void connection?.setConfig(
|
||||
{
|
||||
payloadVariant: {
|
||||
oneofKind: "bluetooth",
|
||||
bluetooth: data,
|
||||
},
|
||||
},
|
||||
async () => {
|
||||
reset({ ...data });
|
||||
setLoading(false);
|
||||
await Promise.resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const pairingMode = useWatch({
|
||||
control,
|
||||
name: "mode",
|
||||
defaultValue: Protobuf.Config_BluetoothConfig_PairingMode.RandomPin,
|
||||
});
|
||||
|
||||
return (
|
||||
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
|
||||
<FormField
|
||||
label="Bluetooth Enabled"
|
||||
description="Description"
|
||||
isInvalid={!!errors.enabled?.message}
|
||||
validationMessage={errors.enabled?.message}
|
||||
>
|
||||
<Controller
|
||||
name="enabled"
|
||||
control={control}
|
||||
render={({ field: { value, ...field } }) => (
|
||||
<Switch height={24} marginLeft="auto" checked={value} {...field} />
|
||||
)}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<SelectField
|
||||
label="Pairing mode"
|
||||
description="This is a description."
|
||||
isInvalid={!!errors.mode?.message}
|
||||
validationMessage={errors.mode?.message}
|
||||
{...register("mode", { valueAsNumber: true })}
|
||||
>
|
||||
{renderOptions(Protobuf.Config_BluetoothConfig_PairingMode)}
|
||||
</SelectField>
|
||||
|
||||
<TextInputField
|
||||
display={
|
||||
pairingMode !== Protobuf.Config_BluetoothConfig_PairingMode.FixedPin
|
||||
? "none"
|
||||
: "block"
|
||||
}
|
||||
label="Pin"
|
||||
description="This is a description."
|
||||
type="number"
|
||||
isInvalid={!!errors.fixedPin?.message}
|
||||
validationMessage={errors.fixedPin?.message}
|
||||
{...register("fixedPin", {
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -12,7 +12,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
import { DeviceValidation } from "@app/validation/config/device.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { renderOptions } from "@core/utils/selectEnumOptions.js";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
import { DisplayValidation } from "@app/validation/config/display.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { renderOptions } from "@core/utils/selectEnumOptions.js";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
import { LoRaValidation } from "@app/validation/config/lora.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { renderOptions } from "@core/utils/selectEnumOptions.js";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
import { PositionValidation } from "@app/validation/config/position.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { bitwiseDecode } from "@core/utils/bitwise";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
import { PowerValidation } from "@app/validation/config/power.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { renderOptions } from "@core/utils/selectEnumOptions.js";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
@@ -7,7 +7,7 @@ import { base16 } from "rfc4648";
|
||||
|
||||
import { UserValidation } from "@app/validation/config/user.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { renderOptions } from "@core/utils/selectEnumOptions.js";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import { renderOptions } from "@app/core/utils/selectEnumOptions.js";
|
||||
import { WiFiValidation } from "@app/validation/config/wifi.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
|
||||
import { CannedMessageValidation } from "@app/validation/moduleConfig/cannedMessage.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { renderOptions } from "@core/utils/selectEnumOptions.js";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
|
||||
import { ExternalNotificationValidation } from "@app/validation/moduleConfig/externalNotification.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
|
||||
export const ExternalNotification = (): JSX.Element => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
|
||||
import { MQTTValidation } from "@app/validation/moduleConfig/mqtt.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
|
||||
export const MQTT = (): JSX.Element => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
|
||||
import { RangeTestValidation } from "@app/validation/moduleConfig/rangeTest.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
|
||||
export const RangeTest = (): JSX.Element => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
|
||||
import { SerialValidation } from "@app/validation/moduleConfig/serial.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
|
||||
export const Serial = (): JSX.Element => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
|
||||
import { StoreForwardValidation } from "@app/validation/moduleConfig/storeForward.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
|
||||
export const StoreForward = (): JSX.Element => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
import { TelemetryValidation } from "@app/validation/moduleConfig/telemetry.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
|
||||
export const Telemetry = (): JSX.Element => {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
StatusIndicator,
|
||||
} from "evergreen-ui";
|
||||
|
||||
import { useDevice } from "@app/core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
|
||||
export const Progress = (): JSX.Element => {
|
||||
const {
|
||||
|
||||
@@ -3,7 +3,8 @@ import { useEffect, useState } from "react";
|
||||
|
||||
import { GeolocationIcon, Pane, PropertyIcon, SideSheet } from "evergreen-ui";
|
||||
|
||||
import { Node, useDevice } from "@app/core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import type { Node } from "@core/stores/deviceStore.js";
|
||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
@@ -14,7 +15,6 @@ import { Overview } from "./tabs/nodes/Overview.js";
|
||||
|
||||
export const PeerInfo = () => {
|
||||
const { peerInfoOpen, activePeer, setPeerInfoOpen, nodes } = useDevice();
|
||||
const [selectedTab, setSelectedTab] = useState(0);
|
||||
const [node, setNode] = useState<Node | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type React from "react";
|
||||
import type { HTMLProps } from "react";
|
||||
|
||||
import { Button, majorScale, Pane, Spinner } from "evergreen-ui";
|
||||
import { FiSave } from "react-icons/fi";
|
||||
import { Button, majorScale, Pane, SavedIcon, Spinner } from "evergreen-ui";
|
||||
|
||||
export interface FormProps extends HTMLProps<HTMLFormElement> {
|
||||
onSubmit: (event: React.FormEvent<HTMLFormElement>) => Promise<void>;
|
||||
@@ -39,7 +38,7 @@ export const Form = ({
|
||||
type="submit"
|
||||
marginLeft="auto"
|
||||
disabled={!dirty}
|
||||
iconBefore={<FiSave />}
|
||||
iconBefore={<SavedIcon />}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { FiBluetooth, FiTerminal, FiWifi } from "react-icons/fi";
|
||||
|
||||
import { toMGRS } from "@app/core/utils/toMGRS.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
||||
import { Types } from "@meshtastic/meshtasticjs";
|
||||
|
||||
@@ -33,7 +33,7 @@ export const DeviceCard = (): JSX.Element => {
|
||||
<Heading>{myNode?.data.user?.longName}</Heading>
|
||||
<Link
|
||||
target="_blank"
|
||||
href="https://github.com/meshtastic/meshtastic-web/releases/"
|
||||
href="https://github.com/meshtastic/meshtastic-device/releases/"
|
||||
>
|
||||
<Badge
|
||||
color="green"
|
||||
|
||||
@@ -17,7 +17,8 @@ import {
|
||||
} from "evergreen-ui";
|
||||
|
||||
import { PeersDialog } from "@app/components/Dialog/PeersDialog.js";
|
||||
import { Page, useDevice } from "@app/core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import type { Page } from "@core/stores/deviceStore.js";
|
||||
|
||||
import { DeviceCard } from "./DeviceCard.js";
|
||||
|
||||
@@ -42,7 +43,6 @@ export const Sidebar = (): JSX.Element => {
|
||||
name: "Map",
|
||||
icon: GlobeIcon,
|
||||
page: "map",
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
name: "Extensions",
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import type React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { Map, MapOptions } from "maplibre-gl";
|
||||
|
||||
export interface useMapboxProps {
|
||||
ref: React.RefObject<HTMLDivElement>;
|
||||
style: string;
|
||||
options?: Partial<MapOptions>;
|
||||
}
|
||||
|
||||
export function useCreateMapbox({
|
||||
ref,
|
||||
style,
|
||||
options,
|
||||
}: useMapboxProps): Map | undefined {
|
||||
const [mapInstance, setMapInstance] = useState<Map>();
|
||||
useEffect(() => {
|
||||
const container = ref.current as HTMLDivElement;
|
||||
if (mapInstance || !container) {
|
||||
return;
|
||||
}
|
||||
const map = new Map({
|
||||
container,
|
||||
style,
|
||||
...options,
|
||||
});
|
||||
setMapInstance(map);
|
||||
}, []);
|
||||
|
||||
return mapInstance;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
import type { Device } from "../stores/deviceStore.js";
|
||||
|
||||
export const DeviceContext = createContext<Device | undefined>(undefined);
|
||||
|
||||
export const useDevice = (): Device => {
|
||||
const context = useContext(DeviceContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useDevice must be used within a ConnectionProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
import type { Map } from "maplibre-gl";
|
||||
|
||||
export interface MapContextValue {
|
||||
ref: React.Ref<HTMLDivElement>;
|
||||
map?: Map;
|
||||
}
|
||||
|
||||
export const MapContext = createContext<MapContextValue>({} as MapContextValue);
|
||||
|
||||
export const useMap = (): MapContextValue => {
|
||||
return useContext(MapContext);
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
export const fetcher = async <JSON>(
|
||||
input: RequestInfo,
|
||||
init?: RequestInit
|
||||
): Promise<JSON> => {
|
||||
const res = await fetch(input, init);
|
||||
return res.json() as Promise<JSON>;
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import "modern-css-reset/dist/reset.min.css";
|
||||
import "maplibre-gl/dist/maplibre-gl.css";
|
||||
|
||||
import type React from "react";
|
||||
import { StrictMode } from "react";
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
export interface SettingsPanelProps {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { IoQrCodeOutline } from "react-icons/io5";
|
||||
|
||||
import { QRDialog } from "@app/components/Dialog/QRDialog.js";
|
||||
import { TabbedContent, TabType } from "@components/layout/page/TabbedContent";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
import { Channel } from "./Channel.js";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useState } from "react";
|
||||
|
||||
import { Pane, Tab, Tablist } from "evergreen-ui";
|
||||
|
||||
import { useDevice } from "@app/core/stores/deviceStore.js";
|
||||
import { Bluetooth } from "@app/components/PageComponents/Config/Bluetooth.js";
|
||||
import { Device } from "@components/PageComponents/Config/Device.js";
|
||||
import { Display } from "@components/PageComponents/Config/Display.js";
|
||||
import { LoRa } from "@components/PageComponents/Config/LoRa.js";
|
||||
@@ -11,6 +11,7 @@ import { Position } from "@components/PageComponents/Config/Position.js";
|
||||
import { Power } from "@components/PageComponents/Config/Power.js";
|
||||
import { User } from "@components/PageComponents/Config/User.js";
|
||||
import { WiFi } from "@components/PageComponents/Config/WiFi.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
|
||||
export const DeviceConfig = (): JSX.Element => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
@@ -46,8 +47,10 @@ export const DeviceConfig = (): JSX.Element => {
|
||||
label: "LoRa",
|
||||
element: LoRa,
|
||||
},
|
||||
// Channels
|
||||
// Interface
|
||||
{
|
||||
label: "Bluetooth",
|
||||
element: Bluetooth,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,7 +2,7 @@ import type React from "react";
|
||||
|
||||
import { Pane } from "evergreen-ui";
|
||||
|
||||
import { useDevice } from "@app/core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
|
||||
export const Environment = (): JSX.Element => {
|
||||
const { nodes } = useDevice();
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { Pane } from "evergreen-ui";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { fetcher } from "@core/utils/fetcher";
|
||||
|
||||
export interface File {
|
||||
nameModified: string;
|
||||
@@ -24,10 +22,15 @@ export interface Files {
|
||||
}
|
||||
|
||||
export const FileBrowser = (): JSX.Element => {
|
||||
const { data } = useSWR<Files>(
|
||||
"http://meshtastic.local/json/fs/browse/static",
|
||||
fetcher
|
||||
);
|
||||
const [data, setData] = useState<Files>();
|
||||
|
||||
useEffect(() => {
|
||||
void fetch("http://meshtastic.local/json/fs/browse/static").then(
|
||||
async (res) => {
|
||||
setData((await res.json()) as Files);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Pane>
|
||||
|
||||
@@ -2,8 +2,8 @@ import type React from "react";
|
||||
|
||||
import { DocumentIcon, GanttChartIcon, RainIcon } from "evergreen-ui";
|
||||
|
||||
import { useDevice } from "@app/core/stores/deviceStore.js";
|
||||
import { TabbedContent, TabType } from "@components/layout/page/TabbedContent";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { FileBrowser } from "@pages/Extensions/FileBrowser";
|
||||
|
||||
import { Environment } from "./Environment.js";
|
||||
|
||||
@@ -3,7 +3,7 @@ import type React from "react";
|
||||
import { Pane } from "evergreen-ui";
|
||||
import JSONPretty from "react-json-pretty";
|
||||
|
||||
import { useDevice } from "@app/core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
|
||||
export const InfoPage = (): JSX.Element => {
|
||||
const { hardware, nodes } = useDevice();
|
||||
|
||||
+49
-123
@@ -1,137 +1,63 @@
|
||||
import type React from "react";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
|
||||
import { Pane } from "evergreen-ui";
|
||||
import { majorScale, Pane } from "evergreen-ui";
|
||||
import { Marker } from "maplibre-gl";
|
||||
|
||||
import { useDevice } from "@app/core/stores/deviceStore.js";
|
||||
import Point from "@arcgis/core/geometry/Point";
|
||||
import Graphic from "@arcgis/core/Graphic";
|
||||
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
|
||||
import LabelClass from "@arcgis/core/layers/support/LabelClass";
|
||||
import Map from "@arcgis/core/Map";
|
||||
import LineCallout3D from "@arcgis/core/symbols/callouts/LineCallout3D";
|
||||
import LabelSymbol3D from "@arcgis/core/symbols/LabelSymbol3D";
|
||||
import TextSymbol3DLayer from "@arcgis/core/symbols/TextSymbol3DLayer";
|
||||
import SceneView from "@arcgis/core/views/SceneView";
|
||||
import { useCreateMapbox } from "@app/core/providers/useCreateMapbox.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
|
||||
export const MapPage = (): JSX.Element => {
|
||||
const { nodes } = useDevice();
|
||||
|
||||
const nodesWithPosition = nodes.filter((node) => node.data.position);
|
||||
const nodeMarkers = useMemo(() => new Map<number, Marker>(), []);
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(nodesWithPosition);
|
||||
}, [nodesWithPosition]);
|
||||
|
||||
const labelClass = useMemo(
|
||||
() =>
|
||||
new LabelClass({
|
||||
labelExpressionInfo: {
|
||||
expression: "$feature.name",
|
||||
},
|
||||
symbol: new LabelSymbol3D({
|
||||
symbolLayers: [
|
||||
new TextSymbol3DLayer({
|
||||
text: "{name}",
|
||||
material: {
|
||||
color: "black",
|
||||
},
|
||||
halo: {
|
||||
color: [255, 255, 255, 0.7],
|
||||
size: 2,
|
||||
},
|
||||
font: {
|
||||
size: 12,
|
||||
weight: "bold",
|
||||
},
|
||||
size: 10,
|
||||
}),
|
||||
],
|
||||
verticalOffset: {
|
||||
screenLength: 150,
|
||||
maxWorldLength: 2000,
|
||||
minWorldLength: 30,
|
||||
},
|
||||
callout: new LineCallout3D({
|
||||
size: 0.5,
|
||||
color: [0, 0, 0],
|
||||
border: {
|
||||
color: [255, 255, 255],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const points: Graphic[] = nodesWithPosition.map(
|
||||
(node, index) =>
|
||||
node.data.position
|
||||
? new Graphic({
|
||||
geometry: new Point({
|
||||
latitude: node.data.position.latitudeI / 1e7,
|
||||
longitude: node.data.position.longitudeI / 1e7,
|
||||
}),
|
||||
attributes: {
|
||||
ObjectID: index,
|
||||
name: node.data.user?.longName,
|
||||
},
|
||||
})
|
||||
: new Graphic() //should be undefined/removed from array
|
||||
);
|
||||
const map = useCreateMapbox({
|
||||
ref,
|
||||
style:
|
||||
"https://raw.githubusercontent.com/hc-oss/maplibre-gl-styles/master/styles/osm-mapnik/v8/default.json",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
const layer = new FeatureLayer({
|
||||
labelsVisible: true,
|
||||
labelingInfo: [labelClass],
|
||||
source: points,
|
||||
fields: [
|
||||
{
|
||||
name: "ObjectID",
|
||||
alias: "ObjectID",
|
||||
type: "oid",
|
||||
},
|
||||
{
|
||||
name: "name",
|
||||
alias: "Name",
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
});
|
||||
nodes.map((n) => {
|
||||
if (n.data.position?.longitudeI && n.data.position?.latitudeI && map) {
|
||||
if (nodeMarkers.has(n.data.num)) {
|
||||
nodeMarkers
|
||||
.get(n.data.num)
|
||||
?.setLngLat([
|
||||
n.data.position?.longitudeI / 1e7,
|
||||
n.data.position?.latitudeI / 1e7,
|
||||
]);
|
||||
} else {
|
||||
nodeMarkers.set(
|
||||
n.data.num,
|
||||
new Marker()
|
||||
.setLngLat([
|
||||
n.data.position?.longitudeI / 1e7,
|
||||
n.data.position?.latitudeI / 1e7,
|
||||
])
|
||||
.addTo(map)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [map, nodeMarkers, nodes]);
|
||||
|
||||
const map = new Map({
|
||||
basemap: "satellite",
|
||||
ground: "world-elevation",
|
||||
layers: [layer],
|
||||
});
|
||||
|
||||
const scene = new SceneView({
|
||||
container: ref.current,
|
||||
map: map,
|
||||
camera: {
|
||||
position: nodesWithPosition[0]
|
||||
? {
|
||||
x: nodesWithPosition[0].data.position?.longitudeI ?? 0 / 1e7,
|
||||
y: nodesWithPosition[0].data.position?.latitudeI ?? 0 / 1e7,
|
||||
z: nodesWithPosition[0].data.position?.altitude ?? 0 / 1e7,
|
||||
}
|
||||
: {
|
||||
y: -35.59, //Longitude
|
||||
x: 148, //Latitude
|
||||
z: 200, //Meters
|
||||
},
|
||||
tilt: 75,
|
||||
},
|
||||
});
|
||||
scene.on("click", (event) => {
|
||||
void scene.hitTest(event).then((point) => {
|
||||
console.log(point);
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [labelClass, points]);
|
||||
|
||||
return <Pane width="100%" height="100%" ref={ref} />;
|
||||
return (
|
||||
<Pane
|
||||
margin={majorScale(3)}
|
||||
borderRadius={majorScale(1)}
|
||||
background="white"
|
||||
elevation={1}
|
||||
display="flex"
|
||||
flexGrow={1}
|
||||
flexDirection="column"
|
||||
gap={majorScale(2)}
|
||||
overflow="hidden"
|
||||
>
|
||||
<Pane width="100%" height="100%" ref={ref} />
|
||||
</Pane>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,8 +12,8 @@ import {
|
||||
Tooltip,
|
||||
} from "evergreen-ui";
|
||||
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import type { Channel } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
|
||||
import { Message } from "./Message.js";
|
||||
import { NewLocationMessage } from "./NewLocationMessage.js";
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Text,
|
||||
} from "evergreen-ui";
|
||||
|
||||
import { useDevice } from "@app/core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
||||
import type { Protobuf, Types } from "@meshtastic/meshtasticjs";
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
TextInputField,
|
||||
} from "evergreen-ui";
|
||||
|
||||
import { useDevice } from "@app/core/stores/deviceStore.js";
|
||||
import { renderOptions } from "@app/core/utils/selectEnumOptions.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
enum LocationType {
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
TabbedContent,
|
||||
TabType,
|
||||
} from "@components/layout/page/TabbedContent.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
import { ChannelChat } from "./ChannelChat.js";
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { IsBoolean, IsEnum, IsInt } from "class-validator";
|
||||
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
export class BluetoothValidation implements Protobuf.Config_BluetoothConfig {
|
||||
@IsBoolean()
|
||||
enabled: boolean;
|
||||
|
||||
@IsEnum(Protobuf.Config_BluetoothConfig_PairingMode)
|
||||
mode: Protobuf.Config_BluetoothConfig_PairingMode;
|
||||
|
||||
@IsInt()
|
||||
fixedPin: number;
|
||||
}
|
||||
Reference in New Issue
Block a user