WIP
This commit is contained in:
+13
-7
@@ -34,20 +34,25 @@ const App = (): JSX.Element => {
|
|||||||
|
|
||||||
const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo);
|
const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo);
|
||||||
const darkMode = useAppSelector((state) => state.app.darkMode);
|
const darkMode = useAppSelector((state) => state.app.darkMode);
|
||||||
|
const hostOverrideEnabled = useAppSelector(
|
||||||
|
(state) => state.meshtastic.hostOverrideEnabled,
|
||||||
|
);
|
||||||
|
const hostOverride = useAppSelector((state) => state.meshtastic.hostOverride);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE;
|
SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE;
|
||||||
|
|
||||||
connection.connect({
|
connection.connect({
|
||||||
address:
|
address: hostOverrideEnabled
|
||||||
import.meta.env.NODE_ENV === 'production'
|
? hostOverride
|
||||||
? window.location.hostname
|
: import.meta.env.NODE_ENV === 'production'
|
||||||
: import.meta.env.SNOWPACK_PUBLIC_DEVICE_IP,
|
? window.location.hostname
|
||||||
|
: import.meta.env.SNOWPACK_PUBLIC_DEVICE_IP,
|
||||||
receiveBatchRequests: false,
|
receiveBatchRequests: false,
|
||||||
tls: false,
|
tls: false,
|
||||||
fetchInterval: 2000,
|
fetchInterval: 2000,
|
||||||
});
|
});
|
||||||
}, []);
|
}, [hostOverrideEnabled, hostOverride]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
connection.onDeviceStatus.subscribe((status) => {
|
connection.onDeviceStatus.subscribe((status) => {
|
||||||
@@ -56,6 +61,9 @@ const App = (): JSX.Element => {
|
|||||||
if (status === Types.DeviceStatusEnum.DEVICE_CONFIGURED) {
|
if (status === Types.DeviceStatusEnum.DEVICE_CONFIGURED) {
|
||||||
dispatch(setReady(true));
|
dispatch(setReady(true));
|
||||||
}
|
}
|
||||||
|
if (status === Types.DeviceStatusEnum.DEVICE_DISCONNECTED) {
|
||||||
|
dispatch(setReady(false));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.onMyNodeInfo.subscribe((nodeInfo) => {
|
connection.onMyNodeInfo.subscribe((nodeInfo) => {
|
||||||
@@ -88,8 +96,6 @@ const App = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
connection.onTextPacket.subscribe((message) => {
|
connection.onTextPacket.subscribe((message) => {
|
||||||
console.log(message.packet.from, '===', myNodeInfo.myNodeNum);
|
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
addMessage({
|
addMessage({
|
||||||
message: message,
|
message: message,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface MessageProps {
|
|||||||
ack: boolean;
|
ack: boolean;
|
||||||
isSender: boolean;
|
isSender: boolean;
|
||||||
rxTime: Date;
|
rxTime: Date;
|
||||||
|
senderName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Message = ({
|
export const Message = ({
|
||||||
@@ -14,6 +15,7 @@ export const Message = ({
|
|||||||
ack,
|
ack,
|
||||||
isSender,
|
isSender,
|
||||||
rxTime,
|
rxTime,
|
||||||
|
senderName,
|
||||||
}: MessageProps): JSX.Element => {
|
}: MessageProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -27,16 +29,19 @@ export const Message = ({
|
|||||||
colors={['#213435', '#46685B', '#648A64', '#A6B985', '#E1E3AC']}
|
colors={['#213435', '#46685B', '#648A64', '#A6B985', '#E1E3AC']}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div>
|
||||||
className={`relative max-w-3/4 px-3 py-2 rounded-t-lg mb-2 ${
|
<div
|
||||||
isSender
|
className={`relative max-w-3/4 px-3 py-2 rounded-t-lg ${
|
||||||
? 'bg-gray-500 text-gray-50 rounded-br-lg'
|
isSender
|
||||||
: 'bg-primary text-blue-50 rounded-bl-lg'
|
? 'bg-gray-500 text-gray-50 rounded-br-lg'
|
||||||
} ${ack ? 'animate-none' : 'animate-pulse'}`}
|
: 'bg-primary text-blue-50 rounded-bl-lg'
|
||||||
>
|
} ${ack ? 'animate-none' : 'animate-pulse'}`}
|
||||||
<div className="min-w-4 leading-5">{message}</div>
|
>
|
||||||
|
<div className="min-w-4 leading-5">{message}</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-600">{senderName}</div>
|
||||||
</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()}
|
{rxTime.getHours()}:{rxTime.getMinutes()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export interface InputProps {
|
|||||||
type: string;
|
type: string;
|
||||||
name: string;
|
name: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Input = ({
|
export const Input = ({
|
||||||
@@ -18,6 +20,8 @@ export const Input = ({
|
|||||||
type,
|
type,
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
|
disabled,
|
||||||
|
onChange,
|
||||||
}: InputProps): JSX.Element => {
|
}: InputProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -40,10 +44,12 @@ export const Input = ({
|
|||||||
name={name}
|
name={name}
|
||||||
id={name}
|
id={name}
|
||||||
value={value}
|
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 ${
|
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'
|
icon ? 'pl-9' : 'pl-2'
|
||||||
}`}
|
}`}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!valid && (
|
{!valid && (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -6,6 +6,7 @@ import { useAppSelector } from '../hooks/redux';
|
|||||||
|
|
||||||
export const Messages = (): JSX.Element => {
|
export const Messages = (): JSX.Element => {
|
||||||
const messages = useAppSelector((state) => state.meshtastic.messages);
|
const messages = useAppSelector((state) => state.meshtastic.messages);
|
||||||
|
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
@@ -17,6 +18,10 @@ export const Messages = (): JSX.Element => {
|
|||||||
message={message.message.data}
|
message={message.message.data}
|
||||||
ack={message.ack}
|
ack={message.ack}
|
||||||
rxTime={new Date()}
|
rxTime={new Date()}
|
||||||
|
senderName={
|
||||||
|
nodes.find((node) => node.num === message.message.packet.from)
|
||||||
|
?.user?.longName ?? 'UNK'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+50
-4
@@ -3,18 +3,32 @@ import React from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Input } from '../components/form/Input';
|
import { Input } from '../components/form/Input';
|
||||||
|
import { Toggle } from '../components/form/Toggle';
|
||||||
import { PrimaryTemplate } from '../components/templates/PrimaryTemplate';
|
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 => {
|
export const Settings = (): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
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 (
|
return (
|
||||||
<PrimaryTemplate title="Settings" tagline="Device">
|
<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="w-1/3 text-lg">WiFi</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Input
|
<Input
|
||||||
@@ -31,6 +45,38 @@ export const Settings = (): JSX.Element => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</PrimaryTemplate>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface AppState {
|
|||||||
|
|
||||||
const initialState: AppState = {
|
const initialState: AppState = {
|
||||||
mobileNavOpen: false,
|
mobileNavOpen: false,
|
||||||
darkMode: false,
|
darkMode: localStorage.getItem('darkMode') === 'true' ?? false,
|
||||||
currentPage: 'messages',
|
currentPage: 'messages',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -26,6 +26,7 @@ export const appSlice = createSlice({
|
|||||||
state.mobileNavOpen = false;
|
state.mobileNavOpen = false;
|
||||||
},
|
},
|
||||||
setDarkModeEnabled(state, action: PayloadAction<boolean>) {
|
setDarkModeEnabled(state, action: PayloadAction<boolean>) {
|
||||||
|
localStorage.setItem('darkMode', String(action.payload));
|
||||||
state.darkMode = action.payload;
|
state.darkMode = action.payload;
|
||||||
},
|
},
|
||||||
setCurrentPage(state, action: PayloadAction<currentPageName>) {
|
setCurrentPage(state, action: PayloadAction<currentPageName>) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { Protobuf, Types } from '@meshtastic/meshtasticjs';
|
|||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import { connection } from '../connection';
|
||||||
|
|
||||||
export interface MessageWithAck {
|
export interface MessageWithAck {
|
||||||
message: Types.TextPacket;
|
message: Types.TextPacket;
|
||||||
ack: boolean;
|
ack: boolean;
|
||||||
@@ -19,6 +21,8 @@ interface AppState {
|
|||||||
channels: Protobuf.Channel[];
|
channels: Protobuf.Channel[];
|
||||||
preferences: Protobuf.RadioConfig_UserPreferences;
|
preferences: Protobuf.RadioConfig_UserPreferences;
|
||||||
messages: MessageWithAck[];
|
messages: MessageWithAck[];
|
||||||
|
hostOverrideEnabled: boolean;
|
||||||
|
hostOverride: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: AppState = {
|
const initialState: AppState = {
|
||||||
@@ -31,6 +35,9 @@ const initialState: AppState = {
|
|||||||
channels: [],
|
channels: [],
|
||||||
preferences: Protobuf.RadioConfig_UserPreferences.create(),
|
preferences: Protobuf.RadioConfig_UserPreferences.create(),
|
||||||
messages: [],
|
messages: [],
|
||||||
|
hostOverrideEnabled:
|
||||||
|
localStorage.getItem('hostOverrideEnabled') === 'true' ?? false,
|
||||||
|
hostOverride: localStorage.getItem('hostOverride') ?? '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const meshtasticSlice = createSlice({
|
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,
|
setPreferences,
|
||||||
addMessage,
|
addMessage,
|
||||||
ackMessage,
|
ackMessage,
|
||||||
|
setHostOverrideEnabled,
|
||||||
|
setHostOverride,
|
||||||
} = meshtasticSlice.actions;
|
} = meshtasticSlice.actions;
|
||||||
|
|
||||||
export default meshtasticSlice.reducer;
|
export default meshtasticSlice.reducer;
|
||||||
|
|||||||
Reference in New Issue
Block a user