WIP 1.3
This commit is contained in:
+13
-12
@@ -22,18 +22,18 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emeraldpay/hashicon-react": "^0.5.2",
|
"@emeraldpay/hashicon-react": "^0.5.2",
|
||||||
"@meshtastic/eslint-config": "^1.0.7",
|
"@meshtastic/eslint-config": "^1.0.7",
|
||||||
"@meshtastic/meshtasticjs": "^0.6.51",
|
"@meshtastic/meshtasticjs": "^0.6.54",
|
||||||
"@reduxjs/toolkit": "^1.8.0",
|
"@reduxjs/toolkit": "^1.8.0",
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"framer-motion": "^6.2.8",
|
"framer-motion": "^6.2.8",
|
||||||
"mapbox-gl": "^2.7.0",
|
"mapbox-gl": "^2.7.1",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.6.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-error-boundary": "^3.1.4",
|
"react-error-boundary": "^3.1.4",
|
||||||
"react-flow-renderer": "^10.0.0-next.51",
|
"react-flow-renderer": "^10.0.6",
|
||||||
"react-hook-form": "^7.27.1",
|
"react-hook-form": "^7.28.1",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-json-pretty": "^2.2.0",
|
"react-json-pretty": "^2.2.0",
|
||||||
"react-multi-select-component": "^4.2.3",
|
"react-multi-select-component": "^4.2.3",
|
||||||
@@ -48,23 +48,24 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hookform/devtools": "^4.0.2",
|
"@hookform/devtools": "^4.0.2",
|
||||||
|
"@types/chrome": "^0.0.180",
|
||||||
"@types/mapbox-gl": "^2.6.3",
|
"@types/mapbox-gl": "^2.6.3",
|
||||||
"@types/react": "^17.0.39",
|
"@types/react": "^17.0.43",
|
||||||
"@types/react-dom": "^17.0.13",
|
"@types/react-dom": "^17.0.14",
|
||||||
"@types/w3c-web-serial": "^1.0.2",
|
"@types/w3c-web-serial": "^1.0.2",
|
||||||
"@types/web-bluetooth": "^0.0.12",
|
"@types/web-bluetooth": "^0.0.14",
|
||||||
"@vitejs/plugin-react": "^1.2.0",
|
"@vitejs/plugin-react": "^1.2.0",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.4",
|
||||||
"gzipper": "^7.0.0",
|
"gzipper": "^7.0.0",
|
||||||
"postcss": "^8.4.8",
|
"postcss": "^8.4.12",
|
||||||
"rollup-plugin-visualizer": "^5.6.0",
|
"rollup-plugin-visualizer": "^5.6.0",
|
||||||
"tailwindcss": "^3.0.23",
|
"tailwindcss": "^3.0.23",
|
||||||
"tar": "^6.1.11",
|
"tar": "^6.1.11",
|
||||||
"typescript": "^4.6.2",
|
"typescript": "^4.6.3",
|
||||||
"unimported": "^1.19.1",
|
"unimported": "^1.19.1",
|
||||||
"vite": "^2.8.6",
|
"vite": "^2.8.6",
|
||||||
"vite-plugin-cdn-import": "^0.3.5",
|
"vite-plugin-cdn-import": "^0.3.5",
|
||||||
"vite-plugin-pwa": "^0.11.13",
|
"vite-plugin-pwa": "^0.11.13",
|
||||||
"workbox-window": "^6.5.1"
|
"workbox-window": "^6.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+773
-751
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,7 @@ export const Connection = (): JSX.Element => {
|
|||||||
|
|
||||||
const meshtasticState = useAppSelector((state) => state.meshtastic);
|
const meshtasticState = useAppSelector((state) => state.meshtastic);
|
||||||
const appState = useAppSelector((state) => state.app);
|
const appState = useAppSelector((state) => state.app);
|
||||||
|
const chromiunm = !!window.chrome;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!import.meta.env.VITE_PUBLIC_HOSTED) {
|
if (!import.meta.env.VITE_PUBLIC_HOSTED) {
|
||||||
@@ -79,22 +80,36 @@ export const Connection = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{appState.connType === connType.BLE && (
|
|
||||||
<BLE
|
{appState.connType === connType.BLE &&
|
||||||
connecting={
|
(chromiunm ? (
|
||||||
meshtasticState.deviceStatus ===
|
<BLE
|
||||||
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
connecting={
|
||||||
}
|
meshtasticState.deviceStatus ===
|
||||||
/>
|
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||||
)}
|
}
|
||||||
{appState.connType === connType.SERIAL && (
|
/>
|
||||||
<Serial
|
) : (
|
||||||
connecting={
|
<div className="rounded-md border border-red-500 bg-red-500 bg-opacity-10 p-8 dark:text-white">
|
||||||
meshtasticState.deviceStatus ===
|
<p>Unsupported.</p>
|
||||||
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
<p>Please use a Chromium based browser.</p>
|
||||||
}
|
</div>
|
||||||
/>
|
))}
|
||||||
)}
|
|
||||||
|
{appState.connType === connType.SERIAL &&
|
||||||
|
(chromiunm ? (
|
||||||
|
<Serial
|
||||||
|
connecting={
|
||||||
|
meshtasticState.deviceStatus ===
|
||||||
|
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="rounded-md border border-red-500 bg-red-500 bg-opacity-10 p-8 dark:text-white">
|
||||||
|
<p>Unsupported.</p>
|
||||||
|
<p>Please use a Chromium based browser.</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="md:w-1/2">
|
<div className="md:w-1/2">
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { Checkbox } from '@components/generic/form/Checkbox';
|
|||||||
import { Form } from '@components/generic/form/Form';
|
import { Form } from '@components/generic/form/Form';
|
||||||
import { Input } from '@components/generic/form/Input';
|
import { Input } from '@components/generic/form/Input';
|
||||||
import { Label } from '@components/generic/form/Label';
|
import { Label } from '@components/generic/form/Label';
|
||||||
import { Select } from '@components/generic/form/Select';
|
|
||||||
import { connection } from '@core/connection';
|
import { connection } from '@core/connection';
|
||||||
import { bitwiseDecode, bitwiseEncode } from '@core/utils/bitwise';
|
import { bitwiseDecode, bitwiseEncode } from '@core/utils/bitwise';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
@@ -25,7 +24,7 @@ export const GPS = (): JSX.Element => {
|
|||||||
...preferences,
|
...preferences,
|
||||||
positionBroadcastSecs:
|
positionBroadcastSecs:
|
||||||
preferences.positionBroadcastSecs === 0
|
preferences.positionBroadcastSecs === 0
|
||||||
? preferences.isRouter
|
? preferences.role === Protobuf.Role.Router
|
||||||
? 43200
|
? 43200
|
||||||
: 900
|
: 900
|
||||||
: preferences.positionBroadcastSecs,
|
: preferences.positionBroadcastSecs,
|
||||||
@@ -54,20 +53,15 @@ export const GPS = (): JSX.Element => {
|
|||||||
{...register('positionBroadcastSecs', { valueAsNumber: true })}
|
{...register('positionBroadcastSecs', { valueAsNumber: true })}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Use Smart Position"
|
label="Disable Smart Position"
|
||||||
{...register('positionBroadcastSmart')}
|
{...register('positionBroadcastSmartDisabled')}
|
||||||
/>
|
/>
|
||||||
<Checkbox label="Use Fixed Position" {...register('fixedPosition')} />
|
<Checkbox label="Use Fixed Position" {...register('fixedPosition')} />
|
||||||
<Select
|
<Checkbox
|
||||||
label="Location Sharing"
|
label="Disable Location Sharing"
|
||||||
optionsEnum={Protobuf.LocationSharing}
|
{...register('locationShareDisabled')}
|
||||||
{...register('locationShare', { valueAsNumber: true })}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
label="GPS Mode"
|
|
||||||
optionsEnum={Protobuf.GpsOperation}
|
|
||||||
{...register('gpsOperation', { valueAsNumber: true })}
|
|
||||||
/>
|
/>
|
||||||
|
<Checkbox label="Disable GPS" {...register('gpsDisabled')} />
|
||||||
<Input
|
<Input
|
||||||
label="GPS Update Interval"
|
label="GPS Update Interval"
|
||||||
type="number"
|
type="number"
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const Settings = ({ open, setOpen }: SettingsProps): JSX.Element => {
|
|||||||
const {
|
const {
|
||||||
rangeTestModuleEnabled,
|
rangeTestModuleEnabled,
|
||||||
extNotificationModuleEnabled,
|
extNotificationModuleEnabled,
|
||||||
serialmoduleEnabled,
|
serialModuleEnabled,
|
||||||
storeForwardModuleEnabled,
|
storeForwardModuleEnabled,
|
||||||
mqttDisabled,
|
mqttDisabled,
|
||||||
cannedMessageModuleEnabled,
|
cannedMessageModuleEnabled,
|
||||||
@@ -132,7 +132,7 @@ export const Settings = ({ open, setOpen }: SettingsProps): JSX.Element => {
|
|||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
icon={<FiAlignLeft />}
|
icon={<FiAlignLeft />}
|
||||||
title="Serial"
|
title="Serial"
|
||||||
status={serialmoduleEnabled}
|
status={serialModuleEnabled}
|
||||||
>
|
>
|
||||||
<SerialSettingsPanel />
|
<SerialSettingsPanel />
|
||||||
</CollapsibleSection>
|
</CollapsibleSection>
|
||||||
|
|||||||
@@ -42,7 +42,11 @@ export const LoRa = (): JSX.Element => {
|
|||||||
{...register('hopLimit', { valueAsNumber: true })}
|
{...register('hopLimit', { valueAsNumber: true })}
|
||||||
/>
|
/>
|
||||||
<Checkbox label="Transmit Disabled" {...register('isLoraTxDisabled')} />
|
<Checkbox label="Transmit Disabled" {...register('isLoraTxDisabled')} />
|
||||||
<Checkbox label="Router Mode" {...register('isRouter')} />
|
<Select
|
||||||
|
label="Operating Role"
|
||||||
|
optionsEnum={Protobuf.Role}
|
||||||
|
{...register('role')}
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
label="Send Owner Interval"
|
label="Send Owner Interval"
|
||||||
type="number"
|
type="number"
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ export const Power = (): JSX.Element => {
|
|||||||
useForm<Protobuf.RadioConfig_UserPreferences>({
|
useForm<Protobuf.RadioConfig_UserPreferences>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
...preferences,
|
...preferences,
|
||||||
isLowPower: preferences.isRouter ? true : preferences.isLowPower,
|
isLowPower:
|
||||||
|
preferences.role === Protobuf.Role.Router
|
||||||
|
? true
|
||||||
|
: preferences.isLowPower,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -45,9 +48,11 @@ export const Power = (): JSX.Element => {
|
|||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Powered by low power source (solar)"
|
label="Powered by low power source (solar)"
|
||||||
disabled={preferences.isRouter}
|
disabled={preferences.role === Protobuf.Role.Router}
|
||||||
validationMessage={
|
validationMessage={
|
||||||
preferences.isRouter ? 'Enabled by default in router mode' : ''
|
preferences.role === Protobuf.Role.Router
|
||||||
|
? 'Enabled by default in router mode'
|
||||||
|
: ''
|
||||||
}
|
}
|
||||||
{...register('isLowPower')}
|
{...register('isLowPower')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const User = (): JSX.Element => {
|
|||||||
(state) => state.meshtastic.radio.hardware,
|
(state) => state.meshtastic.radio.hardware,
|
||||||
).myNodeNum;
|
).myNodeNum;
|
||||||
const node = useAppSelector((state) => state.meshtastic.nodes).find(
|
const node = useAppSelector((state) => state.meshtastic.nodes).find(
|
||||||
(node) => node.number === myNodeNum,
|
(node) => node.num === myNodeNum,
|
||||||
);
|
);
|
||||||
const { register, handleSubmit, formState, reset } = useForm<{
|
const { register, handleSubmit, formState, reset } = useForm<{
|
||||||
longName: string;
|
longName: string;
|
||||||
|
|||||||
@@ -37,24 +37,24 @@ export const SerialSettingsPanel = (): JSX.Element => {
|
|||||||
|
|
||||||
const moduleEnabled = useWatch({
|
const moduleEnabled = useWatch({
|
||||||
control,
|
control,
|
||||||
name: 'serialmoduleEnabled',
|
name: 'serialModuleEnabled',
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
|
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
|
||||||
<Checkbox label="Module Enabled" {...register('serialmoduleEnabled')} />
|
<Checkbox label="Module Enabled" {...register('serialModuleEnabled')} />
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Echo"
|
label="Echo"
|
||||||
disabled={!moduleEnabled}
|
disabled={!moduleEnabled}
|
||||||
{...register('serialmoduleEcho')}
|
{...register('serialModuleEcho')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
label="RX"
|
label="RX"
|
||||||
disabled={!moduleEnabled}
|
disabled={!moduleEnabled}
|
||||||
{...register('serialmoduleRxd', {
|
{...register('serialModuleRxd', {
|
||||||
valueAsNumber: true,
|
valueAsNumber: true,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@@ -62,7 +62,7 @@ export const SerialSettingsPanel = (): JSX.Element => {
|
|||||||
type="number"
|
type="number"
|
||||||
label="TX Pin"
|
label="TX Pin"
|
||||||
disabled={!moduleEnabled}
|
disabled={!moduleEnabled}
|
||||||
{...register('serialmoduleTxd', {
|
{...register('serialModuleTxd', {
|
||||||
valueAsNumber: true,
|
valueAsNumber: true,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@@ -70,7 +70,7 @@ export const SerialSettingsPanel = (): JSX.Element => {
|
|||||||
type="number"
|
type="number"
|
||||||
label="Baud Rate"
|
label="Baud Rate"
|
||||||
disabled={!moduleEnabled}
|
disabled={!moduleEnabled}
|
||||||
{...register('serialmoduleBaud', {
|
{...register('serialModuleBaud', {
|
||||||
valueAsNumber: true,
|
valueAsNumber: true,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@@ -78,7 +78,7 @@ export const SerialSettingsPanel = (): JSX.Element => {
|
|||||||
type="number"
|
type="number"
|
||||||
label="Timeout"
|
label="Timeout"
|
||||||
disabled={!moduleEnabled}
|
disabled={!moduleEnabled}
|
||||||
{...register('serialmoduleTimeout', {
|
{...register('serialModuleTimeout', {
|
||||||
valueAsNumber: true,
|
valueAsNumber: true,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@@ -86,7 +86,7 @@ export const SerialSettingsPanel = (): JSX.Element => {
|
|||||||
type="number"
|
type="number"
|
||||||
label="Mode"
|
label="Mode"
|
||||||
disabled={!moduleEnabled}
|
disabled={!moduleEnabled}
|
||||||
{...register('serialmoduleMode', {
|
{...register('serialModuleMode', {
|
||||||
valueAsNumber: true,
|
valueAsNumber: true,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export const Telemetry = (): JSX.Element => {
|
|||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Display Farenheit"
|
label="Display Farenheit"
|
||||||
{...register('telemetryModuleDisplayFarenheit')}
|
{...register('telemetryModuleDisplayFahrenheit')}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
label="Sensor Type"
|
label="Sensor Type"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
FiBluetooth,
|
FiBluetooth,
|
||||||
FiCpu,
|
FiCpu,
|
||||||
FiGitBranch,
|
FiGitBranch,
|
||||||
|
FiHexagon,
|
||||||
FiMenu,
|
FiMenu,
|
||||||
FiMoon,
|
FiMoon,
|
||||||
FiSun,
|
FiSun,
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
FiX,
|
FiX,
|
||||||
} from 'react-icons/fi';
|
} from 'react-icons/fi';
|
||||||
import { MdUpgrade } from 'react-icons/md';
|
import { MdUpgrade } from 'react-icons/md';
|
||||||
|
import { AiOutlineHeart, AiFillHeart } from 'react-icons/ai';
|
||||||
import {
|
import {
|
||||||
RiArrowDownLine,
|
RiArrowDownLine,
|
||||||
RiArrowUpDownLine,
|
RiArrowUpDownLine,
|
||||||
@@ -29,6 +31,13 @@ import {
|
|||||||
import { useAppDispatch } from '@hooks/useAppDispatch';
|
import { useAppDispatch } from '@hooks/useAppDispatch';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { Protobuf, Types } from '@meshtastic/meshtasticjs';
|
import { Protobuf, Types } from '@meshtastic/meshtasticjs';
|
||||||
|
import {
|
||||||
|
IoBatteryChargingOutline,
|
||||||
|
IoBatteryDeadOutline,
|
||||||
|
IoBatteryFullOutline,
|
||||||
|
IoBatteryHalfOutline,
|
||||||
|
} from 'react-icons/io5';
|
||||||
|
import { FaTrafficLight } from 'react-icons/fa';
|
||||||
|
|
||||||
export const BottomNav = (): JSX.Element => {
|
export const BottomNav = (): JSX.Element => {
|
||||||
const [showVersionInfo, setShowVersionInfo] = useState(false);
|
const [showVersionInfo, setShowVersionInfo] = useState(false);
|
||||||
@@ -38,6 +47,8 @@ export const BottomNav = (): JSX.Element => {
|
|||||||
const primaryChannelSettings = useAppSelector(
|
const primaryChannelSettings = useAppSelector(
|
||||||
(state) => state.meshtastic.radio.channels,
|
(state) => state.meshtastic.radio.channels,
|
||||||
).find((channel) => channel.role === Protobuf.Channel_Role.PRIMARY)?.settings;
|
).find((channel) => channel.role === Protobuf.Channel_Role.PRIMARY)?.settings;
|
||||||
|
const telemetry =
|
||||||
|
meshtasticState.nodes[meshtasticState.radio.hardware.myNodeNum]?.telemetry;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="z-20 flex justify-between divide-x divide-gray-400 border-t border-gray-400 bg-white dark:divide-gray-600 dark:border-gray-600 dark:bg-secondaryDark">
|
<div className="z-20 flex justify-between divide-x divide-gray-400 border-t border-gray-400 bg-white dark:divide-gray-600 dark:border-gray-600 dark:bg-secondaryDark">
|
||||||
@@ -78,10 +89,57 @@ export const BottomNav = (): JSX.Element => {
|
|||||||
)}
|
)}
|
||||||
<div className="truncate text-xs font-medium">
|
<div className="truncate text-xs font-medium">
|
||||||
{meshtasticState.nodes.find(
|
{meshtasticState.nodes.find(
|
||||||
(node) => node.number === meshtasticState.radio.hardware.myNodeNum,
|
(node) => node.num === meshtasticState.radio.hardware.myNodeNum,
|
||||||
)?.user?.longName ?? 'Disconnected'}
|
)?.user?.longName ?? 'Disconnected'}
|
||||||
</div>
|
</div>
|
||||||
</BottomNavItem>
|
</BottomNavItem>
|
||||||
|
<BottomNavItem tooltip="Router Heartbeat">
|
||||||
|
{telemetry?.routerHeartbeat ? (
|
||||||
|
<AiFillHeart className="h-4" />
|
||||||
|
) : (
|
||||||
|
<AiOutlineHeart className="h-4" />
|
||||||
|
)}
|
||||||
|
</BottomNavItem>
|
||||||
|
|
||||||
|
<BottomNavItem tooltip="Battery Level">
|
||||||
|
{!telemetry?.batteryLevel ? (
|
||||||
|
<IoBatteryDeadOutline className="h-4" />
|
||||||
|
) : telemetry?.batteryLevel > 50 ? (
|
||||||
|
<IoBatteryFullOutline className="h-4" />
|
||||||
|
) : telemetry?.batteryLevel > 0 ? (
|
||||||
|
<IoBatteryFullOutline className="h-4" />
|
||||||
|
) : (
|
||||||
|
<IoBatteryChargingOutline className="h-4" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="truncate text-xs font-medium">
|
||||||
|
{telemetry?.batteryLevel
|
||||||
|
? `${telemetry?.batteryLevel}% - ${telemetry?.voltage}v`
|
||||||
|
: 'No Battery'}
|
||||||
|
</div>
|
||||||
|
</BottomNavItem>
|
||||||
|
|
||||||
|
<BottomNavItem tooltip="Network Utilization">
|
||||||
|
<div className="m-auto h-3 w-3 rounded-full bg-primary" />
|
||||||
|
<div className="truncate text-xs font-medium">
|
||||||
|
{`${telemetry?.airUtilTx ?? 0}% - Air`} |
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`m-auto h-3 w-3 rounded-full ${
|
||||||
|
!telemetry?.channelUtilization
|
||||||
|
? 'bg-primary'
|
||||||
|
: telemetry?.channelUtilization > 50
|
||||||
|
? 'bg-red-400'
|
||||||
|
: telemetry?.channelUtilization > 24
|
||||||
|
? 'bg-yellow-400'
|
||||||
|
: 'bg-primary'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
<div className="truncate text-xs font-medium">
|
||||||
|
{`${telemetry?.channelUtilization ?? 0}% - Ch`}
|
||||||
|
</div>
|
||||||
|
</BottomNavItem>
|
||||||
|
|
||||||
<BottomNavItem tooltip="MQTT Status">
|
<BottomNavItem tooltip="MQTT Status">
|
||||||
{primaryChannelSettings?.uplinkEnabled &&
|
{primaryChannelSettings?.uplinkEnabled &&
|
||||||
|
|||||||
@@ -33,16 +33,6 @@ interface Route {
|
|||||||
//speed stats?
|
//speed stats?
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Node {
|
|
||||||
number: number;
|
|
||||||
lastHeard: Date;
|
|
||||||
snr: number[];
|
|
||||||
positions: Protobuf.Position[];
|
|
||||||
currentPosition?: CurrentPosition;
|
|
||||||
user?: Protobuf.User;
|
|
||||||
routes: Route[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Radio {
|
export interface Radio {
|
||||||
channels: Protobuf.Channel[];
|
channels: Protobuf.Channel[];
|
||||||
preferences: Protobuf.RadioConfig_UserPreferences;
|
preferences: Protobuf.RadioConfig_UserPreferences;
|
||||||
@@ -53,7 +43,7 @@ interface MeshtasticState {
|
|||||||
deviceStatus: Types.DeviceStatusEnum;
|
deviceStatus: Types.DeviceStatusEnum;
|
||||||
lastMeshInterraction: number;
|
lastMeshInterraction: number;
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
nodes: Node[];
|
nodes: Protobuf.NodeInfo[];
|
||||||
radio: Radio;
|
radio: Radio;
|
||||||
chats: ChatEntries;
|
chats: ChatEntries;
|
||||||
logs: Types.LogEventPacket[];
|
logs: Types.LogEventPacket[];
|
||||||
@@ -97,73 +87,46 @@ export const meshtasticSlice = createSlice({
|
|||||||
},
|
},
|
||||||
addUser: (state, action: PayloadAction<Types.UserPacket>) => {
|
addUser: (state, action: PayloadAction<Types.UserPacket>) => {
|
||||||
const node = state.nodes.find(
|
const node = state.nodes.find(
|
||||||
(node) => node.number === action.payload.packet.from,
|
(node) => node.num === action.payload.packet.from,
|
||||||
);
|
);
|
||||||
if (node) {
|
if (node) {
|
||||||
node.user = action.payload.data;
|
node.user = action.payload.data;
|
||||||
if (action.payload.packet.rxTime) {
|
if (action.payload.packet.rxTime) {
|
||||||
node.lastHeard = new Date(action.payload.packet.rxTime * 1000);
|
node.lastHeard = new Date(
|
||||||
|
action.payload.packet.rxTime * 1000,
|
||||||
|
).getTime();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.nodes.push({
|
state.nodes.push({
|
||||||
number: action.payload.packet.from,
|
num: action.payload.packet.from,
|
||||||
lastHeard: new Date(),
|
snr: action.payload.packet.rxSnr,
|
||||||
snr: [action.payload.packet.rxSnr],
|
lastHeard: new Date().getTime(),
|
||||||
user: action.payload.data,
|
...action.payload.packet,
|
||||||
positions: [],
|
|
||||||
routes: [],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addPosition: (state, action: PayloadAction<Types.PositionPacket>) => {
|
addPosition: (state, action: PayloadAction<Types.PositionPacket>) => {
|
||||||
const node = state.nodes.find(
|
const node = state.nodes.find(
|
||||||
(node) => node.number === action.payload.packet.from,
|
(node) => node.num === action.payload.packet.from,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
node.positions.push(action.payload.data);
|
node.position = action.payload.data;
|
||||||
|
|
||||||
if (
|
|
||||||
action.payload.data.latitudeI ||
|
|
||||||
action.payload.data.longitudeI ||
|
|
||||||
action.payload.data.altitude
|
|
||||||
) {
|
|
||||||
node.currentPosition = {
|
|
||||||
latitudeI:
|
|
||||||
action.payload.data.latitudeI ?? node.currentPosition?.latitudeI,
|
|
||||||
longitudeI:
|
|
||||||
action.payload.data.longitudeI ??
|
|
||||||
node.currentPosition?.longitudeI,
|
|
||||||
altitude:
|
|
||||||
action.payload.data.altitude ?? node.currentPosition?.altitude,
|
|
||||||
posTimestamp: action.payload.data.posTimestamp,
|
|
||||||
satsInView:
|
|
||||||
action.payload.data.satsInView ??
|
|
||||||
node.currentPosition?.satsInView,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.payload.packet.rxTime) {
|
if (action.payload.packet.rxTime) {
|
||||||
node.lastHeard = new Date(action.payload.packet.rxTime * 1000);
|
node.lastHeard = new Date(
|
||||||
|
action.payload.packet.rxTime * 1000,
|
||||||
|
).getTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addNode: (state, action: PayloadAction<Protobuf.NodeInfo>) => {
|
addNode: (state, action: PayloadAction<Protobuf.NodeInfo>) => {
|
||||||
const node = state.nodes.find(
|
const node = state.nodes.find((node) => node.num === action.payload.num);
|
||||||
(node) => node.number === action.payload.num,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
node.lastHeard = new Date(action.payload.lastHeard * 1000);
|
node.lastHeard = new Date(action.payload.lastHeard * 1000).getTime();
|
||||||
node.snr.push(action.payload.snr);
|
node.snr = action.payload.snr;
|
||||||
} else {
|
} else {
|
||||||
state.nodes.push({
|
state.nodes.push(action.payload);
|
||||||
number: action.payload.num,
|
|
||||||
lastHeard: new Date(action.payload.lastHeard * 1000),
|
|
||||||
snr: [action.payload.snr],
|
|
||||||
positions: [],
|
|
||||||
routes: [],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addChannel: (state, action: PayloadAction<Protobuf.Channel>) => {
|
addChannel: (state, action: PayloadAction<Protobuf.Channel>) => {
|
||||||
@@ -182,17 +145,16 @@ export const meshtasticSlice = createSlice({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addRoute: (state, action: PayloadAction<Route>) => {
|
addRoute: (state, action: PayloadAction<Route>) => {
|
||||||
const node = state.nodes.find(
|
// const node = state.nodes.find(
|
||||||
(node) => node.number === action.payload.from,
|
// (node) => node.num === action.payload.from,
|
||||||
);
|
// );
|
||||||
const exists = node?.routes.findIndex(
|
// const exists = node?.routes.findIndex(
|
||||||
(route) =>
|
// (route) =>
|
||||||
route.from === action.payload.from && route.to === action.payload.to,
|
// route.from === action.payload.from && route.to === action.payload.to,
|
||||||
);
|
// );
|
||||||
|
// if (exists === -1) {
|
||||||
if (exists === -1) {
|
// node?.routes.push(action.payload);
|
||||||
node?.routes.push(action.payload);
|
// }
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setPreferences: (
|
setPreferences: (
|
||||||
state,
|
state,
|
||||||
@@ -243,11 +205,9 @@ export const meshtasticSlice = createSlice({
|
|||||||
state,
|
state,
|
||||||
action: PayloadAction<{ id: number; time: Date }>,
|
action: PayloadAction<{ id: number; time: Date }>,
|
||||||
) => {
|
) => {
|
||||||
const node = state.nodes.find(
|
const node = state.nodes.find((node) => node.num === action.payload.id);
|
||||||
(node) => node.number === action.payload.id,
|
|
||||||
);
|
|
||||||
if (node) {
|
if (node) {
|
||||||
node.lastHeard = action.payload.time;
|
node.lastHeard = action.payload.time.getTime();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addChat: (state, action: PayloadAction<number>) => {
|
addChat: (state, action: PayloadAction<number>) => {
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ export const Debug = (): JSX.Element => {
|
|||||||
(state) => state.meshtastic.radio.hardware,
|
(state) => state.meshtastic.radio.hardware,
|
||||||
);
|
);
|
||||||
const node = useAppSelector((state) =>
|
const node = useAppSelector((state) =>
|
||||||
state.meshtastic.nodes.find(
|
state.meshtastic.nodes.find((node) => node.num === hardwareInfo.myNodeNum),
|
||||||
(node) => node.number === hardwareInfo.myNodeNum,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -33,6 +31,13 @@ export const Debug = (): JSX.Element => {
|
|||||||
>
|
>
|
||||||
Get Preferences
|
Get Preferences
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async (): Promise<void> => {
|
||||||
|
await connection.getAllChannels();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Get All Channels
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ export const Info = (): JSX.Element => {
|
|||||||
(state) => state.meshtastic.radio.hardware,
|
(state) => state.meshtastic.radio.hardware,
|
||||||
);
|
);
|
||||||
const node = useAppSelector((state) =>
|
const node = useAppSelector((state) =>
|
||||||
state.meshtastic.nodes.find(
|
state.meshtastic.nodes.find((node) => node.num === hardwareInfo.myNodeNum),
|
||||||
(node) => node.number === hardwareInfo.myNodeNum,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
+10
-10
@@ -7,14 +7,14 @@ import { RiRoadMapLine } from 'react-icons/ri';
|
|||||||
|
|
||||||
import { Layout } from '@components/layout';
|
import { Layout } from '@components/layout';
|
||||||
import { MapboxProvider } from '@components/MapBox/MapboxProvider';
|
import { MapboxProvider } from '@components/MapBox/MapboxProvider';
|
||||||
import type { Node } from '@core/slices/meshtasticSlice';
|
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { MapContainer } from '@pages/Map/MapContainer';
|
import { MapContainer } from '@pages/Map/MapContainer';
|
||||||
import { Marker } from '@pages/Map/Marker';
|
import { Marker } from '@pages/Map/Marker';
|
||||||
import { NodeCard } from '@pages/Nodes/NodeCard';
|
import { NodeCard } from '@pages/Nodes/NodeCard';
|
||||||
|
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const Map = (): JSX.Element => {
|
export const Map = (): JSX.Element => {
|
||||||
const [selectedNode, setSelectedNode] = useState<Node>();
|
const [selectedNode, setSelectedNode] = useState<Protobuf.NodeInfo>();
|
||||||
|
|
||||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||||
const myNodeNum = useAppSelector(
|
const myNodeNum = useAppSelector(
|
||||||
@@ -25,13 +25,13 @@ export const Map = (): JSX.Element => {
|
|||||||
<MapboxProvider>
|
<MapboxProvider>
|
||||||
{nodes.map((node) => {
|
{nodes.map((node) => {
|
||||||
return (
|
return (
|
||||||
node.currentPosition && (
|
node.position && (
|
||||||
<Marker
|
<Marker
|
||||||
key={node.number}
|
key={node.num}
|
||||||
center={
|
center={
|
||||||
new mapboxgl.LngLat(
|
new mapboxgl.LngLat(
|
||||||
node.currentPosition.longitudeI / 1e7,
|
node.position.longitudeI / 1e7,
|
||||||
node.currentPosition.latitudeI / 1e7,
|
node.position.latitudeI / 1e7,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -40,7 +40,7 @@ export const Map = (): JSX.Element => {
|
|||||||
setSelectedNode(node);
|
setSelectedNode(node);
|
||||||
}}
|
}}
|
||||||
className={`z-50 rounded-full border-2 bg-opacity-30 ${
|
className={`z-50 rounded-full border-2 bg-opacity-30 ${
|
||||||
node.number === selectedNode?.number
|
node.num === selectedNode?.num
|
||||||
? 'border-green-500 bg-green-500'
|
? 'border-green-500 bg-green-500'
|
||||||
: 'border-blue-500 bg-blue-500'
|
: 'border-blue-500 bg-blue-500'
|
||||||
}`}
|
}`}
|
||||||
@@ -66,10 +66,10 @@ export const Map = (): JSX.Element => {
|
|||||||
|
|
||||||
{nodes.map((node) => (
|
{nodes.map((node) => (
|
||||||
<NodeCard
|
<NodeCard
|
||||||
key={node.number}
|
key={node.num}
|
||||||
node={node}
|
node={node}
|
||||||
isMyNode={node.number === myNodeNum}
|
isMyNode={node.num === myNodeNum}
|
||||||
selected={selectedNode?.number === node.number}
|
selected={selectedNode?.num === node.num}
|
||||||
setSelected={(): void => {
|
setSelected={(): void => {
|
||||||
setSelectedNode(node);
|
setSelectedNode(node);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const ChannelChat = ({
|
|||||||
(state) => state.meshtastic.radio.hardware,
|
(state) => state.meshtastic.radio.hardware,
|
||||||
).myNodeNum;
|
).myNodeNum;
|
||||||
const nodes = useAppSelector((state) => state.meshtastic.nodes).filter(
|
const nodes = useAppSelector((state) => state.meshtastic.nodes).filter(
|
||||||
(node) => node.number !== myNodeNum,
|
(node) => node.num !== myNodeNum,
|
||||||
);
|
);
|
||||||
const chats = useAppSelector((state) => state.meshtastic.chats);
|
const chats = useAppSelector((state) => state.meshtastic.chats);
|
||||||
const channels = useAppSelector(
|
const channels = useAppSelector(
|
||||||
@@ -80,7 +80,7 @@ export const ChannelChat = ({
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
key={nodeId}
|
key={nodeId}
|
||||||
content={
|
content={
|
||||||
nodes.find((node) => node.number === nodeId)?.user
|
nodes.find((node) => node.num === nodeId)?.user
|
||||||
?.longName ?? 'UNK'
|
?.longName ?? 'UNK'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { FiSettings } from 'react-icons/fi';
|
|||||||
|
|
||||||
import { IconButton } from '@components/generic/button/IconButton';
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
||||||
import type { Node } from '@core/slices/meshtasticSlice';
|
|
||||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||||
|
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export interface DmChatProps {
|
export interface DmChatProps {
|
||||||
node: Node;
|
node: Protobuf.NodeInfo;
|
||||||
selectedIndex: number;
|
selectedIndex: number;
|
||||||
setSelectedIndex: (index: number) => void;
|
setSelectedIndex: (index: number) => void;
|
||||||
}
|
}
|
||||||
@@ -20,16 +20,16 @@ export const DmChat = ({
|
|||||||
}: DmChatProps): JSX.Element => {
|
}: DmChatProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
key={node.number}
|
key={node.num}
|
||||||
selected={node.number === selectedIndex}
|
selected={node.num === selectedIndex}
|
||||||
setSelected={(): void => {
|
setSelected={(): void => {
|
||||||
setSelectedIndex(node.number);
|
setSelectedIndex(node.num);
|
||||||
}}
|
}}
|
||||||
actions={<IconButton nested icon={<FiSettings />} />}
|
actions={<IconButton nested icon={<FiSettings />} />}
|
||||||
>
|
>
|
||||||
<div className="flex dark:text-white">
|
<div className="flex dark:text-white">
|
||||||
<div className="m-auto">
|
<div className="m-auto">
|
||||||
<Hashicon value={node.number.toString()} size={32} />
|
<Hashicon value={node.num.toString()} size={32} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="my-auto mr-auto font-semibold dark:text-white">
|
<div className="my-auto mr-auto font-semibold dark:text-white">
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import type React from 'react';
|
|||||||
import { FiClock } from 'react-icons/fi';
|
import { FiClock } from 'react-icons/fi';
|
||||||
|
|
||||||
import { Tooltip } from '@components/generic/Tooltip';
|
import { Tooltip } from '@components/generic/Tooltip';
|
||||||
import type { Node } from '@core/slices/meshtasticSlice';
|
|
||||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||||
|
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export interface MessageProps {
|
export interface MessageProps {
|
||||||
lastMsgSameUser: boolean;
|
lastMsgSameUser: boolean;
|
||||||
message: string;
|
message: string;
|
||||||
ack: boolean;
|
ack: boolean;
|
||||||
rxTime: Date;
|
rxTime: Date;
|
||||||
sender?: Node;
|
sender?: Protobuf.NodeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Message = ({
|
export const Message = ({
|
||||||
@@ -59,7 +59,7 @@ export const Message = ({
|
|||||||
) : (
|
) : (
|
||||||
<div className="mx-6 flex gap-2">
|
<div className="mx-6 flex gap-2">
|
||||||
<div className="my-auto w-8">
|
<div className="my-auto w-8">
|
||||||
<Hashicon value={(sender?.number ?? 0).toString()} size={32} />
|
<Hashicon value={(sender?.num ?? 0).toString()} size={32} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
|||||||
@@ -52,10 +52,10 @@ export const Messages = (): JSX.Element => {
|
|||||||
<div className="mx-2 border-b border-gray-400 dark:border-gray-600" />
|
<div className="mx-2 border-b border-gray-400 dark:border-gray-600" />
|
||||||
)}
|
)}
|
||||||
{nodes
|
{nodes
|
||||||
.filter((node) => node.number !== myNodeNum)
|
.filter((node) => node.num !== myNodeNum)
|
||||||
.map((node) => (
|
.map((node) => (
|
||||||
<DmChat
|
<DmChat
|
||||||
key={node.number}
|
key={node.num}
|
||||||
node={node}
|
node={node}
|
||||||
selectedIndex={selectedChatIndex}
|
selectedIndex={selectedChatIndex}
|
||||||
setSelectedIndex={setSelectedChatIndex}
|
setSelectedIndex={setSelectedChatIndex}
|
||||||
@@ -81,7 +81,7 @@ export const Messages = (): JSX.Element => {
|
|||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-gray-500 dark:text-gray-400">
|
<span className="text-gray-500 dark:text-gray-400">
|
||||||
{nodes.find((n) => n.number === selectedChatIndex)?.user
|
{nodes.find((node) => node.num === selectedChatIndex)?.user
|
||||||
?.longName ?? 'Unknown'}
|
?.longName ?? 'Unknown'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -106,7 +106,7 @@ export const Messages = (): JSX.Element => {
|
|||||||
.packet.from === message.message.packet.from
|
.packet.from === message.message.packet.from
|
||||||
}
|
}
|
||||||
sender={nodes.find(
|
sender={nodes.find(
|
||||||
(node) => node.number === message.message.packet.from,
|
(node) => node.num === message.message.packet.from,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ import { SidebarOverlay } from '@components/generic/Sidebar/SidebarOverlay';
|
|||||||
import { Tooltip } from '@components/generic/Tooltip';
|
import { Tooltip } from '@components/generic/Tooltip';
|
||||||
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
||||||
import { CopyButton } from '@components/menu/buttons/CopyButton';
|
import { CopyButton } from '@components/menu/buttons/CopyButton';
|
||||||
import type { Node } from '@core/slices/meshtasticSlice';
|
|
||||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||||
import { useMapbox } from '@hooks/useMapbox';
|
import { useMapbox } from '@hooks/useMapbox';
|
||||||
|
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
type PositionConfidence = 'high' | 'low' | 'none';
|
type PositionConfidence = 'high' | 'low' | 'none';
|
||||||
|
|
||||||
export interface NodeCardProps {
|
export interface NodeCardProps {
|
||||||
node: Node;
|
node: Protobuf.NodeInfo;
|
||||||
isMyNode: boolean;
|
isMyNode: boolean;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
setSelected: () => void;
|
setSelected: () => void;
|
||||||
@@ -47,14 +47,14 @@ export const NodeCard = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPositionConfidence(
|
setPositionConfidence(
|
||||||
node.currentPosition
|
node.position
|
||||||
? new Date(node.currentPosition.posTimestamp * 1000) >
|
? new Date(node.position.posTimestamp * 1000) >
|
||||||
new Date(new Date().getTime() - 1000 * 60 * 30)
|
new Date(new Date().getTime() - 1000 * 60 * 30)
|
||||||
? 'high'
|
? 'high'
|
||||||
: 'low'
|
: 'low'
|
||||||
: 'none',
|
: 'none',
|
||||||
);
|
);
|
||||||
}, [node.currentPosition]);
|
}, [node.position]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
@@ -69,11 +69,11 @@ export const NodeCard = ({
|
|||||||
onClick={(e): void => {
|
onClick={(e): void => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setSelected();
|
setSelected();
|
||||||
if (PositionConfidence !== 'none' && node.currentPosition) {
|
if (PositionConfidence !== 'none' && node.position) {
|
||||||
map?.flyTo({
|
map?.flyTo({
|
||||||
center: new LngLat(
|
center: new LngLat(
|
||||||
node.currentPosition.longitudeI / 1e7,
|
node.position.longitudeI / 1e7,
|
||||||
node.currentPosition.latitudeI / 1e7,
|
node.position.latitudeI / 1e7,
|
||||||
),
|
),
|
||||||
zoom: 16,
|
zoom: 16,
|
||||||
});
|
});
|
||||||
@@ -113,12 +113,12 @@ export const NodeCard = ({
|
|||||||
</m.div>
|
</m.div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Hashicon value={node.number.toString()} size={32} />
|
<Hashicon value={node.num.toString()} size={32} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="my-auto mr-auto text-xs font-semibold dark:text-gray-400">
|
<div className="my-auto mr-auto text-xs font-semibold dark:text-gray-400">
|
||||||
{node.lastHeard.getTime()
|
{node.lastHeard
|
||||||
? node.lastHeard.toLocaleTimeString(undefined, {
|
? new Date(node.lastHeard).toLocaleTimeString(undefined, {
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
})
|
})
|
||||||
@@ -136,7 +136,7 @@ export const NodeCard = ({
|
|||||||
<CollapsibleSection title="User" icon={<FiUser />}>
|
<CollapsibleSection title="User" icon={<FiUser />}>
|
||||||
<div className="flex p-2">
|
<div className="flex p-2">
|
||||||
<div className="m-auto flex flex-col gap-2">
|
<div className="m-auto flex flex-col gap-2">
|
||||||
<Hashicon value={node.number.toString()} size={180} />
|
<Hashicon value={node.num.toString()} size={180} />
|
||||||
<div className="text-center text-lg font-medium dark:text-white">
|
<div className="text-center text-lg font-medium dark:text-white">
|
||||||
{node?.user?.longName || 'Unknown'}
|
{node?.user?.longName || 'Unknown'}
|
||||||
</div>
|
</div>
|
||||||
@@ -146,18 +146,18 @@ export const NodeCard = ({
|
|||||||
<CollapsibleSection title="Location" icon={<FiMapPin />}>
|
<CollapsibleSection title="Location" icon={<FiMapPin />}>
|
||||||
<>
|
<>
|
||||||
<div className="flex h-10 select-none justify-between rounded-md border border-gray-400 bg-transparent bg-gray-300 px-1 text-gray-500 dark:border-gray-600 dark:bg-secondaryDark dark:text-gray-400 ">
|
<div className="flex h-10 select-none justify-between rounded-md border border-gray-400 bg-transparent bg-gray-300 px-1 text-gray-500 dark:border-gray-600 dark:bg-secondaryDark dark:text-gray-400 ">
|
||||||
{node.currentPosition ? (
|
{node.position ? (
|
||||||
<>
|
<>
|
||||||
<div className="my-auto px-1">
|
<div className="my-auto px-1">
|
||||||
{(node.currentPosition.latitudeI / 1e7).toPrecision(6)}
|
{(node.position.latitudeI / 1e7).toPrecision(6)}
|
||||||
,
|
,
|
||||||
{(node.currentPosition?.longitudeI / 1e7).toPrecision(6)}
|
{(node.position?.longitudeI / 1e7).toPrecision(6)}
|
||||||
</div>
|
</div>
|
||||||
<CopyButton
|
<CopyButton
|
||||||
data={
|
data={
|
||||||
node.currentPosition
|
node.position
|
||||||
? `${node.currentPosition.latitudeI / 1e7},${
|
? `${node.position.latitudeI / 1e7},${
|
||||||
node.currentPosition.longitudeI / 1e7
|
node.position.longitudeI / 1e7
|
||||||
}`
|
}`
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-20
@@ -37,8 +37,8 @@ export const Nodes = (): JSX.Element => {
|
|||||||
|
|
||||||
nodes.map((node, index) => {
|
nodes.map((node, index) => {
|
||||||
tmpNodes.push({
|
tmpNodes.push({
|
||||||
id: node.number.toString(),
|
id: node.num.toString(),
|
||||||
data: { label: node.user?.longName ?? `Unknown ${node.number}` },
|
data: { label: node.user?.longName ?? `Unknown ${node.num}` },
|
||||||
position: { x: index * 160 + 500, y: 100 + 500 },
|
position: { x: index * 160 + 500, y: 100 + 500 },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -50,7 +50,7 @@ export const Nodes = (): JSX.Element => {
|
|||||||
const tmpEdges: Edge[] = [];
|
const tmpEdges: Edge[] = [];
|
||||||
|
|
||||||
nodes.map((node, index) => {
|
nodes.map((node, index) => {
|
||||||
if (node.number === myNodeNum) {
|
if (node.num === myNodeNum) {
|
||||||
tmpEdges.push({
|
tmpEdges.push({
|
||||||
id: `e${1}-${myNodeNum}`,
|
id: `e${1}-${myNodeNum}`,
|
||||||
source: '1',
|
source: '1',
|
||||||
@@ -63,15 +63,15 @@ export const Nodes = (): JSX.Element => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
node.routes.map((route) => {
|
// node.routes.map((route) => {
|
||||||
tmpEdges.push({
|
// tmpEdges.push({
|
||||||
id: `e${route.from}-${route.to}`,
|
// id: `e${route.from}-${route.to}`,
|
||||||
source: node.number.toString(),
|
// source: node.num.toString(),
|
||||||
target: route.to.toString(),
|
// target: route.to.toString(),
|
||||||
type: 'smoothstep',
|
// type: 'smoothstep',
|
||||||
animated: true,
|
// animated: true,
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
});
|
||||||
|
|
||||||
setGraphEdges(tmpEdges);
|
setGraphEdges(tmpEdges);
|
||||||
@@ -85,17 +85,17 @@ export const Nodes = (): JSX.Element => {
|
|||||||
<>
|
<>
|
||||||
{nodes.map((node) => (
|
{nodes.map((node) => (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
key={node.number}
|
key={node.num}
|
||||||
selected={node.number === selected}
|
selected={node.num === selected}
|
||||||
setSelected={(): void => {
|
setSelected={(): void => {
|
||||||
setSelected(node.number);
|
setSelected(node.num);
|
||||||
}}
|
}}
|
||||||
actions={
|
actions={
|
||||||
<IconButton
|
<IconButton
|
||||||
nested
|
nested
|
||||||
onClick={(e): void => {
|
onClick={(e): void => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setSelected(node.number);
|
setSelected(node.num);
|
||||||
}}
|
}}
|
||||||
icon={<FiSettings />}
|
icon={<FiSettings />}
|
||||||
/>
|
/>
|
||||||
@@ -103,7 +103,7 @@ export const Nodes = (): JSX.Element => {
|
|||||||
>
|
>
|
||||||
<div className="flex dark:text-white">
|
<div className="flex dark:text-white">
|
||||||
<div className="relative m-auto">
|
<div className="relative m-auto">
|
||||||
{node.number === myNodeNum && (
|
{node.num === myNodeNum && (
|
||||||
<Tooltip content="Your Node">
|
<Tooltip content="Your Node">
|
||||||
<m.div
|
<m.div
|
||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
@@ -113,12 +113,12 @@ export const Nodes = (): JSX.Element => {
|
|||||||
</m.div>
|
</m.div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Hashicon value={node.number.toString()} size={32} />
|
<Hashicon value={node.num.toString()} size={32} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="my-auto mr-auto text-xs font-semibold dark:text-gray-400">
|
<div className="my-auto mr-auto text-xs font-semibold dark:text-gray-400">
|
||||||
{node.lastHeard.getTime()
|
{node.lastHeard
|
||||||
? node.lastHeard.toLocaleTimeString(undefined, {
|
? new Date(node.lastHeard).toLocaleTimeString(undefined, {
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
})
|
})
|
||||||
|
|||||||
Vendored
+2
@@ -1,6 +1,8 @@
|
|||||||
/* Use this file to declare any custom file extensions for importing */
|
/* Use this file to declare any custom file extensions for importing */
|
||||||
/* Use this folder to also add/extend a package d.ts file, if needed. */
|
/* Use this folder to also add/extend a package d.ts file, if needed. */
|
||||||
|
|
||||||
|
///<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 };
|
||||||
|
|||||||
Reference in New Issue
Block a user