This commit is contained in:
Sacha Weatherstone
2021-08-07 22:06:26 +10:00
parent fa5d816d1b
commit 25e3caab0d
8 changed files with 139 additions and 21 deletions

View File

@@ -34,20 +34,25 @@ const App = (): JSX.Element => {
const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo);
const darkMode = useAppSelector((state) => state.app.darkMode);
const hostOverrideEnabled = useAppSelector(
(state) => state.meshtastic.hostOverrideEnabled,
);
const hostOverride = useAppSelector((state) => state.meshtastic.hostOverride);
React.useEffect(() => {
SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE;
connection.connect({
address:
import.meta.env.NODE_ENV === 'production'
? window.location.hostname
: import.meta.env.SNOWPACK_PUBLIC_DEVICE_IP,
address: hostOverrideEnabled
? hostOverride
: import.meta.env.NODE_ENV === 'production'
? window.location.hostname
: import.meta.env.SNOWPACK_PUBLIC_DEVICE_IP,
receiveBatchRequests: false,
tls: false,
fetchInterval: 2000,
});
}, []);
}, [hostOverrideEnabled, hostOverride]);
React.useEffect(() => {
connection.onDeviceStatus.subscribe((status) => {
@@ -56,6 +61,9 @@ const App = (): JSX.Element => {
if (status === Types.DeviceStatusEnum.DEVICE_CONFIGURED) {
dispatch(setReady(true));
}
if (status === Types.DeviceStatusEnum.DEVICE_DISCONNECTED) {
dispatch(setReady(false));
}
});
connection.onMyNodeInfo.subscribe((nodeInfo) => {
@@ -88,8 +96,6 @@ const App = (): JSX.Element => {
);
connection.onTextPacket.subscribe((message) => {
console.log(message.packet.from, '===', myNodeInfo.myNodeNum);
dispatch(
addMessage({
message: message,

View File

@@ -7,6 +7,7 @@ export interface MessageProps {
ack: boolean;
isSender: boolean;
rxTime: Date;
senderName: string;
}
export const Message = ({
@@ -14,6 +15,7 @@ export const Message = ({
ack,
isSender,
rxTime,
senderName,
}: MessageProps): JSX.Element => {
return (
<div
@@ -27,16 +29,19 @@ export const Message = ({
colors={['#213435', '#46685B', '#648A64', '#A6B985', '#E1E3AC']}
/>
</div>
<div
className={`relative max-w-3/4 px-3 py-2 rounded-t-lg mb-2 ${
isSender
? 'bg-gray-500 text-gray-50 rounded-br-lg'
: 'bg-primary text-blue-50 rounded-bl-lg'
} ${ack ? 'animate-none' : 'animate-pulse'}`}
>
<div className="min-w-4 leading-5">{message}</div>
<div>
<div
className={`relative max-w-3/4 px-3 py-2 rounded-t-lg ${
isSender
? 'bg-gray-500 text-gray-50 rounded-br-lg'
: 'bg-primary text-blue-50 rounded-bl-lg'
} ${ack ? 'animate-none' : 'animate-pulse'}`}
>
<div className="min-w-4 leading-5">{message}</div>
</div>
<div className="text-xs text-gray-600">{senderName}</div>
</div>
<div className="mt-auto mb-2 text-xs font-medium text-secondary mr-3 dark:text-gray-200">
<div className="mt-auto mb-4 text-xs font-medium text-secondary mr-3 dark:text-gray-200">
{rxTime.getHours()}:{rxTime.getMinutes()}
</div>
</div>

View File

@@ -8,6 +8,8 @@ export interface InputProps {
type: string;
name: string;
value?: string;
disabled?: boolean;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
export const Input = ({
@@ -18,6 +20,8 @@ export const Input = ({
type,
name,
value,
disabled,
onChange,
}: InputProps): JSX.Element => {
return (
<div className="space-y-1">
@@ -40,10 +44,12 @@ export const Input = ({
name={name}
id={name}
value={value}
onChange={onChange}
className={`block w-full h-11 rounded-md border shadow-sm focus:outline-none focus:border-primary dark:focus:border-primary dark:bg-secondaryDark dark:border-gray-600 dark:text-white ${
icon ? 'pl-9' : 'pl-2'
}`}
placeholder={placeholder}
disabled={disabled}
/>
</div>
{!valid && (

View File

@@ -0,0 +1,26 @@
import React from 'react';
import { Switch } from '@headlessui/react';
export interface ToggleProps {
enabled: boolean;
setEnabled: (state: boolean) => void;
}
export const Toggle = ({ enabled, setEnabled }: ToggleProps): JSX.Element => {
return (
<Switch
checked={enabled}
onChange={setEnabled}
className={`${enabled ? 'bg-primary' : 'bg-gray-300 dark:bg-gray-700'}
relative inline-flex flex-shrink-0 h-[38px] w-[74px] border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75`}
>
<span className="sr-only">Use setting</span>
<span
aria-hidden="true"
className={`${enabled ? 'translate-x-9' : 'translate-x-0'}
pointer-events-none inline-block h-[34px] w-[34px] rounded-full bg-white shadow-lg transform ring-0 transition ease-in-out duration-200`}
/>
</Switch>
);
};

View File

@@ -6,6 +6,7 @@ import { useAppSelector } from '../hooks/redux';
export const Messages = (): JSX.Element => {
const messages = useAppSelector((state) => state.meshtastic.messages);
const nodes = useAppSelector((state) => state.meshtastic.nodes);
return (
<div className="flex flex-col w-full">
@@ -17,6 +18,10 @@ export const Messages = (): JSX.Element => {
message={message.message.data}
ack={message.ack}
rxTime={new Date()}
senderName={
nodes.find((node) => node.num === message.message.packet.from)
?.user?.longName ?? 'UNK'
}
/>
))}
</div>

View File

@@ -3,18 +3,32 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { Input } from '../components/form/Input';
import { Toggle } from '../components/form/Toggle';
import { PrimaryTemplate } from '../components/templates/PrimaryTemplate';
import { useAppSelector } from '../hooks/redux';
import { useAppDispatch, useAppSelector } from '../hooks/redux';
import {
setHostOverride,
setHostOverrideEnabled,
} from '../slices/meshtasticSlice';
export const Settings = (): JSX.Element => {
const { t } = useTranslation();
const radioConfig = useAppSelector((state) => state.meshtastic.preferences);
const dispatch = useAppDispatch();
console.log(radioConfig);
const radioConfig = useAppSelector((state) => state.meshtastic.preferences);
const hostOverride = useAppSelector((state) => state.meshtastic.hostOverride);
const hostOverrideEnabled = useAppSelector(
(state) => state.meshtastic.hostOverrideEnabled,
);
const [localHostOverride, setLocalHostOverride] =
React.useState(hostOverride);
const [localHostOverrideEnabled, setLocalHostOverrideEnabled] =
React.useState(hostOverrideEnabled);
return (
<PrimaryTemplate title="Settings" tagline="Device">
<div className="flex">
<div className="flex mb-8 dark:text-white">
<div className="w-1/3 text-lg">WiFi</div>
<div className="space-y-2">
<Input
@@ -31,6 +45,38 @@ export const Settings = (): JSX.Element => {
/>
</div>
</div>
<div className="flex dark:text-white">
<div className="w-1/3 text-lg">Client</div>
<div className="space-y-2">
<Toggle
enabled={localHostOverrideEnabled}
setEnabled={(state) => {
setLocalHostOverrideEnabled(state);
}}
/>
<Input
name={'Host override'}
placeholder={'meshtastic.local'}
value={localHostOverride}
onChange={(event) => {
setLocalHostOverride(event.target.value);
}}
type="text"
valid={true}
disabled={!localHostOverrideEnabled}
/>
</div>
</div>
<button
onClick={() => {
dispatch(setHostOverride(localHostOverride));
dispatch(setHostOverrideEnabled(localHostOverrideEnabled));
}}
className="w-full rounded-md dark:bg-primaryDark shadow-md border dark:border-gray-600 p-2 mt-6 dark:text-white hover:bg-gray-200 dark:hover:bg-gray-900"
>
{t('strings.save_changes')}
</button>
</PrimaryTemplate>
);
};

View File

@@ -11,7 +11,7 @@ interface AppState {
const initialState: AppState = {
mobileNavOpen: false,
darkMode: false,
darkMode: localStorage.getItem('darkMode') === 'true' ?? false,
currentPage: 'messages',
};
@@ -26,6 +26,7 @@ export const appSlice = createSlice({
state.mobileNavOpen = false;
},
setDarkModeEnabled(state, action: PayloadAction<boolean>) {
localStorage.setItem('darkMode', String(action.payload));
state.darkMode = action.payload;
},
setCurrentPage(state, action: PayloadAction<currentPageName>) {

View File

@@ -2,6 +2,8 @@ import { Protobuf, Types } from '@meshtastic/meshtasticjs';
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import { connection } from '../connection';
export interface MessageWithAck {
message: Types.TextPacket;
ack: boolean;
@@ -19,6 +21,8 @@ interface AppState {
channels: Protobuf.Channel[];
preferences: Protobuf.RadioConfig_UserPreferences;
messages: MessageWithAck[];
hostOverrideEnabled: boolean;
hostOverride: string;
}
const initialState: AppState = {
@@ -31,6 +35,9 @@ const initialState: AppState = {
channels: [],
preferences: Protobuf.RadioConfig_UserPreferences.create(),
messages: [],
hostOverrideEnabled:
localStorage.getItem('hostOverrideEnabled') === 'true' ?? false,
hostOverride: localStorage.getItem('hostOverride') ?? '',
};
export const meshtasticSlice = createSlice({
@@ -95,6 +102,20 @@ export const meshtasticSlice = createSlice({
}
});
},
setHostOverrideEnabled: (state, action: PayloadAction<boolean>) => {
state.hostOverrideEnabled = action.payload;
localStorage.setItem('hostOverrideEnabled', String(action.payload));
if (state.hostOverrideEnabled !== action.payload) {
connection.disconnect();
}
},
setHostOverride: (state, action: PayloadAction<string>) => {
state.hostOverride = action.payload;
localStorage.setItem('hostOverride', action.payload);
if (state.hostOverride !== action.payload) {
connection.disconnect();
}
},
},
});
@@ -109,6 +130,8 @@ export const {
setPreferences,
addMessage,
ackMessage,
setHostOverrideEnabled,
setHostOverride,
} = meshtasticSlice.actions;
export default meshtasticSlice.reducer;