Browse Source

Merge branch 'master' of github.com:meshtastic/meshtastic-web

pull/39/head
Sacha Weatherstone 4 years ago
parent
commit
b8ce40e1aa
No known key found for this signature in database GPG Key ID: 7AB2D7E206124B31
  1. 6
      index.html
  2. 25
      package.json
  3. 1346
      pnpm-lock.yaml
  4. 5
      src/App.tsx
  5. 3
      src/DeviceWrapper.tsx
  6. 6
      src/PageRouter.tsx
  7. 7
      src/components/Dialog/HelpDialog.tsx
  8. 2
      src/components/Dialog/PeersDialog.tsx
  9. 5
      src/components/Dialog/QRDialog.tsx
  10. 100
      src/components/PageComponents/Config/Bluetooth.tsx
  11. 2
      src/components/PageComponents/Config/Device.tsx
  12. 2
      src/components/PageComponents/Config/Display.tsx
  13. 2
      src/components/PageComponents/Config/LoRa.tsx
  14. 2
      src/components/PageComponents/Config/Position.tsx
  15. 2
      src/components/PageComponents/Config/Power.tsx
  16. 23
      src/components/PageComponents/Config/User.tsx
  17. 2
      src/components/PageComponents/Config/WiFi.tsx
  18. 2
      src/components/PageComponents/ModuleConfig/CannedMessage.tsx
  19. 2
      src/components/PageComponents/ModuleConfig/ExternalNotification.tsx
  20. 2
      src/components/PageComponents/ModuleConfig/MQTT.tsx
  21. 2
      src/components/PageComponents/ModuleConfig/RangeTest.tsx
  22. 2
      src/components/PageComponents/ModuleConfig/Serial.tsx
  23. 2
      src/components/PageComponents/ModuleConfig/StoreForward.tsx
  24. 2
      src/components/PageComponents/ModuleConfig/Telemetry.tsx
  25. 2
      src/components/Progress.tsx
  26. 4
      src/components/SlideSheets/PeerInfo.tsx
  27. 5
      src/components/form/Form.tsx
  28. 4
      src/components/layout/Header.tsx
  29. 4
      src/components/layout/Sidebar/DeviceCard.tsx
  30. 4
      src/components/layout/Sidebar/index.tsx
  31. 13
      src/core/providers/useDevice.ts
  32. 61
      src/core/stores/deviceStore.ts
  33. 3
      src/core/subscriptions.ts
  34. 7
      src/core/utils/fetcher.ts
  35. 1
      src/index.tsx
  36. 2
      src/pages/Channels/Channel.tsx
  37. 2
      src/pages/Channels/index.tsx
  38. 9
      src/pages/Config/DeviceConfig.tsx
  39. 2
      src/pages/Config/ModuleConfig.tsx
  40. 2
      src/pages/Extensions/Environment.tsx
  41. 17
      src/pages/Extensions/FileBrowser.tsx
  42. 2
      src/pages/Extensions/Index.tsx
  43. 2
      src/pages/Info/index.tsx
  44. 231
      src/pages/Map/index.tsx
  45. 2
      src/pages/Messages/ChannelChat.tsx
  46. 2
      src/pages/Messages/Message.tsx
  47. 2
      src/pages/Messages/NewLocationMessage.tsx
  48. 2
      src/pages/Messages/index.tsx
  49. 14
      src/validation/config/bluetooth.ts
  50. 11
      src/validation/config/user.ts
  51. 45
      types/static.d.ts
  52. 81
      vite.config.ts

6
index.html

@ -4,8 +4,8 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/icon.svg" /> <link rel="icon" type="image/svg+xml" href="/icon.svg" />
<link rel="manifest" href="/site.webmanifest" /> <link rel="manifest" href="site.webmanifest" />
<link rel="mask-icon" href="/Logo_Black.svg" color="#67ea94" /> <link rel="mask-icon" href="Logo_Black.svg" color="#67ea94" />
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" /> <link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
<link <link
rel="stylesheet" rel="stylesheet"
@ -26,6 +26,6 @@
<body> <body>
<div id="root"></div> <div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<script type="module" src="/src/index.tsx"></script> <script type="module" src="src/index.tsx"></script>
</body> </body>
</html> </html>

25
package.json

@ -8,8 +8,7 @@
"build": "tsc && vite build", "build": "tsc && vite build",
"preview": "vite preview", "preview": "vite preview",
"package": "gzipper c -i html,js,css,png,ico,svg,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ $(ls ./dist/output/)", "package": "gzipper c -i html,js,css,png,ico,svg,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ $(ls ./dist/output/)",
"format": "prettier --write 'src/**/*.{ts,tsx}' && eslint src/*.{ts,tsx}", "format": "prettier --write 'src/**/*.{ts,tsx}' && eslint src/*.{ts,tsx}"
"check": "unimported"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -20,35 +19,34 @@
}, },
"homepage": "https://meshtastic.org", "homepage": "https://meshtastic.org",
"dependencies": { "dependencies": {
"@arcgis/core": "^4.24.7",
"@emeraldpay/hashicon-react": "^0.5.2", "@emeraldpay/hashicon-react": "^0.5.2",
"@hookform/resolvers": "^2.9.7", "@hookform/resolvers": "^2.9.7",
"@meshtastic/eslint-config": "^1.0.8", "@meshtastic/eslint-config": "^1.0.8",
"@meshtastic/meshtasticjs": "^0.6.88", "@meshtastic/meshtasticjs": "^0.6.92",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.13.2", "class-validator": "^0.13.2",
"evergreen-ui": "^6.10.3", "evergreen-ui": "^6.10.3",
"geodesy": "^2.4.0", "geodesy": "^2.4.0",
"immer": "^9.0.15", "immer": "^9.0.15",
"mapbox-gl": "npm:[email protected]",
"maplibre-gl": "^2.3.0",
"modern-css-reset": "^1.4.0", "modern-css-reset": "^1.4.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hook-form": "^7.34.0", "react-hook-form": "^7.34.2",
"react-icons": "^4.4.0", "react-icons": "^4.4.0",
"react-json-pretty": "^2.2.0", "react-json-pretty": "^2.2.0",
"react-qrcode-logo": "^2.7.0", "react-map-gl": "^7.0.19",
"react-qrcode-logo": "^2.8.0",
"rfc4648": "^1.5.2", "rfc4648": "^1.5.2",
"snarkdown": "^2.0.0", "zustand": "4.1.0"
"swr": "^1.3.0",
"vite-plugin-environment": "^1.1.2",
"zustand": "4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/chrome": "^0.0.193", "@types/chrome": "^0.0.193",
"@types/geodesy": "^2.2.3", "@types/geodesy": "^2.2.3",
"@types/node": "^18.7.1", "@types/node": "^18.7.6",
"@types/react": "^18.0.17", "@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@types/w3c-web-serial": "^1.0.2", "@types/w3c-web-serial": "^1.0.2",
@ -59,8 +57,7 @@
"tar": "^6.1.11", "tar": "^6.1.11",
"tslib": "^2.4.0", "tslib": "^2.4.0",
"typescript": "^4.7.4", "typescript": "^4.7.4",
"unimported": "^1.21.0", "vite": "^3.0.8",
"vite": "^3.0.6", "vite-plugin-environment": "^1.1.2"
"vite-plugin-cdn-import": "^0.3.5"
} }
} }

1346
pnpm-lock.yaml

File diff suppressed because it is too large

5
src/App.tsx

@ -1,6 +1,7 @@
import type React from "react"; import type React from "react";
import { Pane } from "evergreen-ui"; import { Pane } from "evergreen-ui";
import { MapProvider } from "react-map-gl";
import { AppLayout } from "@components/layout/AppLayout.js"; import { AppLayout } from "@components/layout/AppLayout.js";
@ -10,7 +11,9 @@ export const App = (): JSX.Element => {
return ( return (
<Pane display="flex"> <Pane display="flex">
<AppLayout> <AppLayout>
<PageRouter /> <MapProvider>
<PageRouter />
</MapProvider>
</AppLayout> </AppLayout>
</Pane> </Pane>
); );

3
src/DeviceWrapper.tsx

@ -1,6 +1,7 @@
import type React from "react"; 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 { export interface DeviceProps {
children: React.ReactNode; children: React.ReactNode;

6
src/PageRouter.tsx

@ -1,10 +1,12 @@
import type React from "react"; 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 { ChannelsPage } from "./pages/Channels/index.js";
import { ConfigPage } from "./pages/Config/index.js"; import { ConfigPage } from "./pages/Config/index.js";
import { ExtensionsPage } from "./pages/Extensions/Index.js"; import { ExtensionsPage } from "./pages/Extensions/Index.js";
import { InfoPage } from "./pages/Info/index.js"; import { InfoPage } from "./pages/Info/index.js";
import { MapPage } from "./pages/Map/index.js";
import { MessagesPage } from "./pages/Messages/index.js"; import { MessagesPage } from "./pages/Messages/index.js";
export const PageRouter = (): JSX.Element => { export const PageRouter = (): JSX.Element => {
@ -12,7 +14,7 @@ export const PageRouter = (): JSX.Element => {
return ( return (
<> <>
{activePage === "messages" && <MessagesPage />} {activePage === "messages" && <MessagesPage />}
{/* {activePage === "map" && <MapPage />} */} {activePage === "map" && <MapPage />}
{activePage === "extensions" && <ExtensionsPage />} {activePage === "extensions" && <ExtensionsPage />}
{activePage === "config" && <ConfigPage />} {activePage === "config" && <ConfigPage />}
{activePage === "channels" && <ChannelsPage />} {activePage === "channels" && <ChannelsPage />}

7
src/components/Dialog/HelpDialog.tsx

@ -0,0 +1,7 @@
import type React from "react";
import { Pane } from "evergreen-ui";
export const HelpDialog = (): JSX.Element => {
return <Pane></Pane>;
};

2
src/components/Dialog/PeersDialog.tsx

@ -11,8 +11,8 @@ import {
Tooltip, Tooltip,
} from "evergreen-ui"; } from "evergreen-ui";
import { useDevice } from "@app/core/stores/deviceStore.js";
import { toMGRS } from "@app/core/utils/toMGRS.js"; import { toMGRS } from "@app/core/utils/toMGRS.js";
import { useDevice } from "@core/providers/useDevice.js";
import { Hashicon } from "@emeraldpay/hashicon-react"; import { Hashicon } from "@emeraldpay/hashicon-react";
import { Protobuf } from "@meshtastic/meshtasticjs"; import { Protobuf } from "@meshtastic/meshtasticjs";

5
src/components/Dialog/QRDialog.tsx

@ -44,7 +44,10 @@ export const QRDialog = ({
settings: channelsToEncode, settings: channelsToEncode,
}) })
); );
const base64 = fromByteArray(encoded); const base64 = fromByteArray(encoded)
.replace(/=/g, "")
.replace(/\+/g, "-")
.replace(/\//g, "_");
setQRCodeURL(`https://www.meshtastic.org/e/#${base64}`); setQRCodeURL(`https://www.meshtastic.org/e/#${base64}`);
}, [channels, selectedChannels, loraConfig]); }, [channels, selectedChannels, loraConfig]);

100
src/components/PageComponents/Config/Bluetooth.tsx

@ -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>
);
};

2
src/components/PageComponents/Config/Device.tsx

@ -12,7 +12,7 @@ import { Controller, useForm } from "react-hook-form";
import { DeviceValidation } from "@app/validation/config/device.js"; import { DeviceValidation } from "@app/validation/config/device.js";
import { Form } from "@components/form/Form"; 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 { renderOptions } from "@core/utils/selectEnumOptions.js";
import { classValidatorResolver } from "@hookform/resolvers/class-validator"; import { classValidatorResolver } from "@hookform/resolvers/class-validator";
import { Protobuf } from "@meshtastic/meshtasticjs"; import { Protobuf } from "@meshtastic/meshtasticjs";

2
src/components/PageComponents/Config/Display.tsx

@ -6,7 +6,7 @@ import { Controller, useForm } from "react-hook-form";
import { DisplayValidation } from "@app/validation/config/display.js"; import { DisplayValidation } from "@app/validation/config/display.js";
import { Form } from "@components/form/Form"; 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 { renderOptions } from "@core/utils/selectEnumOptions.js";
import { classValidatorResolver } from "@hookform/resolvers/class-validator"; import { classValidatorResolver } from "@hookform/resolvers/class-validator";
import { Protobuf } from "@meshtastic/meshtasticjs"; import { Protobuf } from "@meshtastic/meshtasticjs";

2
src/components/PageComponents/Config/LoRa.tsx

@ -6,7 +6,7 @@ import { Controller, useForm } from "react-hook-form";
import { LoRaValidation } from "@app/validation/config/lora.js"; import { LoRaValidation } from "@app/validation/config/lora.js";
import { Form } from "@components/form/Form"; 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 { renderOptions } from "@core/utils/selectEnumOptions.js";
import { classValidatorResolver } from "@hookform/resolvers/class-validator"; import { classValidatorResolver } from "@hookform/resolvers/class-validator";
import { Protobuf } from "@meshtastic/meshtasticjs"; import { Protobuf } from "@meshtastic/meshtasticjs";

2
src/components/PageComponents/Config/Position.tsx

@ -12,7 +12,7 @@ import { Controller, useForm } from "react-hook-form";
import { PositionValidation } from "@app/validation/config/position.js"; import { PositionValidation } from "@app/validation/config/position.js";
import { Form } from "@components/form/Form"; 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 { bitwiseDecode } from "@core/utils/bitwise";
import { classValidatorResolver } from "@hookform/resolvers/class-validator"; import { classValidatorResolver } from "@hookform/resolvers/class-validator";
import { Protobuf } from "@meshtastic/meshtasticjs"; import { Protobuf } from "@meshtastic/meshtasticjs";

2
src/components/PageComponents/Config/Power.tsx

@ -6,7 +6,7 @@ import { Controller, useForm } from "react-hook-form";
import { PowerValidation } from "@app/validation/config/power.js"; import { PowerValidation } from "@app/validation/config/power.js";
import { Form } from "@components/form/Form"; 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 { renderOptions } from "@core/utils/selectEnumOptions.js";
import { classValidatorResolver } from "@hookform/resolvers/class-validator"; import { classValidatorResolver } from "@hookform/resolvers/class-validator";
import { Protobuf } from "@meshtastic/meshtasticjs"; import { Protobuf } from "@meshtastic/meshtasticjs";

23
src/components/PageComponents/Config/User.tsx

@ -7,7 +7,7 @@ import { base16 } from "rfc4648";
import { UserValidation } from "@app/validation/config/user.js"; import { UserValidation } from "@app/validation/config/user.js";
import { Form } from "@components/form/Form"; 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 { renderOptions } from "@core/utils/selectEnumOptions.js";
import { classValidatorResolver } from "@hookform/resolvers/class-validator"; import { classValidatorResolver } from "@hookform/resolvers/class-validator";
import { Protobuf } from "@meshtastic/meshtasticjs"; import { Protobuf } from "@meshtastic/meshtasticjs";
@ -103,27 +103,6 @@ export const User = (): JSX.Element => {
)} )}
/> />
</FormField> </FormField>
<TextInputField
label="Transmit Power"
description="This is a description."
hint="dBm"
type="number"
{...register("txPowerDbm", { valueAsNumber: true })}
/>
<TextInputField
label="Antenna Gain"
description="This is a description."
hint="dBi"
type="number"
{...register("antGainDbi", { valueAsNumber: true })}
/>
<TextInputField
label="Antenna Azimuth"
description="This is a description."
hint="°"
type="number"
{...register("antAzimuth", { valueAsNumber: true })}
/>
</Form> </Form>
); );
}; };

2
src/components/PageComponents/Config/WiFi.tsx

@ -13,7 +13,7 @@ import { Controller, useForm, useWatch } from "react-hook-form";
import { renderOptions } from "@app/core/utils/selectEnumOptions.js"; import { renderOptions } from "@app/core/utils/selectEnumOptions.js";
import { WiFiValidation } from "@app/validation/config/wifi.js"; import { WiFiValidation } from "@app/validation/config/wifi.js";
import { Form } from "@components/form/Form"; 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 { classValidatorResolver } from "@hookform/resolvers/class-validator";
import { Protobuf } from "@meshtastic/meshtasticjs"; import { Protobuf } from "@meshtastic/meshtasticjs";

2
src/components/PageComponents/ModuleConfig/CannedMessage.tsx

@ -6,7 +6,7 @@ import { Controller, useForm, useWatch } from "react-hook-form";
import { CannedMessageValidation } from "@app/validation/moduleConfig/cannedMessage.js"; import { CannedMessageValidation } from "@app/validation/moduleConfig/cannedMessage.js";
import { Form } from "@components/form/Form"; 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 { renderOptions } from "@core/utils/selectEnumOptions.js";
import { classValidatorResolver } from "@hookform/resolvers/class-validator"; import { classValidatorResolver } from "@hookform/resolvers/class-validator";
import { Protobuf } from "@meshtastic/meshtasticjs"; import { Protobuf } from "@meshtastic/meshtasticjs";

2
src/components/PageComponents/ModuleConfig/ExternalNotification.tsx

@ -6,7 +6,7 @@ import { Controller, useForm, useWatch } from "react-hook-form";
import { ExternalNotificationValidation } from "@app/validation/moduleConfig/externalNotification.js"; import { ExternalNotificationValidation } from "@app/validation/moduleConfig/externalNotification.js";
import { Form } from "@components/form/Form"; 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 { classValidatorResolver } from "@hookform/resolvers/class-validator";
export const ExternalNotification = (): JSX.Element => { export const ExternalNotification = (): JSX.Element => {

2
src/components/PageComponents/ModuleConfig/MQTT.tsx

@ -6,7 +6,7 @@ import { Controller, useForm, useWatch } from "react-hook-form";
import { MQTTValidation } from "@app/validation/moduleConfig/mqtt.js"; import { MQTTValidation } from "@app/validation/moduleConfig/mqtt.js";
import { Form } from "@components/form/Form"; 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 { classValidatorResolver } from "@hookform/resolvers/class-validator";
export const MQTT = (): JSX.Element => { export const MQTT = (): JSX.Element => {

2
src/components/PageComponents/ModuleConfig/RangeTest.tsx

@ -6,7 +6,7 @@ import { Controller, useForm, useWatch } from "react-hook-form";
import { RangeTestValidation } from "@app/validation/moduleConfig/rangeTest.js"; import { RangeTestValidation } from "@app/validation/moduleConfig/rangeTest.js";
import { Form } from "@components/form/Form"; 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 { classValidatorResolver } from "@hookform/resolvers/class-validator";
export const RangeTest = (): JSX.Element => { export const RangeTest = (): JSX.Element => {

2
src/components/PageComponents/ModuleConfig/Serial.tsx

@ -6,7 +6,7 @@ import { Controller, useForm, useWatch } from "react-hook-form";
import { SerialValidation } from "@app/validation/moduleConfig/serial.js"; import { SerialValidation } from "@app/validation/moduleConfig/serial.js";
import { Form } from "@components/form/Form"; 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 { classValidatorResolver } from "@hookform/resolvers/class-validator";
export const Serial = (): JSX.Element => { export const Serial = (): JSX.Element => {

2
src/components/PageComponents/ModuleConfig/StoreForward.tsx

@ -6,7 +6,7 @@ import { Controller, useForm, useWatch } from "react-hook-form";
import { StoreForwardValidation } from "@app/validation/moduleConfig/storeForward.js"; import { StoreForwardValidation } from "@app/validation/moduleConfig/storeForward.js";
import { Form } from "@components/form/Form"; 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 { classValidatorResolver } from "@hookform/resolvers/class-validator";
export const StoreForward = (): JSX.Element => { export const StoreForward = (): JSX.Element => {

2
src/components/PageComponents/ModuleConfig/Telemetry.tsx

@ -6,7 +6,7 @@ import { Controller, useForm } from "react-hook-form";
import { TelemetryValidation } from "@app/validation/moduleConfig/telemetry.js"; import { TelemetryValidation } from "@app/validation/moduleConfig/telemetry.js";
import { Form } from "@components/form/Form"; 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 { classValidatorResolver } from "@hookform/resolvers/class-validator";
export const Telemetry = (): JSX.Element => { export const Telemetry = (): JSX.Element => {

2
src/components/Progress.tsx

@ -9,7 +9,7 @@ import {
StatusIndicator, StatusIndicator,
} from "evergreen-ui"; } from "evergreen-ui";
import { useDevice } from "@app/core/stores/deviceStore.js"; import { useDevice } from "@core/providers/useDevice.js";
export const Progress = (): JSX.Element => { export const Progress = (): JSX.Element => {
const { const {

4
src/components/SlideSheets/PeerInfo.tsx

@ -3,7 +3,8 @@ import { useEffect, useState } from "react";
import { GeolocationIcon, Pane, PropertyIcon, SideSheet } from "evergreen-ui"; 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 { Hashicon } from "@emeraldpay/hashicon-react";
import { Protobuf } from "@meshtastic/meshtasticjs"; import { Protobuf } from "@meshtastic/meshtasticjs";
@ -14,7 +15,6 @@ import { Overview } from "./tabs/nodes/Overview.js";
export const PeerInfo = () => { export const PeerInfo = () => {
const { peerInfoOpen, activePeer, setPeerInfoOpen, nodes } = useDevice(); const { peerInfoOpen, activePeer, setPeerInfoOpen, nodes } = useDevice();
const [selectedTab, setSelectedTab] = useState(0);
const [node, setNode] = useState<Node | undefined>(); const [node, setNode] = useState<Node | undefined>();
useEffect(() => { useEffect(() => {

5
src/components/form/Form.tsx

@ -1,8 +1,7 @@
import type React from "react"; import type React from "react";
import type { HTMLProps } from "react"; import type { HTMLProps } from "react";
import { Button, majorScale, Pane, Spinner } from "evergreen-ui"; import { Button, majorScale, Pane, SavedIcon, Spinner } from "evergreen-ui";
import { FiSave } from "react-icons/fi";
export interface FormProps extends HTMLProps<HTMLFormElement> { export interface FormProps extends HTMLProps<HTMLFormElement> {
onSubmit: (event: React.FormEvent<HTMLFormElement>) => Promise<void>; onSubmit: (event: React.FormEvent<HTMLFormElement>) => Promise<void>;
@ -39,7 +38,7 @@ export const Form = ({
type="submit" type="submit"
marginLeft="auto" marginLeft="auto"
disabled={!dirty} disabled={!dirty}
iconBefore={<FiSave />} iconBefore={<SavedIcon />}
> >
Save Save
</Button> </Button>

4
src/components/layout/Header.tsx

@ -55,12 +55,12 @@ export const Header = (): JSX.Element => {
width={majorScale(12)} width={majorScale(12)}
marginRight={majorScale(22)} marginRight={majorScale(22)}
> >
<Link href="/"> <Link href=".">
<Pane <Pane
is="img" is="img"
width={100} width={100}
height={28} height={28}
src="/Logo_Black.svg" src="Logo_Black.svg"
cursor="pointer" cursor="pointer"
/> />
</Link> </Link>

4
src/components/layout/Sidebar/DeviceCard.tsx

@ -11,7 +11,7 @@ import {
import { FiBluetooth, FiTerminal, FiWifi } from "react-icons/fi"; import { FiBluetooth, FiTerminal, FiWifi } from "react-icons/fi";
import { toMGRS } from "@app/core/utils/toMGRS.js"; 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 { Hashicon } from "@emeraldpay/hashicon-react";
import { Types } from "@meshtastic/meshtasticjs"; import { Types } from "@meshtastic/meshtasticjs";
@ -33,7 +33,7 @@ export const DeviceCard = (): JSX.Element => {
<Heading>{myNode?.data.user?.longName}</Heading> <Heading>{myNode?.data.user?.longName}</Heading>
<Link <Link
target="_blank" target="_blank"
href="https://github.com/meshtastic/meshtastic-web/releases/" href="https://github.com/meshtastic/meshtastic-device/releases/"
> >
<Badge <Badge
color="green" color="green"

4
src/components/layout/Sidebar/index.tsx

@ -17,7 +17,8 @@ import {
} from "evergreen-ui"; } from "evergreen-ui";
import { PeersDialog } from "@app/components/Dialog/PeersDialog.js"; 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"; import { DeviceCard } from "./DeviceCard.js";
@ -42,7 +43,6 @@ export const Sidebar = (): JSX.Element => {
name: "Map", name: "Map",
icon: GlobeIcon, icon: GlobeIcon,
page: "map", page: "map",
disabled: true,
}, },
{ {
name: "Extensions", name: "Extensions",

13
src/core/providers/useDevice.ts

@ -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;
};

61
src/core/stores/deviceStore.ts

@ -44,10 +44,10 @@ export interface Device {
activePage: Page; activePage: Page;
peerInfoOpen: boolean; peerInfoOpen: boolean;
activePeer: number; activePeer: number;
waypoints: Protobuf.Location[];
setReady(ready: boolean): void; setReady(ready: boolean): void;
setStatus: (status: Types.DeviceStatusEnum) => void; setStatus: (status: Types.DeviceStatusEnum) => void;
addChannel: (channel: Channel) => void;
setConfig: (config: Protobuf.Config) => void; setConfig: (config: Protobuf.Config) => void;
setModuleConfig: (config: Protobuf.ModuleConfig) => void; setModuleConfig: (config: Protobuf.ModuleConfig) => void;
setHardware: (hardware: Protobuf.MyNodeInfo) => void; setHardware: (hardware: Protobuf.MyNodeInfo) => void;
@ -55,6 +55,8 @@ export interface Device {
setActivePage: (page: Page) => void; setActivePage: (page: Page) => void;
setPeerInfoOpen: (open: boolean) => void; setPeerInfoOpen: (open: boolean) => void;
setActivePeer: (peer: number) => void; setActivePeer: (peer: number) => void;
addChannel: (channel: Channel) => void;
addWaypoint: (waypoint: Protobuf.Location) => void;
addNodeInfo: (nodeInfo: Types.NodeInfoPacket) => void; addNodeInfo: (nodeInfo: Types.NodeInfoPacket) => void;
addUser: (user: Types.UserPacket) => void; addUser: (user: Types.UserPacket) => void;
addPosition: (position: Types.PositionPacket) => void; addPosition: (position: Types.PositionPacket) => void;
@ -92,6 +94,7 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
activePage: "messages", activePage: "messages",
peerInfoOpen: false, peerInfoOpen: false,
activePeer: 0, activePeer: 0,
waypoints: [],
setReady: (ready: boolean) => { setReady: (ready: boolean) => {
set( set(
@ -113,25 +116,6 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
}) })
); );
}, },
addChannel: (channel: Channel) => {
set(
produce<DeviceState>((draft) => {
const device = draft.devices.get(id);
if (device) {
const channelIndex = device.channels.findIndex(
(c) => c.config.index === channel.config.index
);
if (channelIndex !== -1) {
const messages = device.channels[channelIndex].messages;
device.channels[channelIndex] = channel;
device.channels[channelIndex].messages = messages;
} else {
device.channels.push(channel);
}
}
})
);
},
setConfig: (config: Protobuf.Config) => { setConfig: (config: Protobuf.Config) => {
set( set(
produce<DeviceState>((draft) => { produce<DeviceState>((draft) => {
@ -257,6 +241,43 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
}) })
); );
}, },
addChannel: (channel: Channel) => {
set(
produce<DeviceState>((draft) => {
const device = draft.devices.get(id);
if (device) {
const channelIndex = device.channels.findIndex(
(c) => c.config.index === channel.config.index
);
if (channelIndex !== -1) {
const messages = device.channels[channelIndex].messages;
device.channels[channelIndex] = channel;
device.channels[channelIndex].messages = messages;
} else {
device.channels.push(channel);
}
}
})
);
},
addWaypoint: (waypoint: Protobuf.Location) => {
set(
produce<DeviceState>((draft) => {
const device = draft.devices.get(id);
if (device) {
const waypointIndex = device.waypoints.findIndex(
(wp) => wp.id === waypoint.id
);
if (waypointIndex !== -1) {
device.waypoints[waypointIndex] = waypoint;
} else {
device.waypoints.push(waypoint);
}
}
})
);
},
addNodeInfo: (nodeInfo) => { addNodeInfo: (nodeInfo) => {
set( set(
produce<DeviceState>((draft) => { produce<DeviceState>((draft) => {

3
src/core/subscriptions.ts

@ -71,5 +71,8 @@ export const subscribeAll = (device: Device, connection: IConnection) => {
? new Date(messagePacket.packet.rxTime * 1000) ? new Date(messagePacket.packet.rxTime * 1000)
: new Date(), : new Date(),
}); });
if (messagePacket.location) {
device.addWaypoint(messagePacket.location);
}
}); });
}; };

7
src/core/utils/fetcher.ts

@ -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
src/index.tsx

@ -1,4 +1,5 @@
import "modern-css-reset/dist/reset.min.css"; import "modern-css-reset/dist/reset.min.css";
import "maplibre-gl/dist/maplibre-gl.css";
import type React from "react"; import type React from "react";
import { StrictMode } from "react"; import { StrictMode } from "react";

2
src/pages/Channels/Channel.tsx

@ -19,7 +19,7 @@ import {
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { Form } from "@components/form/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"; import { Protobuf } from "@meshtastic/meshtasticjs";
export interface SettingsPanelProps { export interface SettingsPanelProps {

2
src/pages/Channels/index.tsx

@ -6,7 +6,7 @@ import { IoQrCodeOutline } from "react-icons/io5";
import { QRDialog } from "@app/components/Dialog/QRDialog.js"; import { QRDialog } from "@app/components/Dialog/QRDialog.js";
import { TabbedContent, TabType } from "@components/layout/page/TabbedContent"; 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 { Protobuf } from "@meshtastic/meshtasticjs";
import { Channel } from "./Channel.js"; import { Channel } from "./Channel.js";

9
src/pages/Config/DeviceConfig.tsx

@ -3,7 +3,7 @@ import { useState } from "react";
import { Pane, Tab, Tablist } from "evergreen-ui"; 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 { Device } from "@components/PageComponents/Config/Device.js";
import { Display } from "@components/PageComponents/Config/Display.js"; import { Display } from "@components/PageComponents/Config/Display.js";
import { LoRa } from "@components/PageComponents/Config/LoRa.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 { Power } from "@components/PageComponents/Config/Power.js";
import { User } from "@components/PageComponents/Config/User.js"; import { User } from "@components/PageComponents/Config/User.js";
import { WiFi } from "@components/PageComponents/Config/WiFi.js"; import { WiFi } from "@components/PageComponents/Config/WiFi.js";
import { useDevice } from "@core/providers/useDevice.js";
export const DeviceConfig = (): JSX.Element => { export const DeviceConfig = (): JSX.Element => {
const [selectedIndex, setSelectedIndex] = useState(0); const [selectedIndex, setSelectedIndex] = useState(0);
@ -46,8 +47,10 @@ export const DeviceConfig = (): JSX.Element => {
label: "LoRa", label: "LoRa",
element: LoRa, element: LoRa,
}, },
// Channels {
// Interface label: "Bluetooth",
element: Bluetooth,
},
]; ];
return ( return (

2
src/pages/Config/ModuleConfig.tsx

@ -43,8 +43,6 @@ export const ModuleConfig = (): JSX.Element => {
label: "Canned Message", label: "Canned Message",
element: CannedMessage, element: CannedMessage,
}, },
// Channels
// Interface
]; ];
return ( return (

2
src/pages/Extensions/Environment.tsx

@ -2,7 +2,7 @@ import type React from "react";
import { Pane } from "evergreen-ui"; 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 => { export const Environment = (): JSX.Element => {
const { nodes } = useDevice(); const { nodes } = useDevice();

17
src/pages/Extensions/FileBrowser.tsx

@ -1,9 +1,7 @@
import type React from "react"; import type React from "react";
import { useEffect, useState } from "react";
import { Pane } from "evergreen-ui"; import { Pane } from "evergreen-ui";
import useSWR from "swr";
import { fetcher } from "@core/utils/fetcher";
export interface File { export interface File {
nameModified: string; nameModified: string;
@ -24,10 +22,15 @@ export interface Files {
} }
export const FileBrowser = (): JSX.Element => { export const FileBrowser = (): JSX.Element => {
const { data } = useSWR<Files>( const [data, setData] = useState<Files>();
"http://meshtastic.local/json/fs/browse/static",
fetcher useEffect(() => {
); void fetch("http://meshtastic.local/json/fs/browse/static").then(
async (res) => {
setData((await res.json()) as Files);
}
);
});
return ( return (
<Pane> <Pane>

2
src/pages/Extensions/Index.tsx

@ -2,8 +2,8 @@ import type React from "react";
import { DocumentIcon, GanttChartIcon, RainIcon } from "evergreen-ui"; import { DocumentIcon, GanttChartIcon, RainIcon } from "evergreen-ui";
import { useDevice } from "@app/core/stores/deviceStore.js";
import { TabbedContent, TabType } from "@components/layout/page/TabbedContent"; import { TabbedContent, TabType } from "@components/layout/page/TabbedContent";
import { useDevice } from "@core/providers/useDevice.js";
import { FileBrowser } from "@pages/Extensions/FileBrowser"; import { FileBrowser } from "@pages/Extensions/FileBrowser";
import { Environment } from "./Environment.js"; import { Environment } from "./Environment.js";

2
src/pages/Info/index.tsx

@ -3,7 +3,7 @@ import type React from "react";
import { Pane } from "evergreen-ui"; import { Pane } from "evergreen-ui";
import JSONPretty from "react-json-pretty"; 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 => { export const InfoPage = (): JSX.Element => {
const { hardware, nodes } = useDevice(); const { hardware, nodes } = useDevice();

231
src/pages/Map/index.tsx

@ -1,137 +1,112 @@
import type React from "react"; import type React from "react";
import { useEffect, useMemo, useRef } from "react";
import { Pane } from "evergreen-ui"; import {
Heading,
IconButton,
LocateIcon,
majorScale,
MapMarkerIcon,
Pane,
Text,
} from "evergreen-ui";
import maplibregl from "maplibre-gl";
import { Map, Marker, useMap } from "react-map-gl";
import { useDevice } from "@app/core/stores/deviceStore.js"; import { useDevice } from "@core/providers/useDevice.js";
import Point from "@arcgis/core/geometry/Point"; import { Hashicon } from "@emeraldpay/hashicon-react";
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";
export const MapPage = (): JSX.Element => { export const MapPage = (): JSX.Element => {
const { nodes } = useDevice(); const { nodes, waypoints } = useDevice();
const { current: map } = useMap();
const nodesWithPosition = nodes.filter((node) => node.data.position); return (
const ref = useRef<HTMLDivElement>(null); <Pane
margin={majorScale(3)}
borderRadius={majorScale(1)}
elevation={1}
display="flex"
flexGrow={1}
flexDirection="column"
gap={majorScale(2)}
overflow="hidden"
position="relative"
>
<Pane
position="absolute"
zIndex={10}
right={0}
top={0}
borderRadius={majorScale(1)}
padding={majorScale(1)}
margin={majorScale(1)}
background="tint1"
width={majorScale(28)}
elevation={1}
overflow="hidden"
>
<Pane padding={majorScale(1)} background="tint2">
<Heading>Title</Heading>
</Pane>
<Pane display="flex" flexDirection="column" gap={majorScale(1)}>
{nodes.map((n) => (
<Pane key={n.data.num} display="flex" gap={majorScale(1)}>
<Hashicon value={n.data.num.toString()} size={24} />
<Text>{n.data.user?.longName}</Text>
<IconButton
icon={LocateIcon}
marginLeft="auto"
size="small"
onClick={() => {
console.log("clicked");
console.log(map);
useEffect(() => { map?.flyTo({
console.log(nodesWithPosition); center: [
}, [nodesWithPosition]); n.data.position?.latitudeI / 1e7,
n.data.position?.longitudeI / 1e7,
const labelClass = useMemo( ],
() => zoom: 10,
new LabelClass({ });
labelExpressionInfo: { }}
expression: "$feature.name", />
}, </Pane>
symbol: new LabelSymbol3D({ ))}
symbolLayers: [ </Pane>
new TextSymbol3DLayer({ </Pane>
text: "{name}", <Map
material: { mapStyle="https://raw.githubusercontent.com/hc-oss/maplibre-gl-styles/master/styles/osm-mapnik/v8/default.json"
color: "black", mapLib={maplibregl}
}, attributionControl={false}
halo: { >
color: [255, 255, 255, 0.7], {waypoints.map((wp) => (
size: 2, <Marker
}, key={wp.id}
font: { longitude={wp.longitudeI / 1e7}
size: 12, latitude={wp.latitudeI / 1e7}
weight: "bold", anchor="bottom"
}, >
size: 10, <Pane>
}), <MapMarkerIcon />
], </Pane>
verticalOffset: { </Marker>
screenLength: 150, ))}
maxWorldLength: 2000, {nodes
minWorldLength: 30, .filter((n) => n.data.position?.latitudeI)
}, .map((n) => {
callout: new LineCallout3D({ if (n.data.position?.latitudeI) {
size: 0.5, return (
color: [0, 0, 0], <Marker
border: { key={n.data.num}
color: [255, 255, 255], longitude={n.data.position.longitudeI / 1e7}
}, latitude={n.data.position.latitudeI / 1e7}
}), anchor="bottom"
}), >
}), <Hashicon value={n.data.num.toString()} size={32} />
[] </Marker>
); );
}
const points: Graphic[] = nodesWithPosition.map( })}
(node, index) => </Map>
node.data.position </Pane>
? 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
); );
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",
},
],
});
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} />;
}; };

2
src/pages/Messages/ChannelChat.tsx

@ -12,8 +12,8 @@ import {
Tooltip, Tooltip,
} from "evergreen-ui"; } from "evergreen-ui";
import { useDevice } from "@core/providers/useDevice.js";
import type { Channel } from "@core/stores/deviceStore.js"; import type { Channel } from "@core/stores/deviceStore.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Message } from "./Message.js"; import { Message } from "./Message.js";
import { NewLocationMessage } from "./NewLocationMessage.js"; import { NewLocationMessage } from "./NewLocationMessage.js";

2
src/pages/Messages/Message.tsx

@ -10,7 +10,7 @@ import {
Text, Text,
} from "evergreen-ui"; } 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 { Hashicon } from "@emeraldpay/hashicon-react";
import type { Protobuf, Types } from "@meshtastic/meshtasticjs"; import type { Protobuf, Types } from "@meshtastic/meshtasticjs";

2
src/pages/Messages/NewLocationMessage.tsx

@ -8,8 +8,8 @@ import {
TextInputField, TextInputField,
} from "evergreen-ui"; } from "evergreen-ui";
import { useDevice } from "@app/core/stores/deviceStore.js";
import { renderOptions } from "@app/core/utils/selectEnumOptions.js"; import { renderOptions } from "@app/core/utils/selectEnumOptions.js";
import { useDevice } from "@core/providers/useDevice.js";
import { Protobuf } from "@meshtastic/meshtasticjs"; import { Protobuf } from "@meshtastic/meshtasticjs";
enum LocationType { enum LocationType {

2
src/pages/Messages/index.tsx

@ -13,7 +13,7 @@ import {
TabbedContent, TabbedContent,
TabType, TabType,
} from "@components/layout/page/TabbedContent.js"; } 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 { Protobuf } from "@meshtastic/meshtasticjs";
import { ChannelChat } from "./ChannelChat.js"; import { ChannelChat } from "./ChannelChat.js";

14
src/validation/config/bluetooth.ts

@ -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;
}

11
src/validation/config/user.ts

@ -1,4 +1,4 @@
import { IsBoolean, IsInt, Length } from "class-validator"; import { IsBoolean, Length } from "class-validator";
import type { Protobuf } from "@meshtastic/meshtasticjs"; import type { Protobuf } from "@meshtastic/meshtasticjs";
@ -16,13 +16,4 @@ export class UserValidation
@IsBoolean() @IsBoolean()
isLicensed: boolean; isLicensed: boolean;
@IsInt()
txPowerDbm: number;
@IsInt()
antGainDbi: number;
@IsInt()
antAzimuth: number;
} }

45
types/static.d.ts

@ -4,71 +4,58 @@
///<reference types="chrome"/> ///<reference types="chrome"/>
/* CSS MODULES */ /* CSS MODULES */
declare module '*.module.css' { declare module "*.module.css" {
const classes: { [key: string]: string }; const classes: { [key: string]: string };
export default classes; export default classes;
} }
declare module '*.module.scss' { declare module "*.module.scss" {
const classes: { [key: string]: string }; const classes: { [key: string]: string };
export default classes; export default classes;
} }
declare module '*.module.sass' { declare module "*.module.sass" {
const classes: { [key: string]: string }; const classes: { [key: string]: string };
export default classes; export default classes;
} }
declare module '*.module.less' { declare module "*.module.less" {
const classes: { [key: string]: string }; const classes: { [key: string]: string };
export default classes; export default classes;
} }
declare module '*.module.styl' { declare module "*.module.styl" {
const classes: { [key: string]: string }; const classes: { [key: string]: string };
export default classes; export default classes;
} }
/* CSS */ /* CSS */
declare module '*.css'; declare module "*.css";
declare module '*.scss'; declare module "*.scss";
declare module '*.sass'; declare module "*.sass";
declare module '*.less'; declare module "*.less";
declare module '*.styl'; declare module "*.styl";
/* IMAGES */ /* IMAGES */
declare module '*.svg' { declare module "*.svg" {
const ref: string; const ref: string;
export default ref; export default ref;
} }
declare module '*.bmp' { declare module "*.bmp" {
const ref: string; const ref: string;
export default ref; export default ref;
} }
declare module '*.gif' { declare module "*.gif" {
const ref: string; const ref: string;
export default ref; export default ref;
} }
declare module '*.jpg' { declare module "*.jpg" {
const ref: string; const ref: string;
export default ref; export default ref;
} }
declare module '*.jpeg' { declare module "*.jpeg" {
const ref: string; const ref: string;
export default ref; export default ref;
} }
declare module '*.png' { declare module "*.png" {
const ref: string; const ref: string;
export default ref; export default ref;
} }
/* CUSTOM: ADD YOUR OWN HERE */ /* CUSTOM: ADD YOUR OWN HERE */
type Primitive = string | number | boolean | symbol | undefined | null;
type DeepOmitHelper<T, K extends keyof T> = {
[P in K]: T[P] extends infer TP //extra level of indirection needed to trigger homomorhic behavior // distribute over unions
? TP extends Primitive
? TP // leave primitives and functions alone
: DeepOmit<TP, K>
: never;
};
type DeepOmit<T, K> = T extends Primitive
? T
: DeepOmitHelper<T, Exclude<keyof T, K>>;

81
vite.config.ts

@ -1,18 +1,17 @@
import { execSync } from 'child_process'; import { execSync } from "child_process";
import { resolve } from 'path'; import { resolve } from "path";
import { visualizer } from 'rollup-plugin-visualizer'; import { visualizer } from "rollup-plugin-visualizer";
import { defineConfig } from 'vite'; import { defineConfig } from "vite";
import importToCDN from 'vite-plugin-cdn-import'; import EnvironmentPlugin from "vite-plugin-environment";
import EnvironmentPlugin from 'vite-plugin-environment';
import react from '@vitejs/plugin-react'; import react from "@vitejs/plugin-react";
let hash = ''; let hash = "";
try { try {
hash = execSync('git rev-parse --short HEAD').toString().trim(); hash = execSync("git rev-parse --short HEAD").toString().trim();
} catch (error) { } catch (error) {
hash = 'DEVELOPMENT'; hash = "DEVELOPMENT";
} }
export default defineConfig({ export default defineConfig({
@ -21,69 +20,21 @@ export default defineConfig({
EnvironmentPlugin({ EnvironmentPlugin({
COMMIT_HASH: hash, COMMIT_HASH: hash,
}), }),
importToCDN({
modules: [
// {
// name: 'mapbox-gl',
// var: 'mapboxgl',
// path: `dist/mapbox-gl.js`,
// },
// autoComplete('@arcgis/core'),
],
}),
// VitePWA({
// mode: 'production',
// includeAssets: [
// 'favicon.svg',
// 'favicon.ico',
// 'robots.txt',
// 'touch-icon.png',
// ],
// manifest: {
// name: 'Meshtastic Web',
// short_name: 'Meshtastic',
// description: 'Meshtastic Web App',
// theme_color: '#67ea94',
// icons: [
// {
// src: 'android-192.png',
// sizes: '192x192',
// type: 'image/png',
// },
// {
// src: 'android-512.png',
// sizes: '512x512',
// type: 'image/png',
// },
// {
// src: 'android-512.png',
// sizes: '512x512',
// type: 'image/png',
// purpose: 'any maskable',
// },
// ],
// },
// workbox: {
// sourcemap: true,
// },
// }),
], ],
build: { build: {
target: 'esnext', target: "esnext",
assetsDir: '', assetsDir: "",
rollupOptions: { rollupOptions: {
plugins: [visualizer()], plugins: [visualizer()],
}, },
}, },
resolve: { resolve: {
alias: { alias: {
'@app': resolve(__dirname, './src'), "@app": resolve(__dirname, "./src"),
'@pages': resolve(__dirname, './src/pages'), "@pages": resolve(__dirname, "./src/pages"),
'@components': resolve(__dirname, './src/components'), "@components": resolve(__dirname, "./src/components"),
'@core': resolve(__dirname, './src/core'), "@core": resolve(__dirname, "./src/core"),
'@layouts': resolve(__dirname, './src/layouts'), "@layouts": resolve(__dirname, "./src/layouts"),
}, },
}, },
}); });

Loading…
Cancel
Save