WIP
This commit is contained in:
@@ -11,7 +11,6 @@ import {
|
||||
addNode,
|
||||
setDeviceStatus,
|
||||
setLastMeshInterraction,
|
||||
setMyId,
|
||||
setMyNodeInfo,
|
||||
setPreferences,
|
||||
setReady,
|
||||
@@ -45,7 +44,6 @@ const App = (): JSX.Element => {
|
||||
|
||||
connection.onMyNodeInfo.subscribe((nodeInfo) => {
|
||||
dispatch(setMyNodeInfo(nodeInfo));
|
||||
dispatch(setMyId(nodeInfo.myNodeNum));
|
||||
});
|
||||
|
||||
connection.onNodeInfoPacket.subscribe((nodeInfoPacket) =>
|
||||
|
||||
@@ -15,11 +15,11 @@ interface ChatMessageProps {
|
||||
}
|
||||
|
||||
export const ChatMessage = (props: ChatMessageProps): JSX.Element => {
|
||||
const myId = useAppSelector((state) => state.meshtastic.myId);
|
||||
const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo);
|
||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||
|
||||
const node = nodes.find((node) => {
|
||||
node.num === props.message.message.packet.from;
|
||||
return node.num === props.message.message.packet.from;
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -39,8 +39,8 @@ export const ChatMessage = (props: ChatMessageProps): JSX.Element => {
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={`px-4 py-2 rounded-md shadow-md ${
|
||||
props.message.message.packet.from !== myId
|
||||
className={`px-4 py-2 rounded-3xl shadow-md ${
|
||||
props.message.message.packet.from !== myNodeInfo.myNodeNum
|
||||
? 'bg-gray-300'
|
||||
: 'bg-green-200'
|
||||
}`}
|
||||
@@ -58,11 +58,12 @@ export const ChatMessage = (props: ChatMessageProps): JSX.Element => {
|
||||
</div>
|
||||
<div className="flex justify-between text-gray-600">
|
||||
<span className="inline-block">{props.message.message.data}</span>
|
||||
{props.message.ack ? (
|
||||
<CheckCircleIcon className="my-auto w-5 h-5" />
|
||||
) : (
|
||||
<DotsCircleHorizontalIcon className="my-auto animate-pulse w-5 h-5" />
|
||||
)}
|
||||
{node?.num === myNodeInfo.myNodeNum &&
|
||||
(props.message.ack ? (
|
||||
<CheckCircleIcon className="my-auto w-5 h-5" />
|
||||
) : (
|
||||
<DotsCircleHorizontalIcon className="my-auto animate-pulse w-5 h-5" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</React.Suspense>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { MenuIcon, PaperAirplaneIcon } from '@heroicons/react/outline';
|
||||
@@ -22,14 +23,18 @@ export const MessageBox = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<div className="flex text-lg font-medium 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 border shadow-md focus:outline-none cursor-pointer md:hidden"
|
||||
<motion.button
|
||||
initial={{}}
|
||||
whileHover={{
|
||||
backgroundColor: 'rgba(229, 231, 235)',
|
||||
}}
|
||||
className="flex h-14 w-14 text-xl hover:text-gray-500 text-gray-400 rounded-full border shadow-md focus:outline-none cursor-pointer md:hidden"
|
||||
onClick={() => {
|
||||
dispatch(toggleSidebar());
|
||||
}}
|
||||
>
|
||||
<MenuIcon className="m-auto h-6 2-6" />
|
||||
</div>
|
||||
<MenuIcon className="m-auto h-6 w-6" />
|
||||
</motion.button>
|
||||
<form
|
||||
className="flex flex-wrap relative w-full"
|
||||
onSubmit={(e) => {
|
||||
@@ -46,11 +51,11 @@ export const MessageBox = (): JSX.Element => {
|
||||
onChange={(e) => {
|
||||
setCurrentMessage(e.target.value);
|
||||
}}
|
||||
className={`p-3 placeholder-gray-400 text-gray-700 relative rounded-md border shadow-md focus:outline-none w-full pr-10 ${
|
||||
className={`p-3 placeholder-gray-400 text-gray-700 relative rounded-3xl border shadow-md focus:outline-none w-full pr-10 ${
|
||||
ready ? 'cursor-text' : 'cursor-not-allowed'
|
||||
}`}
|
||||
/>
|
||||
<span className="flex z-10 h-full text-gray-400 absolute w-8 right-0">
|
||||
<span className="flex z-10 h-full text-gray-400 absolute w-8 right-1">
|
||||
<PaperAirplaneIcon
|
||||
onClick={sendMessage}
|
||||
className={`text-xl hover:text-gray-500 h-6 w-6 my-auto ${
|
||||
|
||||
@@ -15,9 +15,16 @@ export const Sidebar = (): JSX.Element => {
|
||||
<AnimatePresence>
|
||||
{sidebarOpen && (
|
||||
<motion.div
|
||||
className={`${
|
||||
sidebarOpen ? 'flex' : 'hidden md:flex'
|
||||
} flex-col rounded-md md:ml-0 shadow-md border w-full max-w-sm`}
|
||||
initial={{
|
||||
height: 0,
|
||||
}}
|
||||
animate={{
|
||||
height: 'auto',
|
||||
}}
|
||||
exit={{
|
||||
height: 0,
|
||||
}}
|
||||
className="flex flex-col rounded-3xl md:ml-0 shadow-md border w-full md:max-w-sm"
|
||||
>
|
||||
<Nodes />
|
||||
<Device />
|
||||
|
||||
@@ -23,7 +23,7 @@ export const Settings = (): JSX.Element => {
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className="flex bg-gray-50 whitespace-nowrap p-3 justify-between border-b">
|
||||
<div className="my-auto">{t('strings.device_region')}</div>
|
||||
<div className="flex shadow-md rounded-md ml-2">
|
||||
<div className="flex shadow-md rounded-3xl ml-2">
|
||||
<select
|
||||
{...register('region', {
|
||||
valueAsNumber: true,
|
||||
@@ -61,13 +61,13 @@ export const Settings = (): JSX.Element => {
|
||||
</div>
|
||||
<div className="flex bg-gray-50 whitespace-nowrap p-3 justify-between border-b">
|
||||
<div className="my-auto">{t('strings.wifi_ssid')}</div>
|
||||
<div className="flex shadow-md rounded-md ml-2">
|
||||
<div className="flex shadow-md rounded-3xl ml-2">
|
||||
<input {...register('wifiSsid', {})} type="text" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex bg-gray-50 whitespace-nowrap p-3 justify-between border-b">
|
||||
<div className="my-auto">{t('strings.wifi_psk')}</div>
|
||||
<div className="flex shadow-md rounded-md ml-2">
|
||||
<div className="flex shadow-md rounded-3xl ml-2">
|
||||
<input {...register('wifiPassword', {})} type="password" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ export const Nodes = (): JSX.Element => {
|
||||
return (
|
||||
<Dropdown
|
||||
icon={<UsersIcon className="my-auto text-gray-600 mr-2 w-5 h-5" />}
|
||||
title={t('placeholder.no_nodes')}
|
||||
title={t('strings.nodes')}
|
||||
content={<NodeList />}
|
||||
fallbackMessage={t('placeholder.no_messages')}
|
||||
/>
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface NodeProps {
|
||||
}
|
||||
|
||||
export const Node = (props: NodeProps): JSX.Element => {
|
||||
const myId = useAppSelector((state) => state.meshtastic.myId);
|
||||
const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo);
|
||||
|
||||
return (
|
||||
<Disclosure>
|
||||
@@ -34,7 +34,7 @@ export const Node = (props: NodeProps): JSX.Element => {
|
||||
<ChevronRightIcon className="my-auto w-5 h-5 mr-2" />
|
||||
)}
|
||||
<div className="relative">
|
||||
{props.node.num === myId ? (
|
||||
{props.node.num === myNodeInfo.myNodeNum ? (
|
||||
<FlagIcon className="absolute -right-1 -top-2 text-yellow-500 my-auto w-4 h-4" />
|
||||
) : null}
|
||||
<Avatar
|
||||
|
||||
@@ -17,7 +17,7 @@ export const Dropdown = (props: DropdownProps): JSX.Element => {
|
||||
<Disclosure>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button className="bg-white flex w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer">
|
||||
<Disclosure.Button className="flex w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer first:rounded-t-3xl last:rounded-b-3xl">
|
||||
<div className="flex">
|
||||
<motion.div
|
||||
className="my-auto mr-2"
|
||||
@@ -41,7 +41,6 @@ export const Dropdown = (props: DropdownProps): JSX.Element => {
|
||||
{open && (
|
||||
<Disclosure.Panel
|
||||
as={motion.div}
|
||||
static
|
||||
initial={{
|
||||
height: 0,
|
||||
}}
|
||||
|
||||
+2
-5
@@ -9,16 +9,13 @@ import { Provider } from 'react-redux';
|
||||
import App from './App';
|
||||
import { store } from './store';
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
if (!rootElement) throw new Error('Failed to find the root element');
|
||||
const root = ReactDOM.createRoot(rootElement);
|
||||
|
||||
root.render(
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
|
||||
// Hot Module Replacement (HMR) - Remove this snippet to remove HMR.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
import type { RootState } from '../store';
|
||||
|
||||
interface AppState {
|
||||
sidebarOpen: boolean;
|
||||
darkMode: boolean;
|
||||
@@ -13,7 +11,7 @@ const initialState: AppState = {
|
||||
};
|
||||
|
||||
export const appSlice = createSlice({
|
||||
name: 'auth',
|
||||
name: 'app',
|
||||
initialState,
|
||||
reducers: {
|
||||
openSidebar(state) {
|
||||
@@ -29,6 +27,5 @@ export const appSlice = createSlice({
|
||||
});
|
||||
|
||||
export const { openSidebar, closeSidebar, toggleSidebar } = appSlice.actions;
|
||||
export const selectOpenState = (state: RootState): boolean =>
|
||||
state.app.sidebarOpen;
|
||||
|
||||
export default appSlice.reducer;
|
||||
|
||||
@@ -4,7 +4,6 @@ import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
interface AppState {
|
||||
deviceStatus: Types.DeviceStatusEnum;
|
||||
myId: number;
|
||||
lastMeshInterraction: number;
|
||||
ready: boolean;
|
||||
fromRaioPackets: Protobuf.FromRadio[];
|
||||
@@ -23,7 +22,6 @@ interface AppState {
|
||||
|
||||
const initialState: AppState = {
|
||||
deviceStatus: Types.DeviceStatusEnum.DEVICE_DISCONNECTED,
|
||||
myId: 0,
|
||||
lastMeshInterraction: 0,
|
||||
ready: false,
|
||||
fromRaioPackets: [],
|
||||
@@ -47,9 +45,6 @@ export const meshtasticSlice = createSlice({
|
||||
setDeviceStatus: (state, action: PayloadAction<Types.DeviceStatusEnum>) => {
|
||||
state.deviceStatus = action.payload;
|
||||
},
|
||||
setMyId: (state, action: PayloadAction<number>) => {
|
||||
state.myId = action.payload;
|
||||
},
|
||||
setLastMeshInterraction: (state, action: PayloadAction<number>) => {
|
||||
state.lastMeshInterraction = action.payload;
|
||||
},
|
||||
@@ -119,7 +114,6 @@ export const meshtasticSlice = createSlice({
|
||||
|
||||
export const {
|
||||
setDeviceStatus,
|
||||
setMyId,
|
||||
setLastMeshInterraction,
|
||||
setReady,
|
||||
addFromRadioPacket,
|
||||
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
|
||||
import appSlice from './slices/appSlice';
|
||||
import meshtasticSlice from './slices/meshtasticSlice';
|
||||
import appReducer from './slices/appSlice';
|
||||
import meshtasticReducer from './slices/meshtasticSlice';
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
app: appSlice,
|
||||
meshtastic: meshtasticSlice,
|
||||
app: appReducer,
|
||||
meshtastic: meshtasticReducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware({
|
||||
|
||||
Reference in New Issue
Block a user