mirror of
https://github.com/meshtastic/web.git
synced 2026-05-19 19:55:06 -04:00
WIP
This commit is contained in:
20
src/App.tsx
20
src/App.tsx
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
26
src/components/form/Toggle.tsx
Normal file
26
src/components/form/Toggle.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user