21 changed files with 835 additions and 9437 deletions
File diff suppressed because it is too large
@ -0,0 +1,66 @@ |
|||
import React from 'react'; |
|||
|
|||
import { MenuIcon, PaperAirplaneIcon } from '@heroicons/react/outline'; |
|||
import type { IHTTPConnection } from '@meshtastic/meshtasticjs'; |
|||
|
|||
import type { languageTemplate } from '../App'; |
|||
|
|||
export interface MessageBoxProps { |
|||
translations: languageTemplate; |
|||
setSettingsModalOpen: React.Dispatch<React.SetStateAction<boolean>>; |
|||
connection: IHTTPConnection; |
|||
isReady: boolean; |
|||
} |
|||
|
|||
const MessageBox = (props: MessageBoxProps) => { |
|||
const [currentMessage, setCurrentMessage] = React.useState(''); |
|||
const sendMessage = () => { |
|||
if (props.isReady) { |
|||
props.connection.sendText(currentMessage, undefined, true); |
|||
setCurrentMessage(''); |
|||
} |
|||
}; |
|||
return ( |
|||
<div className="flex text-lg font-medium border rounded-md space-x-2 md:space-x-0 w-full"> |
|||
<div |
|||
className="flex p-3 text-xl hover:text-gray-500 text-gray-400 rounded-md shadow-md focus:outline-none cursor-pointer md:hidden" |
|||
onClick={() => { |
|||
props.setSettingsModalOpen(true); |
|||
}} |
|||
> |
|||
<MenuIcon className="m-auto h-6 2-6" /> |
|||
</div> |
|||
<form |
|||
className="flex flex-wrap relative w-full" |
|||
onSubmit={(e) => { |
|||
e.preventDefault(); |
|||
sendMessage(); |
|||
}} |
|||
> |
|||
{props.isReady} |
|||
<input |
|||
type="text" |
|||
placeholder={`${props.translations.no_message_placeholder}...`} |
|||
disabled={!props.isReady} |
|||
value={currentMessage} |
|||
onChange={(e) => { |
|||
setCurrentMessage(e.target.value); |
|||
}} |
|||
className={`p-3 placeholder-gray-400 text-gray-700 relative rounded-md shadow-md focus:outline-none w-full pr-10 ${ |
|||
props.isReady ? 'cursor-text' : 'cursor-not-allowed' |
|||
}`}
|
|||
/> |
|||
<span className="flex z-10 h-full text-gray-400 absolute w-8 right-0"> |
|||
<PaperAirplaneIcon |
|||
onClick={sendMessage} |
|||
className={`text-xl hover:text-gray-500 h-6 w-6 my-auto ${ |
|||
props.isReady ? 'cursor-pointer' : 'cursor-not-allowed' |
|||
}`}
|
|||
/> |
|||
</span> |
|||
</form> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default MessageBox; |
|||
@ -1,67 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/outline'; |
|||
|
|||
interface NavItemProps { |
|||
isDropdown: boolean; |
|||
open: boolean; |
|||
isNested: boolean; |
|||
titleContent: React.ReactNode; |
|||
dropdownContent?: React.ReactNode; |
|||
onClick?: Function; |
|||
isLoading?: boolean; |
|||
} |
|||
|
|||
const NavItem = (props: NavItemProps) => { |
|||
React.useEffect(() => { |
|||
if (props.open) { |
|||
setNavItemOpen(props.open); |
|||
} |
|||
}, []); |
|||
const [navItemOpen, setNavItemOpen] = React.useState(false); |
|||
return ( |
|||
<> |
|||
<div |
|||
className={`flex w-full text-lg font-medium justify-between ${ |
|||
navItemOpen && props.isNested ? 'bg-gray-100' : null |
|||
} ${props.isNested ? 'border-b px-3 py-1' : 'p-3'} ${ |
|||
props.isDropdown && navItemOpen ? 'shadow-md' : 'border-b' |
|||
} ${ |
|||
props.isDropdown || props.isNested |
|||
? 'hover:bg-gray-200 cursor-pointer' |
|||
: null |
|||
}`}
|
|||
onClick={() => { |
|||
if (props.isDropdown) setNavItemOpen(!navItemOpen); |
|||
if (props.onClick) { |
|||
props.onClick(); |
|||
} |
|||
}} |
|||
> |
|||
{props.titleContent} |
|||
{props.isDropdown && !props.isLoading ? ( |
|||
navItemOpen ? ( |
|||
<ChevronDownIcon className="my-auto group-hover:text-gray-700 w-5 h-5" /> |
|||
) : ( |
|||
<ChevronRightIcon className="my-auto group-hover:text-gray-700 w-5 h-5" /> |
|||
) |
|||
) : null} |
|||
{props.isLoading ? ( |
|||
// <FaSpinner className="animate-spin my-auto" />
|
|||
<div>loading</div> |
|||
) : null} |
|||
</div> |
|||
{props.isDropdown ? ( |
|||
<div |
|||
className={`duration-200 ease-in-out transition-all overflow-hidden max-h-0 border-l-8 ${ |
|||
props.isNested ? 'border-gray-500' : 'border-gray-300' |
|||
} ${navItemOpen ? 'max-h-full' : null}`}
|
|||
> |
|||
{props.dropdownContent} |
|||
</div> |
|||
) : null} |
|||
</> |
|||
); |
|||
}; |
|||
|
|||
export default NavItem; |
|||
@ -0,0 +1,110 @@ |
|||
import React from 'react'; |
|||
|
|||
import { Disclosure } from '@headlessui/react'; |
|||
import { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
export interface ChannelProps { |
|||
channel: Protobuf.Channel; |
|||
} |
|||
|
|||
const Channel = (props: ChannelProps) => { |
|||
return ( |
|||
<Disclosure> |
|||
{({ open }) => ( |
|||
<> |
|||
<Disclosure.Button className="flex w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer"> |
|||
<div className="flex"> |
|||
{props.channel.index} -{' '} |
|||
{Protobuf.Channel_Role[props.channel.role]} |
|||
</div> |
|||
</Disclosure.Button> |
|||
<Disclosure.Panel> |
|||
<div className="w-full"> |
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Bandwidth:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{props.channel.settings?.bandwidth} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Channel Number:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{props.channel.settings?.channelNum} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Coding Rate:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{props.channel.settings?.codingRate} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>ID:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{props.channel.settings?.id} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Modem Config:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{props.channel.settings?.modemConfig |
|||
? Protobuf.ChannelSettings_ModemConfig[ |
|||
props.channel.settings.modemConfig |
|||
] |
|||
: null} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Name:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{props.channel.settings?.name} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>PSK:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{props.channel.settings?.psk.toLocaleString()} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Spread Factor:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{props.channel.settings?.spreadFactor} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Tx Power:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{props.channel.settings?.txPower} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Uplink:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{props.channel.settings?.uplinkEnabled ? 'true' : 'false'} |
|||
</code> |
|||
</div> |
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Downlink:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{props.channel.settings?.downlinkEnabled ? 'true' : 'false'} |
|||
</code> |
|||
</div> |
|||
</div> |
|||
</Disclosure.Panel> |
|||
</> |
|||
)} |
|||
</Disclosure> |
|||
); |
|||
}; |
|||
|
|||
export default Channel; |
|||
@ -0,0 +1,50 @@ |
|||
import React from 'react'; |
|||
|
|||
import { Disclosure } from '@headlessui/react'; |
|||
import { |
|||
ChevronDownIcon, |
|||
ChevronRightIcon, |
|||
HashtagIcon, |
|||
} from '@heroicons/react/outline'; |
|||
import { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
import type { languageTemplate } from '../../../App'; |
|||
import Channel from './Channel'; |
|||
|
|||
export interface ChannelsProps { |
|||
isReady: boolean; |
|||
channels: Protobuf.Channel[]; |
|||
translations: languageTemplate; |
|||
} |
|||
|
|||
const Channels = (props: ChannelsProps) => { |
|||
return ( |
|||
<Disclosure> |
|||
{({ open }) => ( |
|||
<> |
|||
<Disclosure.Button className="flex w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer"> |
|||
<div className="flex"> |
|||
<HashtagIcon className="my-auto mr-2 2-5 h-5" /> |
|||
{props.translations.device_channels_title} |
|||
</div> |
|||
{open ? ( |
|||
<ChevronDownIcon className="my-auto group-hover:text-gray-700 w-5 h-5" /> |
|||
) : ( |
|||
<ChevronRightIcon className="my-auto group-hover:text-gray-700 w-5 h-5" /> |
|||
)} |
|||
</Disclosure.Button> |
|||
<Disclosure.Panel> |
|||
<> |
|||
{props.channels.map((channel, index) => { |
|||
if (channel.role !== Protobuf.Channel_Role.DISABLED) |
|||
return <Channel channel={channel} />; |
|||
})} |
|||
</> |
|||
</Disclosure.Panel> |
|||
</> |
|||
)} |
|||
</Disclosure> |
|||
); |
|||
}; |
|||
|
|||
export default Channels; |
|||
@ -0,0 +1,124 @@ |
|||
import React from 'react'; |
|||
|
|||
import { Disclosure } from '@headlessui/react'; |
|||
import { |
|||
AdjustmentsIcon, |
|||
ChevronDownIcon, |
|||
ChevronRightIcon, |
|||
SaveIcon, |
|||
} from '@heroicons/react/outline'; |
|||
import { IHTTPConnection, Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
import type { languageTemplate } from '../../../App'; |
|||
|
|||
interface DeviceProps { |
|||
isReady: boolean; |
|||
preferences: Protobuf.RadioConfig_UserPreferences; |
|||
connection: IHTTPConnection; |
|||
translations: languageTemplate; |
|||
} |
|||
|
|||
const Device = (props: DeviceProps) => { |
|||
return ( |
|||
<Disclosure> |
|||
{({ open }) => ( |
|||
<> |
|||
<Disclosure.Button className="flex w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer"> |
|||
<div className="flex"> |
|||
<AdjustmentsIcon className="my-auto mr-2 w-5 h-5" /> |
|||
{props.translations.device_settings_title} |
|||
</div> |
|||
{open ? ( |
|||
<ChevronDownIcon className="my-auto group-hover:text-gray-700 w-5 h-5" /> |
|||
) : ( |
|||
<ChevronRightIcon className="my-auto group-hover:text-gray-700 w-5 h-5" /> |
|||
)} |
|||
</Disclosure.Button> |
|||
<Disclosure.Panel> |
|||
<> |
|||
<div className="flex whitespace-nowrap p-3 justify-between border-b"> |
|||
<div className="my-auto"> |
|||
{props.translations.device_region_title} |
|||
</div> |
|||
<div className="flex shadow-md rounded-md ml-2"> |
|||
<select |
|||
value={ |
|||
props.preferences?.region ?? Protobuf.RegionCode.Unset |
|||
} |
|||
onChange={(e) => { |
|||
props.preferences.region = parseInt(e.target.value); |
|||
}} |
|||
> |
|||
<option value={Protobuf.RegionCode.ANZ}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.ANZ]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.CN}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.CN]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.EU433}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.EU433]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.EU865}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.EU865]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.JP}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.JP]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.KR}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.KR]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.TW}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.TW]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.US}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.US]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.Unset}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.Unset]} |
|||
</option> |
|||
</select> |
|||
</div> |
|||
</div> |
|||
<div className="flex whitespace-nowrap p-3 justify-between border-b"> |
|||
<div className="my-auto"> |
|||
{props.translations.device_wifi_ssid} |
|||
</div> |
|||
<div className="flex shadow-md rounded-md ml-2"> |
|||
<input |
|||
onChange={() => {}} |
|||
type="text" |
|||
value={props.preferences.wifiSsid} |
|||
/> |
|||
</div> |
|||
</div> |
|||
<div className="flex whitespace-nowrap p-3 justify-between border-b"> |
|||
<div className="my-auto"> |
|||
{props.translations.device_wifi_psk} |
|||
</div> |
|||
<div className="flex shadow-md rounded-md ml-2"> |
|||
<input |
|||
type="password" |
|||
value={props.preferences.wifiPassword} |
|||
/> |
|||
</div> |
|||
</div> |
|||
<div className="flex group p-1 bg-gray-100 cursor-pointer hover:bg-gray-200 border-b"> |
|||
<div |
|||
className="flex m-auto font-medium group-hover:text-gray-700" |
|||
onClick={() => { |
|||
props.connection.setPreferences(props.preferences); |
|||
}} |
|||
> |
|||
<SaveIcon className="m-auto mr-2 group-hover:text-gray-700 w-5 h-5" /> |
|||
{props.translations.save_changes_button} |
|||
</div> |
|||
</div> |
|||
</> |
|||
</Disclosure.Panel> |
|||
</> |
|||
)} |
|||
</Disclosure> |
|||
); |
|||
}; |
|||
|
|||
export default Device; |
|||
@ -0,0 +1,56 @@ |
|||
import React from 'react'; |
|||
|
|||
import { Disclosure } from '@headlessui/react'; |
|||
import { |
|||
ChevronDownIcon, |
|||
ChevronRightIcon, |
|||
UsersIcon, |
|||
} from '@heroicons/react/outline'; |
|||
import type { Types } from '@meshtastic/meshtasticjs'; |
|||
|
|||
import type { languageTemplate } from '../../../App'; |
|||
import Node from './Node'; |
|||
|
|||
interface NodesProps { |
|||
isReady: boolean; |
|||
nodes: Types.NodeInfoPacket[]; |
|||
translations: languageTemplate; |
|||
myId: number; |
|||
} |
|||
|
|||
const Nodes = (props: NodesProps) => { |
|||
return ( |
|||
<Disclosure> |
|||
{({ open }) => ( |
|||
<> |
|||
<Disclosure.Button className="flex rounded-t-md w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer"> |
|||
<div className="flex"> |
|||
<UsersIcon className="my-auto mr-2 w-5 h-5" /> |
|||
{props.translations.nodes_title} |
|||
</div> |
|||
{open ? ( |
|||
<ChevronDownIcon className="my-auto group-hover:text-gray-700 w-5 h-5" /> |
|||
) : ( |
|||
<ChevronRightIcon className="my-auto group-hover:text-gray-700 w-5 h-5" /> |
|||
)} |
|||
</Disclosure.Button> |
|||
<Disclosure.Panel className="shadow-inner"> |
|||
{props.nodes.length ? ( |
|||
props.nodes.map((node, index) => ( |
|||
<Node key={index} node={node} myId={props.myId} /> |
|||
)) |
|||
) : ( |
|||
<div className="flex border-b border-gray-300"> |
|||
<div className="m-auto p-3 text-gray-500"> |
|||
{props.translations.no_nodes_message} |
|||
</div> |
|||
</div> |
|||
)} |
|||
</Disclosure.Panel> |
|||
</> |
|||
)} |
|||
</Disclosure> |
|||
); |
|||
}; |
|||
|
|||
export default Nodes; |
|||
@ -0,0 +1,82 @@ |
|||
import React from 'react'; |
|||
|
|||
import { Disclosure } from '@headlessui/react'; |
|||
import { |
|||
ClockIcon, |
|||
DesktopComputerIcon, |
|||
FlagIcon, |
|||
GlobeIcon, |
|||
LightningBoltIcon, |
|||
} from '@heroicons/react/outline'; |
|||
import type { Types } from '@meshtastic/meshtasticjs'; |
|||
|
|||
export interface NodeProps { |
|||
node: Types.NodeInfoPacket; |
|||
myId: number; |
|||
} |
|||
|
|||
const Node = (props: NodeProps) => { |
|||
return ( |
|||
<Disclosure> |
|||
{({ open }) => ( |
|||
<> |
|||
<Disclosure.Button className="flex w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer"> |
|||
<div className="flex"> |
|||
{props.node.data.num === props.myId ? ( |
|||
<FlagIcon className="text-yellow-500 my-auto mr-2 w-5 h-5" /> |
|||
) : ( |
|||
<DesktopComputerIcon className="my-auto mr-2 w-5 h-5" /> |
|||
)} |
|||
<div className="m-auto">{props.node.data.user?.longName}</div> |
|||
</div> |
|||
</Disclosure.Button> |
|||
<Disclosure.Panel> |
|||
<div> |
|||
<p> |
|||
SNR:{' '} |
|||
{props.node.packet?.rxSnr ? props.node.packet.rxSnr : 'Unknown'} |
|||
</p> |
|||
<p> |
|||
RSSI:{' '} |
|||
{props.node.packet?.rxRssi |
|||
? props.node.packet.rxRssi |
|||
: 'Unknown'} |
|||
</p> |
|||
<p> |
|||
{`Last heard: ${ |
|||
props.node.data?.lastHeard |
|||
? new Date(props.node.data.lastHeard).toLocaleString() |
|||
: 'Unknown' |
|||
}`}{' '}
|
|||
{} |
|||
</p> |
|||
<div className="flex"> |
|||
<GlobeIcon className="my-auto mr-2 w-5 h-5" /> |
|||
<p> |
|||
{props.node.data.position?.latitudeI}, |
|||
{props.node.data.position?.longitudeI} |
|||
</p> |
|||
</div> |
|||
|
|||
<div className="flex"> |
|||
<GlobeIcon className="my-auto mr-2 w-5 h-5" /> |
|||
<p>{props.node.data.position?.altitude}</p> |
|||
</div> |
|||
|
|||
<div className="flex"> |
|||
<ClockIcon className="my-auto mr-2 w-5 h-5" /> |
|||
<p>{props.node.data.position?.time}</p> |
|||
</div> |
|||
<div className="flex"> |
|||
<LightningBoltIcon className="my-auto mr-2 w-5 h-5" /> |
|||
<p>{props.node.data.position?.batteryLevel}</p> |
|||
</div> |
|||
</div> |
|||
</Disclosure.Panel> |
|||
</> |
|||
)} |
|||
</Disclosure> |
|||
); |
|||
}; |
|||
|
|||
export default Node; |
|||
@ -1,146 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
import { HashtagIcon } from '@heroicons/react/outline'; |
|||
import { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
import type { languageTemplate } from '../../App'; |
|||
import NavItem from '../NavItem'; |
|||
|
|||
interface SidebarChannelsProps { |
|||
IsReady: boolean; |
|||
Channels: Protobuf.Channel[]; |
|||
Translations: languageTemplate; |
|||
} |
|||
|
|||
const SidebarChannels = (props: SidebarChannelsProps) => { |
|||
return ( |
|||
<NavItem |
|||
isDropdown={true} |
|||
open={false} |
|||
isNested={false} |
|||
titleContent={ |
|||
<div className="flex"> |
|||
<HashtagIcon className="my-auto mr-2 2-5 h-5" /> |
|||
{props.Translations.device_channels_title} |
|||
</div> |
|||
} |
|||
isLoading={!props.IsReady} |
|||
dropdownContent={ |
|||
<> |
|||
{props.Channels.map((channel, index) => { |
|||
if (channel.role !== Protobuf.Channel_Role.DISABLED) |
|||
return ( |
|||
<NavItem |
|||
key={index} |
|||
isDropdown={true} |
|||
isNested={true} |
|||
open={false} |
|||
titleContent={ |
|||
<div className="flex"> |
|||
{channel.index} - {Protobuf.Channel_Role[channel.role]} |
|||
</div> |
|||
} |
|||
dropdownContent={ |
|||
<NavItem |
|||
isDropdown={false} |
|||
isNested={false} |
|||
open={false} |
|||
titleContent={ |
|||
<div className="w-full"> |
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Bandwidth:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{channel.settings?.bandwidth} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Channel Number:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{channel.settings?.channelNum} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Coding Rate:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{channel.settings?.codingRate} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>ID:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{channel.settings?.id} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Modem Config:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{channel.settings?.modemConfig |
|||
? Protobuf.ChannelSettings_ModemConfig[ |
|||
channel.settings.modemConfig |
|||
] |
|||
: null} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Name:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{channel.settings?.name} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>PSK:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{channel.settings?.psk.toLocaleString()} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Spread Factor:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{channel.settings?.spreadFactor} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Tx Power:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{channel.settings?.txPower} |
|||
</code> |
|||
</div> |
|||
|
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Uplink:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{channel.settings?.uplinkEnabled |
|||
? 'true' |
|||
: 'false'} |
|||
</code> |
|||
</div> |
|||
<div className="flex justify-between border-b hover:bg-gray-200"> |
|||
<p>Downlink:</p> |
|||
<code className="bg-gray-200 rounded-full px-2"> |
|||
{channel.settings?.downlinkEnabled |
|||
? 'true' |
|||
: 'false'} |
|||
</code> |
|||
</div> |
|||
</div> |
|||
} |
|||
/> |
|||
} |
|||
/> |
|||
); |
|||
})} |
|||
</> |
|||
} |
|||
/> |
|||
); |
|||
}; |
|||
|
|||
export default SidebarChannels; |
|||
@ -1,105 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
import { AdjustmentsIcon, SaveIcon } from '@heroicons/react/outline'; |
|||
import { IHTTPConnection, Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
import type { languageTemplate } from '../../App'; |
|||
import NavItem from '../NavItem'; |
|||
|
|||
interface SidebarDeviceSettingsProps { |
|||
IsReady: boolean; |
|||
Preferences: Protobuf.RadioConfig_UserPreferences; |
|||
Connection: IHTTPConnection; |
|||
Translations: languageTemplate; |
|||
} |
|||
|
|||
const SidebarDeviceSettings = (props: SidebarDeviceSettingsProps) => { |
|||
return ( |
|||
<NavItem |
|||
isDropdown={true} |
|||
open={false} |
|||
isNested={false} |
|||
titleContent={ |
|||
<div className="flex"> |
|||
<AdjustmentsIcon className="my-auto mr-2 w-5 h-5" /> |
|||
{props.Translations.device_settings_title} |
|||
</div> |
|||
} |
|||
isLoading={!props.IsReady} |
|||
dropdownContent={ |
|||
<> |
|||
<div className="flex whitespace-nowrap p-3 justify-between border-b"> |
|||
<div className="my-auto"> |
|||
{props.Translations.device_region_title} |
|||
</div> |
|||
<div className="flex shadow-md rounded-md ml-2"> |
|||
<select |
|||
value={props.Preferences?.region ?? Protobuf.RegionCode.Unset} |
|||
onChange={(e) => { |
|||
props.Preferences.region = parseInt(e.target.value); |
|||
}} |
|||
> |
|||
<option value={Protobuf.RegionCode.ANZ}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.ANZ]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.CN}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.CN]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.EU433}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.EU433]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.EU865}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.EU865]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.JP}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.JP]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.KR}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.KR]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.TW}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.TW]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.US}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.US]} |
|||
</option> |
|||
<option value={Protobuf.RegionCode.Unset}> |
|||
{Protobuf.RegionCode[Protobuf.RegionCode.Unset]} |
|||
</option> |
|||
</select> |
|||
</div> |
|||
</div> |
|||
<div className="flex whitespace-nowrap p-3 justify-between border-b"> |
|||
<div className="my-auto">{props.Translations.device_wifi_ssid}</div> |
|||
<div className="flex shadow-md rounded-md ml-2"> |
|||
<input |
|||
onChange={() => {}} |
|||
type="text" |
|||
value={props.Preferences.wifiSsid} |
|||
/> |
|||
</div> |
|||
</div> |
|||
<div className="flex whitespace-nowrap p-3 justify-between border-b"> |
|||
<div className="my-auto">{props.Translations.device_wifi_psk}</div> |
|||
<div className="flex shadow-md rounded-md ml-2"> |
|||
<input type="password" value={props.Preferences.wifiPassword} /> |
|||
</div> |
|||
</div> |
|||
<div className="flex group p-1 bg-gray-100 cursor-pointer hover:bg-gray-200 border-b"> |
|||
<div |
|||
className="flex m-auto font-medium group-hover:text-gray-700" |
|||
onClick={() => { |
|||
props.Connection.setPreferences(props.Preferences); |
|||
}} |
|||
> |
|||
<SaveIcon className="m-auto mr-2 group-hover:text-gray-700 w-5 h-5" /> |
|||
{props.Translations.save_changes_button} |
|||
</div> |
|||
</div> |
|||
</> |
|||
} |
|||
/> |
|||
); |
|||
}; |
|||
|
|||
export default SidebarDeviceSettings; |
|||
@ -1,119 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
import { |
|||
ClockIcon, |
|||
DesktopComputerIcon, |
|||
FlagIcon, |
|||
GlobeIcon, |
|||
LightningBoltIcon, |
|||
UsersIcon, |
|||
} from '@heroicons/react/outline'; |
|||
import type { Types } from '@meshtastic/meshtasticjs'; |
|||
|
|||
import type { languageTemplate } from '../../App'; |
|||
import NavItem from '../NavItem'; |
|||
|
|||
interface sidebarNodesProps { |
|||
IsReady: boolean; |
|||
Nodes: Types.NodeInfoPacket[]; |
|||
Translations: languageTemplate; |
|||
myId: number; |
|||
} |
|||
|
|||
const SidebarNodes = (props: sidebarNodesProps) => { |
|||
return ( |
|||
<NavItem |
|||
isDropdown={true} |
|||
open={false} |
|||
isNested={false} |
|||
titleContent={ |
|||
<div className="flex"> |
|||
<UsersIcon className="my-auto mr-2 w-5 h-5" /> |
|||
{props.Translations.nodes_title} |
|||
<div className="flex m-auto rounded-full bg-gray-300 w-6 h-6 text-sm ml-2"> |
|||
<div className="m-auto">{props.Nodes.length ?? 0}</div> |
|||
</div> |
|||
</div> |
|||
} |
|||
isLoading={!props.IsReady} |
|||
dropdownContent={ |
|||
props.Nodes.length ? ( |
|||
props.Nodes.map((node, index) => ( |
|||
<NavItem |
|||
key={index} |
|||
isDropdown={true} |
|||
isNested={true} |
|||
open={false} |
|||
titleContent={ |
|||
<div key={index} className="flex"> |
|||
{node.data.num === props.myId ? ( |
|||
<FlagIcon className="text-yellow-500 my-auto mr-2 w-5 h-5" /> |
|||
) : ( |
|||
<DesktopComputerIcon className="my-auto mr-2 w-5 h-5" /> |
|||
)} |
|||
<div className="m-auto">{node.data.user?.longName}</div> |
|||
</div> |
|||
} |
|||
dropdownContent={ |
|||
<NavItem |
|||
isDropdown={false} |
|||
isNested={true} |
|||
open={false} |
|||
titleContent={ |
|||
<div> |
|||
<p> |
|||
SNR:{' '} |
|||
{node.packet?.rxSnr ? node.packet.rxSnr : 'Unknown'} |
|||
</p> |
|||
<p> |
|||
RSSI:{' '} |
|||
{node.packet?.rxRssi ? node.packet.rxRssi : 'Unknown'} |
|||
</p> |
|||
<p> |
|||
{`Last heard: ${ |
|||
node.data?.lastHeard |
|||
? new Date(node.data.lastHeard).toLocaleString() |
|||
: 'Unknown' |
|||
}`}{' '}
|
|||
{} |
|||
</p> |
|||
<div className="flex"> |
|||
<GlobeIcon className="my-auto mr-2 w-5 h-5" /> |
|||
<p> |
|||
{node.data.position?.latitudeI}, |
|||
{node.data.position?.longitudeI} |
|||
</p> |
|||
</div> |
|||
|
|||
<div className="flex"> |
|||
<GlobeIcon className="my-auto mr-2 w-5 h-5" /> |
|||
<p>{node.data.position?.altitude}</p> |
|||
</div> |
|||
|
|||
<div className="flex"> |
|||
<ClockIcon className="my-auto mr-2 w-5 h-5" /> |
|||
<p>{node.data.position?.time}</p> |
|||
</div> |
|||
<div className="flex"> |
|||
<LightningBoltIcon className="my-auto mr-2 w-5 h-5" /> |
|||
<p>{node.data.position?.batteryLevel}</p> |
|||
</div> |
|||
</div> |
|||
} |
|||
/> |
|||
} |
|||
/> |
|||
)) |
|||
) : ( |
|||
<div className="flex border-b border-gray-300"> |
|||
<div className="m-auto p-3 text-gray-500"> |
|||
{props.Translations.no_nodes_message} |
|||
</div> |
|||
</div> |
|||
) |
|||
} |
|||
/> |
|||
); |
|||
}; |
|||
|
|||
export default SidebarNodes; |
|||
@ -1,132 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
import { Br, Jp, Us } from 'react-flags-select'; |
|||
|
|||
import { CogIcon } from '@heroicons/react/outline'; |
|||
|
|||
import type { languageTemplate } from '../../App'; |
|||
import { LanguageEnum } from '../../App'; |
|||
import ToggleSwitch from '../basic/ToggleSwitch'; |
|||
import NavItem from '../NavItem'; |
|||
|
|||
interface SidebarUISettingsProps { |
|||
Language: LanguageEnum; |
|||
SetLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>; |
|||
Translations: languageTemplate; |
|||
} |
|||
|
|||
const SidebarUISettings = (props: SidebarUISettingsProps) => { |
|||
return ( |
|||
<NavItem |
|||
isDropdown={true} |
|||
isNested={false} |
|||
open={false} |
|||
titleContent={ |
|||
<div className="flex"> |
|||
<CogIcon className="my-auto mr-2 w-5 h-5" /> |
|||
{props.Translations.ui_settings_title} |
|||
</div> |
|||
} |
|||
dropdownContent={ |
|||
<> |
|||
<NavItem |
|||
isDropdown={false} |
|||
isNested={true} |
|||
open={false} |
|||
titleContent={ |
|||
<> |
|||
<div className="my-auto"> |
|||
{props.Translations.color_scheme_title} |
|||
</div> |
|||
<div className="flex shadow-md rounded-md ml-2"> |
|||
{/* <div className="bg-gray-200 flex group p-2 rounded-l-md border border-gray-300 hover:bg-gray-200 cursor-pointer"> |
|||
<FaSun className="m-auto group-hover:text-gray-700" /> |
|||
</div> |
|||
<div className="flex group p-2 border border-gray-300 hover:bg-gray-200 cursor-pointer"> |
|||
<FaMoon className="m-auto group-hover:text-gray-700" /> |
|||
</div> |
|||
<div className="flex group p-2 rounded-r-md border border-gray-300 hover:bg-gray-200 cursor-pointer"> |
|||
<FaLaptop className="m-auto group-hover:text-gray-700" /> |
|||
</div> */} |
|||
</div> |
|||
</> |
|||
} |
|||
/> |
|||
<NavItem |
|||
isDropdown={true} |
|||
isNested={true} |
|||
open={false} |
|||
titleContent={ |
|||
<div className="flex my-auto"> |
|||
{props.Translations.language_title} |
|||
<div className="my-auto"> |
|||
{props.Language === LanguageEnum.ENGLISH ? ( |
|||
<Us className="ml-2 w-8 shadow-md" /> |
|||
) : props.Language === LanguageEnum.JAPANESE ? ( |
|||
<Jp className="ml-2 w-8 shadow-md" /> |
|||
) : null} |
|||
</div> |
|||
</div> |
|||
} |
|||
dropdownContent={ |
|||
<> |
|||
<NavItem |
|||
onClick={() => { |
|||
props.SetLanguage(LanguageEnum.ENGLISH); |
|||
}} |
|||
open={false} |
|||
isDropdown={false} |
|||
isNested={true} |
|||
titleContent={ |
|||
<> |
|||
English <Us className="w-8 shadow-md" /> |
|||
</> |
|||
} |
|||
/> |
|||
<NavItem |
|||
onClick={() => { |
|||
props.SetLanguage(LanguageEnum.PORTUGUESE); |
|||
}} |
|||
open={false} |
|||
isDropdown={false} |
|||
isNested={true} |
|||
titleContent={ |
|||
<> |
|||
Português <Br className="w-8 shadow-md" /> |
|||
</> |
|||
} |
|||
/> |
|||
<NavItem |
|||
onClick={() => { |
|||
props.SetLanguage(LanguageEnum.JAPANESE); |
|||
}} |
|||
open={false} |
|||
isDropdown={false} |
|||
isNested={true} |
|||
titleContent={ |
|||
<> |
|||
日本語 <Jp className="w-8 shadow-md" /> |
|||
</> |
|||
} |
|||
/> |
|||
</> |
|||
} |
|||
/> |
|||
<NavItem |
|||
isDropdown={false} |
|||
isNested={true} |
|||
open={false} |
|||
titleContent={ |
|||
<> |
|||
<div className="">Test</div> |
|||
<ToggleSwitch active={true} /> |
|||
</> |
|||
} |
|||
/> |
|||
</> |
|||
} |
|||
/> |
|||
); |
|||
}; |
|||
|
|||
export default SidebarUISettings; |
|||
@ -0,0 +1,50 @@ |
|||
import React from 'react'; |
|||
|
|||
import { Disclosure } from '@headlessui/react'; |
|||
import { |
|||
ChevronDownIcon, |
|||
ChevronRightIcon, |
|||
CogIcon, |
|||
} from '@heroicons/react/outline'; |
|||
|
|||
import type { LanguageEnum, languageTemplate } from '../../../App'; |
|||
import Translations from './Translations'; |
|||
|
|||
interface UIProps { |
|||
language: LanguageEnum; |
|||
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>; |
|||
translations: languageTemplate; |
|||
darkmode: boolean; |
|||
setDarkmode: React.Dispatch<React.SetStateAction<boolean>>; |
|||
} |
|||
|
|||
const UI = (props: UIProps) => { |
|||
return ( |
|||
<Disclosure> |
|||
{({ open }) => ( |
|||
<> |
|||
<Disclosure.Button className="flex w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer"> |
|||
<div className="flex"> |
|||
<CogIcon className="my-auto mr-2 w-5 h-5" /> |
|||
{props.translations.ui_settings_title} |
|||
</div> |
|||
{open ? ( |
|||
<ChevronDownIcon className="my-auto group-hover:text-gray-700 w-5 h-5" /> |
|||
) : ( |
|||
<ChevronRightIcon className="my-auto group-hover:text-gray-700 w-5 h-5" /> |
|||
)} |
|||
</Disclosure.Button> |
|||
<Disclosure.Panel> |
|||
<Translations |
|||
language={props.language} |
|||
setLanguage={props.setLanguage} |
|||
translations={props.translations} |
|||
/> |
|||
</Disclosure.Panel> |
|||
</> |
|||
)} |
|||
</Disclosure> |
|||
); |
|||
}; |
|||
|
|||
export default UI; |
|||
@ -0,0 +1,49 @@ |
|||
import React from 'react'; |
|||
|
|||
import { Br, Jp, Us } from 'react-flags-select'; |
|||
|
|||
import { Disclosure } from '@headlessui/react'; |
|||
|
|||
import { LanguageEnum, languageTemplate } from '../../../App'; |
|||
|
|||
export interface TranslationsProps { |
|||
language: LanguageEnum; |
|||
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>; |
|||
translations: languageTemplate; |
|||
} |
|||
|
|||
const Translations = (props: TranslationsProps) => { |
|||
return ( |
|||
<Disclosure> |
|||
{({ open }) => ( |
|||
<> |
|||
<Disclosure.Button className="flex w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer"> |
|||
<div className="flex my-auto"> |
|||
{props.translations.language_title} |
|||
<div className="my-auto"> |
|||
{props.language === LanguageEnum.ENGLISH ? ( |
|||
<Us className="ml-2 w-8 shadow-md" /> |
|||
) : props.language === LanguageEnum.JAPANESE ? ( |
|||
<Jp className="ml-2 w-8 shadow-md" /> |
|||
) : null} |
|||
</div> |
|||
</div> |
|||
</Disclosure.Button> |
|||
<Disclosure.Panel> |
|||
<div> |
|||
English <Us className="w-8 shadow-md" /> |
|||
</div> |
|||
<div> |
|||
Português <Br className="w-8 shadow-md" /> |
|||
</div> |
|||
<div> |
|||
日本語 <Jp className="w-8 shadow-md" /> |
|||
</div> |
|||
</Disclosure.Panel> |
|||
</> |
|||
)} |
|||
</Disclosure> |
|||
); |
|||
}; |
|||
|
|||
export default Translations; |
|||
@ -1,56 +0,0 @@ |
|||
Arguments: |
|||
/usr/bin/node /usr/share/yarn/bin/yarn.js add -D tailwindcss@latest postcss@latest autoprefixer@latest |
|||
|
|||
PATH: |
|||
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/mnt/c/Program Files/AdoptOpenJDK/jdk-11.0.10.9-hotspot/bin:/mnt/c/Program Files (x86)/Common Files/Oracle/Java/javapath:/mnt/c/Windows/system32:/mnt/c/Windows:/mnt/c/Windows/System32/Wbem:/mnt/c/Windows/System32/WindowsPowerShell/v1.0/:/mnt/c/Windows/System32/OpenSSH/:/mnt/c/Program Files (x86)/NVIDIA Corporation/PhysX/Common:/mnt/c/Program Files/Git/cmd:/mnt/c/Program Files/nodejs/:/mnt/c/Users/sacha/AppData/Local/Programs/Python/Python39/Scripts/:/mnt/c/Users/sacha/AppData/Local/Programs/Python/Python39/:/mnt/c/Users/sacha/AppData/Local/Microsoft/WindowsApps:/mnt/c/Users/sacha/AppData/Local/Programs/Microsoft VS Code/bin:/mnt/c/Users/sacha/AppData/Roaming/npm:/home/sachaw/.yarn/bin |
|||
|
|||
Yarn version: |
|||
1.22.5 |
|||
|
|||
Node version: |
|||
15.12.0 |
|||
|
|||
Platform: |
|||
linux x64 |
|||
|
|||
Trace: |
|||
Error: getaddrinfo EAI_AGAIN registry.yarnpkg.com |
|||
at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:69:26) |
|||
|
|||
npm manifest: |
|||
{ |
|||
"scripts": { |
|||
"start": "snowpack dev", |
|||
"build": "snowpack build", |
|||
"test": "web-test-runner \"src/**/*.test.tsx\"", |
|||
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"", |
|||
"lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"" |
|||
}, |
|||
"dependencies": { |
|||
"react": "^17.0.0", |
|||
"react-dom": "^17.0.0" |
|||
}, |
|||
"devDependencies": { |
|||
"@snowpack/plugin-dotenv": "^2.0.5", |
|||
"@snowpack/plugin-react-refresh": "^2.4.0", |
|||
"@snowpack/plugin-typescript": "^1.2.0", |
|||
"@snowpack/web-test-runner-plugin": "^0.2.0", |
|||
"@testing-library/react": "^11.0.0", |
|||
"@types/chai": "^4.2.13", |
|||
"@types/mocha": "^8.2.0", |
|||
"@types/react": "^17.0.0", |
|||
"@types/react-dom": "^17.0.0", |
|||
"@types/snowpack-env": "^2.3.2", |
|||
"@web/test-runner": "^0.12.0", |
|||
"chai": "^4.2.0", |
|||
"prettier": "^2.0.5", |
|||
"snowpack": "^3.0.1", |
|||
"typescript": "^4.0.0" |
|||
} |
|||
} |
|||
|
|||
yarn manifest: |
|||
No manifest |
|||
|
|||
Lockfile: |
|||
No lockfile |
|||
Loading…
Reference in new issue