29 changed files with 9025 additions and 113 deletions
@ -0,0 +1,6 @@ |
|||||
|
[submodule "design"] |
||||
|
path = design |
||||
|
url = https://github.com/meshtastic/meshtastic-design/ |
||||
|
[submodule "public/design"] |
||||
|
path = public/design |
||||
|
url = https://github.com/meshtastic/meshtastic-design/ |
||||
@ -0,0 +1,6 @@ |
|||||
|
module.exports = { |
||||
|
plugins: { |
||||
|
tailwindcss: {}, |
||||
|
autoprefixer: {}, |
||||
|
}, |
||||
|
} |
||||
|
Before Width: | Height: | Size: 3.1 KiB |
@ -1,46 +0,0 @@ |
|||||
.App { |
|
||||
text-align: center; |
|
||||
} |
|
||||
.App code { |
|
||||
background: #FFF3; |
|
||||
padding: 4px 8px; |
|
||||
border-radius: 4px; |
|
||||
} |
|
||||
.App p { |
|
||||
margin: 0.4rem; |
|
||||
} |
|
||||
|
|
||||
.App-logo { |
|
||||
height: 40vmin; |
|
||||
pointer-events: none; |
|
||||
} |
|
||||
|
|
||||
@media (prefers-reduced-motion: no-preference) { |
|
||||
.App-logo { |
|
||||
animation: App-logo-spin infinite 20s linear; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.App-header { |
|
||||
background-color: #282c34; |
|
||||
min-height: 100vh; |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
align-items: center; |
|
||||
justify-content: center; |
|
||||
font-size: calc(10px + 2vmin); |
|
||||
color: white; |
|
||||
} |
|
||||
|
|
||||
.App-link { |
|
||||
color: #61dafb; |
|
||||
} |
|
||||
|
|
||||
@keyframes App-logo-spin { |
|
||||
from { |
|
||||
transform: rotate(0deg); |
|
||||
} |
|
||||
to { |
|
||||
transform: rotate(360deg); |
|
||||
} |
|
||||
} |
|
||||
@ -1,12 +0,0 @@ |
|||||
import * as React from 'react'; |
|
||||
import { render } from '@testing-library/react'; |
|
||||
import { expect } from 'chai'; |
|
||||
import App from './App'; |
|
||||
|
|
||||
describe('<App>', () => { |
|
||||
it('renders learn react link', () => { |
|
||||
const { getByText } = render(<App />); |
|
||||
const linkElement = getByText(/learn react/i); |
|
||||
expect(document.body.contains(linkElement)); |
|
||||
}); |
|
||||
}); |
|
||||
@ -1,41 +1,206 @@ |
|||||
import React, { useState, useEffect } from 'react'; |
import React, { useEffect, useState } from 'react'; |
||||
import logo from './logo.svg'; |
|
||||
import './App.css'; |
|
||||
|
|
||||
interface AppProps {} |
import { |
||||
|
Client, |
||||
|
IHTTPConnection, |
||||
|
Protobuf, |
||||
|
SettingsManager, |
||||
|
Types, |
||||
|
} from '@meshtastic/meshtasticjs'; |
||||
|
|
||||
|
import Header from './components/Header'; |
||||
|
import Main from './Main'; |
||||
|
import Translations_English from './translations/en'; |
||||
|
import Translations_Japanese from './translations/jp'; |
||||
|
|
||||
|
export enum LanguageEnum { |
||||
|
ENGLISH, |
||||
|
JAPANESE, |
||||
|
} |
||||
|
|
||||
|
export interface languageTemplate { |
||||
|
no_messages_message: string; |
||||
|
ui_settings_title: string; |
||||
|
nodes_title: string; |
||||
|
color_scheme_title: string; |
||||
|
language_title: string; |
||||
|
device_settings_title: string; |
||||
|
device_channels_title: string; |
||||
|
device_region_title: string; |
||||
|
device_wifi_ssid: string; |
||||
|
device_wifi_psk: string; |
||||
|
save_changes_button: string; |
||||
|
no_nodes_message: string; |
||||
|
no_message_placeholder: string; |
||||
|
} |
||||
|
|
||||
|
const App = () => { |
||||
|
const [deviceStatus, setDeviceStatus] = useState( |
||||
|
{} as Types.DeviceStatusEnum, |
||||
|
); |
||||
|
const [myNodeInfo, setMyNodeInfo] = useState({} as Protobuf.MyNodeInfo); |
||||
|
const [messages, setMessages] = useState( |
||||
|
[] as { message: Types.TextPacket; ack: false }[], |
||||
|
); |
||||
|
const [channels, setChannels] = useState([] as Protobuf.Channel[]); |
||||
|
const [nodes, setNodes] = useState([] as Types.NodeInfoPacket[]); |
||||
|
const [connection, setConnection] = useState({} as IHTTPConnection); |
||||
|
const [isReady, setIsReady] = useState(false); |
||||
|
const [lastMeshInterraction, setLastMeshInterraction] = useState(0); |
||||
|
const [preferences, setPreferences] = useState( |
||||
|
{} as Protobuf.RadioConfig_UserPreferences, |
||||
|
); |
||||
|
const [language, setLanguage] = useState(LanguageEnum.ENGLISH); |
||||
|
const [translations, setTranslations] = useState(Translations_English); |
||||
|
|
||||
function App({}: AppProps) { |
|
||||
// Create the count state.
|
|
||||
const [count, setCount] = useState(0); |
|
||||
// Create the counter (+1 every second).
|
|
||||
useEffect(() => { |
useEffect(() => { |
||||
const timer = setTimeout(() => setCount(count + 1), 1000); |
switch (language) { |
||||
return () => clearTimeout(timer); |
case LanguageEnum.ENGLISH: |
||||
}, [count, setCount]); |
setTranslations(Translations_English); |
||||
// Return the App component.
|
break; |
||||
|
case LanguageEnum.JAPANESE: |
||||
|
setTranslations(Translations_Japanese); |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
}, [language]); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
const client = new Client(); |
||||
|
const connection = client.createHTTPConnection(); |
||||
|
// connection.connect(window.location.hostname, undefined, true);
|
||||
|
connection.connect({ |
||||
|
address: '192.168.105.71', |
||||
|
receiveBatchRequests: false, |
||||
|
tls: false, |
||||
|
fetchInterval: 2000, |
||||
|
}); |
||||
|
setConnection(connection); |
||||
|
SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE; |
||||
|
connection.onDeviceStatusEvent.subscribe((status) => { |
||||
|
setDeviceStatus(status); |
||||
|
if (status === Types.DeviceStatusEnum.DEVICE_CONFIGURED) { |
||||
|
setIsReady(true); |
||||
|
} |
||||
|
}); |
||||
|
connection.onMyNodeInfoEvent.subscribe(setMyNodeInfo); |
||||
|
connection.onTextPacketEvent.subscribe((message) => { |
||||
|
setMessages((messages) => [ |
||||
|
...messages, |
||||
|
{ message: message, ack: false }, |
||||
|
]); |
||||
|
}); |
||||
|
connection.onNodeInfoPacketEvent.subscribe((node) => { |
||||
|
if ( |
||||
|
nodes.findIndex( |
||||
|
(currentNode) => currentNode.data.num === node.data.num, |
||||
|
) >= 0 |
||||
|
) { |
||||
|
setNodes( |
||||
|
nodes.map((currentNode) => |
||||
|
currentNode.data.num === node.data.num ? node : currentNode, |
||||
|
), |
||||
|
); |
||||
|
} else { |
||||
|
setNodes((nodes) => [...nodes, node]); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
connection.onAdminPacketEvent.subscribe((adminMessage) => { |
||||
|
switch (adminMessage.data.variant.oneofKind) { |
||||
|
case 'getRadioResponse': |
||||
|
if (adminMessage.data.variant.getRadioResponse.preferences) { |
||||
|
setPreferences( |
||||
|
adminMessage.data.variant.getRadioResponse.preferences, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
// case 'getChannelResponse':
|
||||
|
// if (adminMessage.data.variant.getChannelResponse) {
|
||||
|
// setChannels((channels) => [
|
||||
|
// ...channels,
|
||||
|
// adminMessage.data.variant.getChannelResponse,
|
||||
|
// ]);
|
||||
|
// }
|
||||
|
|
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
connection.onMeshHeartbeat.subscribe(setLastMeshInterraction); |
||||
|
|
||||
|
connection.onRoutingPacketEvent.subscribe((routingPacket) => { |
||||
|
console.log(routingPacket); |
||||
|
|
||||
|
messages.map((message) => { |
||||
|
console.log( |
||||
|
`${ |
||||
|
routingPacket.payloadVariant.oneofKind === 'decoded' |
||||
|
? routingPacket.payloadVariant.decoded.requestId |
||||
|
: null |
||||
|
} === ${message.message.packet.id}: ${ |
||||
|
routingPacket.payloadVariant.oneofKind === 'decoded' |
||||
|
? routingPacket.payloadVariant.decoded.requestId |
||||
|
: null === message.message.packet.id |
||||
|
}`,
|
||||
|
); |
||||
|
}); |
||||
|
// messages.find((message) => {
|
||||
|
// message.message.packet.id === routingPacket.decoded.requestId;
|
||||
|
// });
|
||||
|
}); |
||||
|
}, []); |
||||
return ( |
return ( |
||||
<div className="App"> |
<div className="flex flex-col h-screen w-screen"> |
||||
<header className="App-header"> |
{/* <Head> |
||||
<img src={logo} className="App-logo" alt="logo" /> |
<link |
||||
<p> |
rel="apple-touch-icon" |
||||
Edit <code>src/App.tsx</code> and save to reload. |
sizes="180x180" |
||||
</p> |
href="/design/web/apple-touch-icon.png" |
||||
<p> |
/> |
||||
Page has been open for <code>{count}</code> seconds. |
<link |
||||
</p> |
rel="icon" |
||||
<p> |
type="image/png" |
||||
<a |
sizes="32x32" |
||||
className="App-link" |
href="/design/web/favicon-32x32.png" |
||||
href="https://reactjs.org" |
/> |
||||
target="_blank" |
<link |
||||
rel="noopener noreferrer" |
rel="icon" |
||||
> |
type="image/png" |
||||
Learn React |
sizes="16x16" |
||||
</a> |
href="/design/web/favicon-16x16.png" |
||||
</p> |
/> |
||||
</header> |
<link rel="manifest" href="/design/web/site.webmanifest" /> |
||||
|
<link |
||||
|
rel="mask-icon" |
||||
|
href="/design/web/safari-pinned-tab.svg" |
||||
|
color="#67ea94" |
||||
|
/> |
||||
|
<meta name="theme-color" content="#67ea94" /> |
||||
|
</Head> */} |
||||
|
<Header |
||||
|
status={deviceStatus} |
||||
|
IsReady={isReady} |
||||
|
LastMeshInterraction={lastMeshInterraction} |
||||
|
/> |
||||
|
<Main |
||||
|
IsReady={isReady} |
||||
|
Messages={messages} |
||||
|
MyNodeInfo={myNodeInfo} |
||||
|
Connection={connection} |
||||
|
Nodes={nodes} |
||||
|
Channels={channels} |
||||
|
Preferences={preferences} |
||||
|
Language={language} |
||||
|
SetLanguage={setLanguage} |
||||
|
Translations={translations} |
||||
|
/> |
||||
</div> |
</div> |
||||
); |
); |
||||
} |
}; |
||||
|
|
||||
export default App; |
export default App; |
||||
|
|||||
@ -0,0 +1,113 @@ |
|||||
|
import React, { useState } from 'react'; |
||||
|
|
||||
|
import { FaBars, FaPaperPlane } from 'react-icons/fa'; |
||||
|
|
||||
|
import type { |
||||
|
IHTTPConnection, |
||||
|
Protobuf, |
||||
|
Types, |
||||
|
} from '@meshtastic/meshtasticjs'; |
||||
|
|
||||
|
import type { LanguageEnum, languageTemplate } from './App'; |
||||
|
import ChatMessage from './components/ChatMessage'; |
||||
|
import Sidebar from './components/Sidebar'; |
||||
|
|
||||
|
interface MainProps { |
||||
|
Messages: { message: Types.TextPacket; ack: boolean }[]; |
||||
|
Connection: IHTTPConnection; |
||||
|
MyNodeInfo: Protobuf.MyNodeInfo; |
||||
|
Nodes: Types.NodeInfoPacket[]; |
||||
|
Channels: Protobuf.Channel[]; |
||||
|
IsReady: boolean; |
||||
|
Preferences: Protobuf.RadioConfig_UserPreferences; |
||||
|
Language: LanguageEnum; |
||||
|
SetLanguage: Function; |
||||
|
Translations: languageTemplate; |
||||
|
} |
||||
|
|
||||
|
const Main = (props: MainProps) => { |
||||
|
const [currentMessage, setCurrentMessage] = useState(''); |
||||
|
const [mobileNavOpen, setMobileNavOpen] = useState(true); |
||||
|
|
||||
|
const sendMessage = () => { |
||||
|
if (props.IsReady) { |
||||
|
props.Connection.sendText(currentMessage, undefined, true); |
||||
|
setCurrentMessage(''); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<div className="flex flex-col md:flex-row flex-grow space-2"> |
||||
|
<div className="flex flex-col flex-grow container mx-auto"> |
||||
|
<div className="flex flex-col flex-grow py-6 px-3 space-y-2"> |
||||
|
{props.Messages.length ? ( |
||||
|
props.Messages.map((message, Main) => ( |
||||
|
<ChatMessage |
||||
|
nodes={props.Nodes} |
||||
|
key={Main} |
||||
|
message={message} |
||||
|
myId={props.MyNodeInfo.myNodeNum} |
||||
|
/> |
||||
|
)) |
||||
|
) : ( |
||||
|
<div className="m-auto text-2xl text-gray-500"> |
||||
|
{props.Translations.no_messages_message} |
||||
|
</div> |
||||
|
)} |
||||
|
</div> |
||||
|
<div className="flex space-x-2 w-full p-3"> |
||||
|
<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="z-10 h-full text-gray-400 absolute w-8 right-0 py-4"> |
||||
|
<FaPaperPlane |
||||
|
onClick={sendMessage} |
||||
|
className={`text-xl hover:text-gray-500 ${ |
||||
|
props.IsReady ? 'cursor-pointer' : 'cursor-not-allowed' |
||||
|
}`}
|
||||
|
/> |
||||
|
</span> |
||||
|
</form> |
||||
|
<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={() => { |
||||
|
setMobileNavOpen(!mobileNavOpen); |
||||
|
}} |
||||
|
> |
||||
|
<FaBars className="m-auto" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<Sidebar |
||||
|
IsReady={props.IsReady} |
||||
|
Nodes={props.Nodes} |
||||
|
Channels={props.Channels} |
||||
|
Preferences={props.Preferences} |
||||
|
Connection={props.Connection} |
||||
|
MobileNavOpen={mobileNavOpen} |
||||
|
Language={props.Language} |
||||
|
SetLanguage={props.SetLanguage} |
||||
|
Translations={props.Translations} |
||||
|
/> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default Main; |
||||
@ -0,0 +1,64 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import { FaCheckCircle, FaCircle, FaUser } from 'react-icons/fa'; |
||||
|
|
||||
|
import type { Types } from '@meshtastic/meshtasticjs'; |
||||
|
|
||||
|
interface ChatMessageProps { |
||||
|
message: { message: Types.TextPacket; ack: boolean }; |
||||
|
myId: number; |
||||
|
nodes: Types.NodeInfoPacket[]; |
||||
|
} |
||||
|
|
||||
|
const ChatMessage = (props: ChatMessageProps) => { |
||||
|
return ( |
||||
|
<div className="flex items-end"> |
||||
|
<div |
||||
|
className={`flex p-3 rounded-full shadow-md ${ |
||||
|
props.message.message.packet.from !== props.myId |
||||
|
? 'bg-gray-300' |
||||
|
: 'bg-green-200' |
||||
|
}`}
|
||||
|
> |
||||
|
<FaUser className="m-auto" /> |
||||
|
</div> |
||||
|
<div className="flex flex-col container px-2 items-start"> |
||||
|
<div |
||||
|
className={`px-4 py-2 rounded-md shadow-md ${ |
||||
|
props.message.message.packet.from !== props.myId |
||||
|
? 'bg-gray-300' |
||||
|
: 'bg-green-200' |
||||
|
}`}
|
||||
|
> |
||||
|
<div className="flex text-xs text-gray-500 space-x-1"> |
||||
|
<div className="font-medium"> |
||||
|
{/* { |
||||
|
props.nodes.find( |
||||
|
(node) => node.data.num === props.message.message.packet.from, |
||||
|
).data.user.longName |
||||
|
} */} |
||||
|
</div> |
||||
|
<p>-</p> |
||||
|
<div className="underline"> |
||||
|
{new Date( |
||||
|
props.message.message.packet.rxTime > 0 |
||||
|
? props.message.message.packet.rxTime |
||||
|
: Date.now(), |
||||
|
).toLocaleString()} |
||||
|
</div> |
||||
|
</div> |
||||
|
<div className="flex justify-between text-gray-600"> |
||||
|
<span className="inline-block">{props.message.message.data}</span> |
||||
|
{props.message.ack ? ( |
||||
|
<FaCheckCircle className="my-auto" /> |
||||
|
) : ( |
||||
|
<FaCircle className=" text-lg my-auto animate-pulse" /> |
||||
|
)} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default ChatMessage; |
||||
@ -0,0 +1,56 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import { FaBroadcastTower, FaMobileAlt } from 'react-icons/fa'; |
||||
|
|
||||
|
import { Types } from '@meshtastic/meshtasticjs'; |
||||
|
|
||||
|
interface HeaderProps { |
||||
|
status: Types.DeviceStatusEnum; |
||||
|
IsReady: boolean; |
||||
|
LastMeshInterraction: number; |
||||
|
} |
||||
|
|
||||
|
const Header = (props: HeaderProps) => { |
||||
|
return ( |
||||
|
<nav className="w-full shadow-md"> |
||||
|
<div className="flex w-full container mx-auto justify-between px-6 py-4"> |
||||
|
<img src="/design/typelogo/typelogo.svg" height="30" width="200" /> |
||||
|
|
||||
|
<div className="flex items-center"> |
||||
|
<div className="flex pl-4"> |
||||
|
<div |
||||
|
className={`w-5 h-5 rounded-full ${ |
||||
|
new Date(props.LastMeshInterraction) < |
||||
|
new Date(Date.now() - 40000) |
||||
|
? 'bg-red-400' |
||||
|
: new Date(props.LastMeshInterraction) < |
||||
|
new Date(Date.now() - 20000) |
||||
|
? 'bg-yellow-400' |
||||
|
: 'bg-green-400' |
||||
|
}`}
|
||||
|
></div> |
||||
|
<FaBroadcastTower className="m-auto ml-1 text-xl" /> |
||||
|
</div> |
||||
|
|
||||
|
<div className="flex pl-4"> |
||||
|
<div |
||||
|
className={`w-5 h-5 rounded-full ${ |
||||
|
props.status <= Types.DeviceStatusEnum.DEVICE_DISCONNECTED |
||||
|
? 'bg-red-400' |
||||
|
: props.status <= Types.DeviceStatusEnum.DEVICE_CONFIGURING && |
||||
|
!props.IsReady |
||||
|
? 'bg-yellow-400' |
||||
|
: props.IsReady |
||||
|
? 'bg-green-400' |
||||
|
: 'bg-gray-400' |
||||
|
}`}
|
||||
|
></div> |
||||
|
<FaMobileAlt className="m-auto ml-1 text-xl" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</nav> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default Header; |
||||
@ -0,0 +1,66 @@ |
|||||
|
import React, { ReactNode, useEffect, useState } from 'react'; |
||||
|
|
||||
|
import { FaCaretDown, FaCaretRight, FaSpinner } from 'react-icons/fa'; |
||||
|
|
||||
|
interface NavItemProps { |
||||
|
isDropdown: boolean; |
||||
|
open?: boolean; |
||||
|
isNested: boolean; |
||||
|
titleContent: ReactNode; |
||||
|
dropdownContent?: ReactNode; |
||||
|
onClick?: Function; |
||||
|
isLoading?: boolean; |
||||
|
} |
||||
|
|
||||
|
const NavItem = (props: NavItemProps) => { |
||||
|
useEffect(() => { |
||||
|
if (props.open) { |
||||
|
setNavItemOpen(props.open); |
||||
|
} |
||||
|
}, []); |
||||
|
const [navItemOpen, setNavItemOpen] = 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 ? ( |
||||
|
<FaCaretDown className="my-auto group-hover:text-gray-700" /> |
||||
|
) : ( |
||||
|
<FaCaretRight className="my-auto group-hover:text-gray-700" /> |
||||
|
) |
||||
|
) : null} |
||||
|
{props.isLoading ? ( |
||||
|
<FaSpinner className="animate-spin my-auto" /> |
||||
|
) : 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,62 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import type { |
||||
|
IHTTPConnection, |
||||
|
Protobuf, |
||||
|
Types, |
||||
|
} from '@meshtastic/meshtasticjs'; |
||||
|
|
||||
|
import type { LanguageEnum, languageTemplate } from '../App'; |
||||
|
import SidebarChannels from './Sidebar/SidebarChannels'; |
||||
|
import SidebarDeviceSettings from './Sidebar/SidebarDeviceSettings'; |
||||
|
import SidebarNodes from './Sidebar/SidebarNodes'; |
||||
|
import SidebarUISettings from './Sidebar/SidebarUISettings'; |
||||
|
|
||||
|
interface SidebarProps { |
||||
|
IsReady: boolean; |
||||
|
Nodes: Types.NodeInfoPacket[]; |
||||
|
Channels: Protobuf.Channel[]; |
||||
|
Preferences: Protobuf.RadioConfig_UserPreferences; |
||||
|
Connection: IHTTPConnection; |
||||
|
MobileNavOpen: boolean; |
||||
|
Language: LanguageEnum; |
||||
|
SetLanguage: Function; |
||||
|
Translations: languageTemplate; |
||||
|
} |
||||
|
|
||||
|
const Sidebar = (props: SidebarProps) => { |
||||
|
const updatePreferences = () => {}; |
||||
|
|
||||
|
return ( |
||||
|
<div |
||||
|
className={`flex flex-col rounded-md m-3 md:ml-0 shadow-md w-full max-w-sm ${ |
||||
|
!props.MobileNavOpen ? 'hidden' : 'visible' |
||||
|
}`}
|
||||
|
> |
||||
|
<SidebarNodes |
||||
|
IsReady={props.IsReady} |
||||
|
Nodes={props.Nodes} |
||||
|
Translations={props.Translations} |
||||
|
/> |
||||
|
<SidebarDeviceSettings |
||||
|
IsReady={props.IsReady} |
||||
|
Preferences={props.Preferences} |
||||
|
Connection={props.Connection} |
||||
|
Translations={props.Translations} |
||||
|
/> |
||||
|
<SidebarChannels |
||||
|
IsReady={props.IsReady} |
||||
|
Channels={props.Channels} |
||||
|
Translations={props.Translations} |
||||
|
/> |
||||
|
<div className="flex-grow border-b"></div> |
||||
|
<SidebarUISettings |
||||
|
Language={props.Language} |
||||
|
SetLanguage={props.SetLanguage} |
||||
|
Translations={props.Translations} |
||||
|
/> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default Sidebar; |
||||
@ -0,0 +1,145 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import { FaStream } from 'react-icons/fa'; |
||||
|
|
||||
|
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"> |
||||
|
<FaStream className="my-auto mr-2" /> |
||||
|
{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} |
||||
|
titleContent={ |
||||
|
<div className="flex"> |
||||
|
{channel.index} - {Protobuf.Channel_Role[channel.role]} |
||||
|
</div> |
||||
|
} |
||||
|
dropdownContent={ |
||||
|
<NavItem |
||||
|
isDropdown={false} |
||||
|
isNested={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; |
||||
@ -0,0 +1,106 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import { FaSave, FaUserCog } from 'react-icons/fa'; |
||||
|
|
||||
|
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"> |
||||
|
<FaUserCog className="my-auto mr-2" /> |
||||
|
{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); |
||||
|
}} |
||||
|
> |
||||
|
<FaSave className="m-auto mr-2 group-hover:text-gray-700" /> |
||||
|
{props.Translations.save_changes_button} |
||||
|
</div> |
||||
|
</div> |
||||
|
</> |
||||
|
} |
||||
|
/> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default SidebarDeviceSettings; |
||||
@ -0,0 +1,88 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import { FaDesktop, FaUsers } from 'react-icons/fa'; |
||||
|
|
||||
|
import type { Types } from '@meshtastic/meshtasticjs'; |
||||
|
|
||||
|
import type { languageTemplate } from '../../App'; |
||||
|
import NavItem from '../NavItem'; |
||||
|
|
||||
|
interface sidebarNodesProps { |
||||
|
IsReady: boolean; |
||||
|
Nodes: Types.NodeInfoPacket[]; |
||||
|
Translations: languageTemplate; |
||||
|
} |
||||
|
|
||||
|
const SidebarNodes = (props: sidebarNodesProps) => { |
||||
|
return ( |
||||
|
<NavItem |
||||
|
isDropdown={true} |
||||
|
open={false} |
||||
|
isNested={false} |
||||
|
titleContent={ |
||||
|
<div className="flex"> |
||||
|
<FaUsers className="my-auto mr-2" /> |
||||
|
{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"> |
||||
|
<FaDesktop className="my-auto mr-2" /> |
||||
|
<div className="m-auto">{node.data.user?.longName}</div> |
||||
|
</div> |
||||
|
} |
||||
|
dropdownContent={ |
||||
|
<NavItem |
||||
|
isDropdown={false} |
||||
|
isNested={true} |
||||
|
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 ? node.data.lastHeard : 'Unknown'} |
||||
|
</p> |
||||
|
<p> |
||||
|
Loc:{' '} |
||||
|
{node.data?.position |
||||
|
? `alt: ${node.data?.position.altitude}, lat: ${node.data?.position.latitudeI}, lng: ${node.data?.position.longitudeI}, time: ${node.data?.position.time}, batt: ${node.data?.position.batteryLevel}` |
||||
|
: 'Unknown'} |
||||
|
</p> |
||||
|
</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; |
||||
@ -0,0 +1,110 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
// import Flags from 'country-flag-icons/react/3x2';
|
||||
|
import { FaCog, FaLaptop, FaMoon, FaSun } from 'react-icons/fa'; |
||||
|
|
||||
|
import type { languageTemplate } from '../../App'; |
||||
|
import { LanguageEnum } from '../../App'; |
||||
|
import ToggleSwitch from '../basic/ToggleSwitch'; |
||||
|
import NavItem from '../NavItem'; |
||||
|
|
||||
|
interface SidebarUISettingsProps { |
||||
|
Language: LanguageEnum; |
||||
|
SetLanguage: Function; |
||||
|
Translations: languageTemplate; |
||||
|
} |
||||
|
|
||||
|
const SidebarUISettings = (props: SidebarUISettingsProps) => { |
||||
|
return ( |
||||
|
<NavItem |
||||
|
isDropdown={true} |
||||
|
isNested={false} |
||||
|
titleContent={ |
||||
|
<div className="flex"> |
||||
|
<FaCog className="my-auto mr-2" /> |
||||
|
{props.Translations.ui_settings_title} |
||||
|
</div> |
||||
|
} |
||||
|
dropdownContent={ |
||||
|
<> |
||||
|
<NavItem |
||||
|
isDropdown={false} |
||||
|
isNested={true} |
||||
|
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} |
||||
|
{props.Language === LanguageEnum.ENGLISH ? ( |
||||
|
<Flags.US className="ml-2 w-8 shadow-md" /> |
||||
|
) : props.Language === LanguageEnum.JAPANESE ? ( |
||||
|
<Flags.JP className="ml-2 w-8 shadow-md" /> |
||||
|
) : ( |
||||
|
'' |
||||
|
)} */} |
||||
|
</div> |
||||
|
} |
||||
|
dropdownContent={ |
||||
|
<> |
||||
|
<NavItem |
||||
|
onClick={() => { |
||||
|
props.SetLanguage(LanguageEnum.ENGLISH); |
||||
|
}} |
||||
|
isDropdown={false} |
||||
|
isNested={true} |
||||
|
titleContent={ |
||||
|
<>{/* English <Flags.US className="w-8 shadow-md" /> */}</> |
||||
|
} |
||||
|
/> |
||||
|
<NavItem |
||||
|
onClick={() => { |
||||
|
props.SetLanguage(LanguageEnum.JAPANESE); |
||||
|
}} |
||||
|
isDropdown={false} |
||||
|
isNested={true} |
||||
|
titleContent={ |
||||
|
<>{/* 日本語 <Flags.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,9 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
interface TimelineProps {} |
||||
|
|
||||
|
const Timeline = (props: TimelineProps) => { |
||||
|
return <div></div>; |
||||
|
}; |
||||
|
|
||||
|
export default Timeline; |
||||
@ -0,0 +1,12 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
interface TimelineItemProps { |
||||
|
time: number; |
||||
|
color: string; |
||||
|
} |
||||
|
|
||||
|
const TimelineItem = (props: TimelineItemProps) => { |
||||
|
return <div className={`rounded-full h-6 w-6 bg-${props.color}`}>Test</div>; |
||||
|
}; |
||||
|
|
||||
|
export default TimelineItem; |
||||
@ -0,0 +1,33 @@ |
|||||
|
import React, { useEffect, useState } from 'react'; |
||||
|
|
||||
|
interface ToggleSwitchProps { |
||||
|
active: boolean; |
||||
|
toggle?: Function; |
||||
|
} |
||||
|
|
||||
|
const ToggleSwitch = (props: ToggleSwitchProps) => { |
||||
|
const [active, setActive] = useState(false); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
setActive(props.active); |
||||
|
}, []); |
||||
|
|
||||
|
return ( |
||||
|
<div |
||||
|
onClick={() => { |
||||
|
setActive(!active); |
||||
|
}} |
||||
|
className={`w-12 h-6 flex items-center bg-gray-300 rounded-full p-1 duration-300 ease-in-out my-auto ${ |
||||
|
active ? 'bg-green-400' : null |
||||
|
}`}
|
||||
|
> |
||||
|
<div |
||||
|
className={`bg-white w-4 h-4 rounded-full shadow-md transform duration-300 ease-in-out ${ |
||||
|
active ? 'translate-x-6' : null |
||||
|
}`}
|
||||
|
></div> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default ToggleSwitch; |
||||
@ -1,13 +1,3 @@ |
|||||
body { |
@tailwind base; |
||||
margin: 0; |
@tailwind components; |
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", |
@tailwind utilities; |
||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", |
|
||||
sans-serif; |
|
||||
-webkit-font-smoothing: antialiased; |
|
||||
-moz-osx-font-smoothing: grayscale; |
|
||||
} |
|
||||
|
|
||||
code { |
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", |
|
||||
monospace; |
|
||||
} |
|
||||
|
|||||
|
Before Width: | Height: | Size: 2.3 KiB |
@ -0,0 +1,17 @@ |
|||||
|
import type { languageTemplate } from '../App'; |
||||
|
|
||||
|
export default { |
||||
|
no_messages_message: 'No messages yet', |
||||
|
ui_settings_title: 'UI Settings', |
||||
|
nodes_title: 'Nodes', |
||||
|
device_settings_title: 'Device Settings', |
||||
|
device_channels_title: 'Channels', |
||||
|
color_scheme_title: 'Color scheme', |
||||
|
language_title: 'Language', |
||||
|
device_region_title: 'Device Region', |
||||
|
device_wifi_ssid: 'WiFi SSID', |
||||
|
device_wifi_psk: 'WiFi PSK', |
||||
|
save_changes_button: 'Save changes', |
||||
|
no_nodes_message: 'No nodes found', |
||||
|
no_message_placeholder: 'Enter Message', |
||||
|
} as languageTemplate; |
||||
@ -0,0 +1,16 @@ |
|||||
|
import type { languageTemplate } from '../App'; |
||||
|
|
||||
|
export default { |
||||
|
no_messages_message: 'まだメッセージはありません', |
||||
|
ui_settings_title: 'UI設定', |
||||
|
nodes_title: 'ノード', |
||||
|
device_settings_title: 'デバイスの設定', |
||||
|
color_scheme_title: 'カラースキーム', |
||||
|
language_title: '言語', |
||||
|
device_region_title: 'デバイスリージョン', |
||||
|
device_wifi_ssid: 'WiFi名', |
||||
|
device_wifi_psk: 'WiFiパスワード', |
||||
|
save_changes_button: '変更内容を保存', |
||||
|
no_nodes_message: 'ノードが見つかりません', |
||||
|
no_message_placeholder: 'メッセージを入力してください', |
||||
|
} as languageTemplate; |
||||
@ -0,0 +1,11 @@ |
|||||
|
module.exports = { |
||||
|
purge: ['./src/**/*.{js,ts,jsx,tsx}'], |
||||
|
darkMode: false, // or 'media' or 'class'
|
||||
|
theme: { |
||||
|
extend: {}, |
||||
|
}, |
||||
|
variants: { |
||||
|
extend: {}, |
||||
|
}, |
||||
|
plugins: [], |
||||
|
}; |
||||
@ -0,0 +1,56 @@ |
|||||
|
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 |
||||
File diff suppressed because it is too large
Loading…
Reference in new issue