DM's and layout fixes
This commit is contained in:
+8
-8
@@ -18,14 +18,14 @@
|
|||||||
"@meshtastic/meshtasticjs": "^0.6.39",
|
"@meshtastic/meshtasticjs": "^0.6.39",
|
||||||
"@reduxjs/toolkit": "^1.7.2",
|
"@reduxjs/toolkit": "^1.7.2",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"framer-motion": "^6.2.4",
|
"framer-motion": "^6.2.6",
|
||||||
"graphql-request": "^4.0.0",
|
"graphql-request": "^4.0.0",
|
||||||
"mapbox-gl": "^2.7.0",
|
"mapbox-gl": "^2.7.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-error-boundary": "^3.1.4",
|
"react-error-boundary": "^3.1.4",
|
||||||
"react-flow-renderer": "^10.0.0-next.38",
|
"react-flow-renderer": "^10.0.0-next.39",
|
||||||
"react-hook-form": "^7.26.1",
|
"react-hook-form": "^7.27.0",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-json-pretty": "^2.2.0",
|
"react-json-pretty": "^2.2.0",
|
||||||
"react-multi-select-component": "^4.2.1",
|
"react-multi-select-component": "^4.2.1",
|
||||||
@@ -48,10 +48,10 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^5.11.0",
|
"@typescript-eslint/eslint-plugin": "^5.11.0",
|
||||||
"@typescript-eslint/parser": "^5.11.0",
|
"@typescript-eslint/parser": "^5.11.0",
|
||||||
"@verypossible/eslint-config": "^1.6.1",
|
"@verypossible/eslint-config": "^1.6.1",
|
||||||
"@vitejs/plugin-react": "^1.1.4",
|
"@vitejs/plugin-react": "^1.2.0",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
"babel-plugin-module-resolver": "^4.1.0",
|
"babel-plugin-module-resolver": "^4.1.0",
|
||||||
"eslint": "8.8.0",
|
"eslint": "8.9.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-import-resolver-alias": "^1.1.2",
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
"eslint-import-resolver-babel-module": "^5.3.1",
|
"eslint-import-resolver-babel-module": "^5.3.1",
|
||||||
@@ -62,11 +62,11 @@
|
|||||||
"gzipper": "^7.0.0",
|
"gzipper": "^7.0.0",
|
||||||
"postcss": "^8.4.6",
|
"postcss": "^8.4.6",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"prettier-plugin-tailwindcss": "^0.1.5",
|
"prettier-plugin-tailwindcss": "^0.1.7",
|
||||||
"tailwindcss": "^3.0.19",
|
"tailwindcss": "^3.0.22",
|
||||||
"tar": "^6.1.11",
|
"tar": "^6.1.11",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.5.5",
|
||||||
"vite": "^2.7.13",
|
"vite": "^2.8.1",
|
||||||
"vite-plugin-cdn-import": "^0.3.5",
|
"vite-plugin-cdn-import": "^0.3.5",
|
||||||
"vite-plugin-pwa": "^0.11.13",
|
"vite-plugin-pwa": "^0.11.13",
|
||||||
"workbox-window": "^6.4.2"
|
"workbox-window": "^6.4.2"
|
||||||
|
|||||||
Generated
+550
-513
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,8 @@ import { FiMessageCircle, FiSettings } from 'react-icons/fi';
|
|||||||
import { RiMindMap, RiRoadMapLine } from 'react-icons/ri';
|
import { RiMindMap, RiRoadMapLine } from 'react-icons/ri';
|
||||||
import { VscExtensions } from 'react-icons/vsc';
|
import { VscExtensions } from 'react-icons/vsc';
|
||||||
|
|
||||||
import { toggleMobileNav } from '@app/core/slices/appSlice.js';
|
import { toggleMobileNav } from '@app/core/slices/appSlice';
|
||||||
import { useAppDispatch } from '@app/hooks/useAppDispatch.js';
|
import { useAppDispatch } from '@app/hooks/useAppDispatch';
|
||||||
import { routes, useRoute } from '@core/router';
|
import { routes, useRoute } from '@core/router';
|
||||||
|
|
||||||
import { NavLinkButton } from './NavLinkButton';
|
import { NavLinkButton } from './NavLinkButton';
|
||||||
@@ -21,7 +21,7 @@ export const ButtonNav = ({
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="z-10 flex justify-between border-t border-gray-300 px-6 py-2 dark:border-gray-600 dark:bg-primaryDark">
|
<div className="z-30 flex justify-between border-t border-gray-300 px-6 py-2 dark:border-gray-600 dark:bg-primaryDark">
|
||||||
<div
|
<div
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
dispatch(toggleMobileNav());
|
dispatch(toggleMobileNav());
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
} from 'react-icons/ri';
|
} from 'react-icons/ri';
|
||||||
|
|
||||||
import { ListItem } from '@app/components/generic/ListItem';
|
import { ListItem } from '@app/components/generic/ListItem';
|
||||||
import type { ChannelData } from '@app/core/slices/meshtasticSlice';
|
|
||||||
import { connection } from '@core/connection';
|
import { connection } from '@core/connection';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { Checkbox, Input, Select, Tooltip } from '@meshtastic/components';
|
import { Checkbox, Input, Select, Tooltip } from '@meshtastic/components';
|
||||||
@@ -19,19 +18,19 @@ export const Channels = (): JSX.Element => {
|
|||||||
const channels = useAppSelector((state) => state.meshtastic.radio.channels);
|
const channels = useAppSelector((state) => state.meshtastic.radio.channels);
|
||||||
const adminChannel =
|
const adminChannel =
|
||||||
channels.find(
|
channels.find(
|
||||||
(channel) => channel.channel.role === Protobuf.Channel_Role.PRIMARY,
|
(channel) => channel.role === Protobuf.Channel_Role.PRIMARY,
|
||||||
) ?? channels[0];
|
) ?? channels[0];
|
||||||
const [usePreset, setUsePreset] = React.useState(true);
|
const [usePreset, setUsePreset] = React.useState(true);
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
const [selectedChannel, setSelectedChannel] = React.useState<
|
const [selectedChannel, setSelectedChannel] = React.useState<
|
||||||
ChannelData | undefined
|
Protobuf.Channel | undefined
|
||||||
>();
|
>();
|
||||||
|
|
||||||
const { register, handleSubmit, reset, formState } = useForm<
|
const { register, handleSubmit, reset, formState } = useForm<
|
||||||
DeepOmit<Protobuf.Channel, 'psk'>
|
DeepOmit<Protobuf.Channel, 'psk'>
|
||||||
>({
|
>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
...adminChannel.channel,
|
...adminChannel,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -42,7 +41,7 @@ export const Channels = (): JSX.Element => {
|
|||||||
...data,
|
...data,
|
||||||
settings: {
|
settings: {
|
||||||
...data.settings,
|
...data.settings,
|
||||||
psk: adminChannel.channel.settings?.psk,
|
psk: adminChannel.settings?.psk,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -111,7 +110,7 @@ export const Channels = (): JSX.Element => {
|
|||||||
)}
|
)}
|
||||||
{channels.map((channel) => (
|
{channels.map((channel) => (
|
||||||
<ListItem
|
<ListItem
|
||||||
key={channel.channel.index}
|
key={channel.index}
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
setSelectedChannel(channel);
|
setSelectedChannel(channel);
|
||||||
}}
|
}}
|
||||||
@@ -121,23 +120,23 @@ export const Channels = (): JSX.Element => {
|
|||||||
[
|
[
|
||||||
Protobuf.Channel_Role.SECONDARY,
|
Protobuf.Channel_Role.SECONDARY,
|
||||||
Protobuf.Channel_Role.PRIMARY,
|
Protobuf.Channel_Role.PRIMARY,
|
||||||
].find((role) => role === channel.channel.role)
|
].find((role) => role === channel.role)
|
||||||
? 'bg-green-500'
|
? 'bg-green-500'
|
||||||
: 'bg-gray-400'
|
: 'bg-gray-400'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
selected={selectedChannel?.channel.index === channel.channel.index}
|
selected={selectedChannel?.index === channel.index}
|
||||||
selectedIcon={<FiExternalLink />}
|
selectedIcon={<FiExternalLink />}
|
||||||
actions={
|
actions={
|
||||||
<Tooltip content={`MQTT Status`}>
|
<Tooltip content={`MQTT Status`}>
|
||||||
<div className="rounded-md p-2">
|
<div className="rounded-md p-2">
|
||||||
{channel.channel.settings?.uplinkEnabled &&
|
{channel.settings?.uplinkEnabled &&
|
||||||
channel.channel.settings?.downlinkEnabled ? (
|
channel.settings?.downlinkEnabled ? (
|
||||||
<RiArrowUpDownLine className="p-0.5 group-active:scale-90" />
|
<RiArrowUpDownLine className="p-0.5 group-active:scale-90" />
|
||||||
) : channel.channel.settings?.uplinkEnabled ? (
|
) : channel.settings?.uplinkEnabled ? (
|
||||||
<RiArrowUpLine className="p-0.5 group-active:scale-90" />
|
<RiArrowUpLine className="p-0.5 group-active:scale-90" />
|
||||||
) : channel.channel.settings?.downlinkEnabled ? (
|
) : channel.settings?.downlinkEnabled ? (
|
||||||
<RiArrowDownLine className="p-0.5 group-active:scale-90" />
|
<RiArrowDownLine className="p-0.5 group-active:scale-90" />
|
||||||
) : (
|
) : (
|
||||||
<FiX className="p-0.5" />
|
<FiX className="p-0.5" />
|
||||||
@@ -147,11 +146,11 @@ export const Channels = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{channel.channel.settings?.name.length
|
{channel.settings?.name.length
|
||||||
? channel.channel.settings.name
|
? channel.settings.name
|
||||||
: channel.channel.role === Protobuf.Channel_Role.PRIMARY
|
: channel.role === Protobuf.Channel_Role.PRIMARY
|
||||||
? 'Primary'
|
? 'Primary'
|
||||||
: `Channel: ${channel.channel.index}`}
|
: `Channel: ${channel.index}`}
|
||||||
</div>
|
</div>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -17,21 +17,21 @@ export const ChannelsGroup = (): JSX.Element => {
|
|||||||
<>
|
<>
|
||||||
{channels.map((channel) => {
|
{channels.map((channel) => {
|
||||||
return (
|
return (
|
||||||
<div key={channel.channel.index}>
|
<div key={channel.index}>
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
title={
|
title={
|
||||||
channel.channel.settings?.name.length
|
channel.settings?.name.length
|
||||||
? channel.channel.settings.name
|
? channel.settings.name
|
||||||
: channel.channel.role === Protobuf.Channel_Role.PRIMARY
|
: channel.role === Protobuf.Channel_Role.PRIMARY
|
||||||
? 'Primary'
|
? 'Primary'
|
||||||
: `Channel: ${channel.channel.index}`
|
: `Channel: ${channel.index}`
|
||||||
}
|
}
|
||||||
icon={
|
icon={
|
||||||
<div
|
<div
|
||||||
className={`h-3 w-3 rounded-full ${
|
className={`h-3 w-3 rounded-full ${
|
||||||
channel.channel.role === Protobuf.Channel_Role.PRIMARY
|
channel.role === Protobuf.Channel_Role.PRIMARY
|
||||||
? 'bg-orange-500'
|
? 'bg-orange-500'
|
||||||
: channel.channel.role === Protobuf.Channel_Role.SECONDARY
|
: channel.role === Protobuf.Channel_Role.SECONDARY
|
||||||
? 'bg-green-500'
|
? 'bg-green-500'
|
||||||
: 'bg-gray-500'
|
: 'bg-gray-500'
|
||||||
}`}
|
}`}
|
||||||
@@ -46,9 +46,9 @@ export const ChannelsGroup = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
{/* <DebugPanel channel={channel.channel} /> */}
|
{/* <DebugPanel channel={channel} /> */}
|
||||||
{/* <QRCodePanel channel={channel.channel} /> */}
|
{/* <QRCodePanel channel={channel} /> */}
|
||||||
<SettingsPanel channel={channel.channel} />
|
<SettingsPanel channel={channel} />
|
||||||
</>
|
</>
|
||||||
</CollapsibleSection>
|
</CollapsibleSection>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector.js';
|
import { useAppSelector } from '@app/hooks/useAppSelector';
|
||||||
|
|
||||||
import { ButtonNav } from './ButtonNav';
|
import { ButtonNav } from './ButtonNav';
|
||||||
import { Settings } from './Settings/Index';
|
import { Settings } from './Settings/Index';
|
||||||
@@ -15,9 +15,8 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => {
|
|||||||
const appState = useAppSelector((state) => state.app);
|
const appState = useAppSelector((state) => state.app);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-grow">
|
|
||||||
<div
|
<div
|
||||||
className={`absolute h-full w-full flex-grow flex-col pb-6 md:relative md:flex md:w-96 md:pb-0 ${
|
className={`absolute z-20 h-full w-full flex-grow flex-col md:relative md:flex md:w-96 ${
|
||||||
appState.mobileNavOpen ? 'flex' : 'hidden'
|
appState.mobileNavOpen ? 'flex' : 'hidden'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -33,6 +32,5 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const SidebarOverlay = ({
|
|||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{open && (
|
{open && (
|
||||||
<m.div
|
<m.div
|
||||||
className="absolute z-10 flex h-full w-full flex-col bg-gray-100 dark:bg-primaryDark"
|
className="absolute z-30 flex h-full w-full flex-col bg-gray-100 dark:bg-primaryDark"
|
||||||
animate={direction === 'x' ? { translateX: 0 } : { translateY: 0 }}
|
animate={direction === 'x' ? { translateX: 0 } : { translateY: 0 }}
|
||||||
initial={
|
initial={
|
||||||
direction === 'x' ? { translateX: '-100%' } : { translateY: '100%' }
|
direction === 'x' ? { translateX: '-100%' } : { translateY: '100%' }
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ export const Layout = ({
|
|||||||
children,
|
children,
|
||||||
}: LayoutProps): JSX.Element => {
|
}: LayoutProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full bg-gray-100 dark:bg-secondaryDark md:overflow-hidden md:shadow-xl">
|
<div className="relative flex w-full bg-gray-100 dark:bg-secondaryDark md:overflow-hidden md:shadow-xl">
|
||||||
|
<div className="flex flex-grow">
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
<div className="flex gap-2 border-b border-gray-300 p-2 dark:border-gray-600">
|
<div className="flex gap-2 border-b border-gray-300 p-2 dark:border-gray-600">
|
||||||
<IconButton icon={icon} />
|
<IconButton icon={icon} />
|
||||||
@@ -31,6 +32,7 @@ export const Layout = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">{sidebarContents}</div>
|
<div className="flex flex-col gap-2">{sidebarContents}</div>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
</div>
|
||||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||||
{children}
|
{children}
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ export const BottomNav = (): JSX.Element => {
|
|||||||
const appState = useAppSelector((state) => state.app);
|
const appState = useAppSelector((state) => state.app);
|
||||||
const primaryChannelSettings = useAppSelector(
|
const primaryChannelSettings = useAppSelector(
|
||||||
(state) => state.meshtastic.radio.channels,
|
(state) => state.meshtastic.radio.channels,
|
||||||
).find((channel) => channel.channel.role === Protobuf.Channel_Role.PRIMARY)
|
).find((channel) => channel.role === Protobuf.Channel_Role.PRIMARY)?.settings;
|
||||||
?.channel.settings;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="z-20 flex justify-between border-t border-gray-300 bg-white dark:border-gray-600 dark:bg-secondaryDark">
|
<div className="z-20 flex justify-between border-t border-gray-300 bg-white dark:border-gray-600 dark:bg-secondaryDark">
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ export const VersionInfo = ({
|
|||||||
// }`,
|
// }`,
|
||||||
// fetcher,
|
// fetcher,
|
||||||
// );
|
// );
|
||||||
// console.log(data);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { connType } from '@core/slices/appSlice';
|
import { connType } from '@core/slices/appSlice';
|
||||||
import {
|
import {
|
||||||
addChannel,
|
addChannel,
|
||||||
|
addChat,
|
||||||
addMessage,
|
addMessage,
|
||||||
addNode,
|
addNode,
|
||||||
addPosition,
|
addPosition,
|
||||||
@@ -86,7 +87,6 @@ const registerListeners = (): void => {
|
|||||||
SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE;
|
SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE;
|
||||||
|
|
||||||
connection.onMeshPacket.subscribe((packet) => {
|
connection.onMeshPacket.subscribe((packet) => {
|
||||||
console.log(packet);
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
addRoute({
|
addRoute({
|
||||||
from: packet.from,
|
from: packet.from,
|
||||||
@@ -127,6 +127,7 @@ const registerListeners = (): void => {
|
|||||||
connection.onNodeInfoPacket.subscribe(
|
connection.onNodeInfoPacket.subscribe(
|
||||||
(nodeInfoPacket): void | { payload: Protobuf.NodeInfo; type: string } => {
|
(nodeInfoPacket): void | { payload: Protobuf.NodeInfo; type: string } => {
|
||||||
store.dispatch(addNode(nodeInfoPacket.data));
|
store.dispatch(addNode(nodeInfoPacket.data));
|
||||||
|
store.dispatch(addChat(nodeInfoPacket.data.num));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -134,6 +135,9 @@ const registerListeners = (): void => {
|
|||||||
switch (adminPacket.data.variant.oneofKind) {
|
switch (adminPacket.data.variant.oneofKind) {
|
||||||
case 'getChannelResponse':
|
case 'getChannelResponse':
|
||||||
store.dispatch(addChannel(adminPacket.data.variant.getChannelResponse));
|
store.dispatch(addChannel(adminPacket.data.variant.getChannelResponse));
|
||||||
|
store.dispatch(
|
||||||
|
addChat(adminPacket.data.variant.getChannelResponse.index),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'getRadioResponse':
|
case 'getRadioResponse':
|
||||||
if (adminPacket.data.variant.getRadioResponse.preferences) {
|
if (adminPacket.data.variant.getRadioResponse.preferences) {
|
||||||
@@ -153,8 +157,6 @@ const registerListeners = (): void => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
connection.onRoutingPacket.subscribe((routingPacket) => {
|
connection.onRoutingPacket.subscribe((routingPacket) => {
|
||||||
console.log(routingPacket);
|
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
updateLastInteraction({
|
updateLastInteraction({
|
||||||
id: routingPacket.packet.from,
|
id: routingPacket.packet.from,
|
||||||
|
|||||||
@@ -9,13 +9,7 @@ export interface MessageWithAck {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Chat {
|
export interface Chat {
|
||||||
id: number; //Channel or user id (for dm's)
|
lastInterraction: Date;
|
||||||
messages: MessageWithAck[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChannelData {
|
|
||||||
channel: Protobuf.Channel;
|
|
||||||
lastChatInterraction: Date;
|
|
||||||
messages: MessageWithAck[];
|
messages: MessageWithAck[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +21,10 @@ interface CurrentPosition {
|
|||||||
satsInView: number;
|
satsInView: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChatEntries = {
|
||||||
|
[key in number]: Chat;
|
||||||
|
};
|
||||||
|
|
||||||
interface Route {
|
interface Route {
|
||||||
from: number;
|
from: number;
|
||||||
to: number;
|
to: number;
|
||||||
@@ -46,7 +44,7 @@ export interface Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Radio {
|
export interface Radio {
|
||||||
channels: ChannelData[];
|
channels: Protobuf.Channel[];
|
||||||
preferences: Protobuf.RadioConfig_UserPreferences;
|
preferences: Protobuf.RadioConfig_UserPreferences;
|
||||||
hardware: Protobuf.MyNodeInfo;
|
hardware: Protobuf.MyNodeInfo;
|
||||||
}
|
}
|
||||||
@@ -59,7 +57,7 @@ interface MeshtasticState {
|
|||||||
radio: Radio;
|
radio: Radio;
|
||||||
hostOverrideEnabled: boolean;
|
hostOverrideEnabled: boolean;
|
||||||
hostOverride: string;
|
hostOverride: string;
|
||||||
chats: Chat[];
|
chats: ChatEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: MeshtasticState = {
|
const initialState: MeshtasticState = {
|
||||||
@@ -77,7 +75,7 @@ const initialState: MeshtasticState = {
|
|||||||
hostOverrideEnabled:
|
hostOverrideEnabled:
|
||||||
localStorage.getItem('hostOverrideEnabled') === 'true' ?? false,
|
localStorage.getItem('hostOverrideEnabled') === 'true' ?? false,
|
||||||
hostOverride: localStorage.getItem('hostOverride') ?? '',
|
hostOverride: localStorage.getItem('hostOverride') ?? '',
|
||||||
chats: [],
|
chats: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const meshtasticSlice = createSlice({
|
export const meshtasticSlice = createSlice({
|
||||||
@@ -167,24 +165,16 @@ export const meshtasticSlice = createSlice({
|
|||||||
addChannel: (state, action: PayloadAction<Protobuf.Channel>) => {
|
addChannel: (state, action: PayloadAction<Protobuf.Channel>) => {
|
||||||
if (
|
if (
|
||||||
state.radio.channels.findIndex(
|
state.radio.channels.findIndex(
|
||||||
(channel) => channel.channel.index === action.payload.index,
|
(channel) => channel.index === action.payload.index,
|
||||||
) !== -1
|
) !== -1
|
||||||
) {
|
) {
|
||||||
state.radio.channels = state.radio.channels.map((channel) => {
|
state.radio.channels = state.radio.channels.map((channel) => {
|
||||||
return channel.channel.index === action.payload.index
|
return channel.index === action.payload.index
|
||||||
? {
|
? action.payload
|
||||||
channel: action.payload,
|
|
||||||
lastChatInterraction: new Date(),
|
|
||||||
messages: channel.messages,
|
|
||||||
}
|
|
||||||
: channel;
|
: channel;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
state.radio.channels.push({
|
state.radio.channels.push(action.payload);
|
||||||
channel: action.payload,
|
|
||||||
lastChatInterraction: new Date(),
|
|
||||||
messages: [],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addRoute: (state, action: PayloadAction<Route>) => {
|
addRoute: (state, action: PayloadAction<Route>) => {
|
||||||
@@ -195,27 +185,10 @@ export const meshtasticSlice = createSlice({
|
|||||||
(route) =>
|
(route) =>
|
||||||
route.from === action.payload.from && route.to === action.payload.to,
|
route.from === action.payload.from && route.to === action.payload.to,
|
||||||
);
|
);
|
||||||
console.log(exists);
|
|
||||||
|
|
||||||
if (exists === -1) {
|
if (exists === -1) {
|
||||||
node?.routes.push(action.payload);
|
node?.routes.push(action.payload);
|
||||||
}
|
}
|
||||||
// node?.routes.map((route) => {
|
|
||||||
// if (
|
|
||||||
|
|
||||||
// ) {
|
|
||||||
// node?.routes.push(action.payload);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if (node) {
|
|
||||||
// node.routes = node.routes.map((route) => {
|
|
||||||
// return route.from === action.payload.from &&
|
|
||||||
// route.to === action.payload.to
|
|
||||||
// ? action.payload
|
|
||||||
// : route;
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
},
|
},
|
||||||
setPreferences: (
|
setPreferences: (
|
||||||
state,
|
state,
|
||||||
@@ -224,21 +197,38 @@ export const meshtasticSlice = createSlice({
|
|||||||
state.radio.preferences = action.payload;
|
state.radio.preferences = action.payload;
|
||||||
},
|
},
|
||||||
addMessage: (state, action: PayloadAction<MessageWithAck>) => {
|
addMessage: (state, action: PayloadAction<MessageWithAck>) => {
|
||||||
const channelIndex = state.radio.channels.findIndex(
|
console.log(action.payload);
|
||||||
(channel) =>
|
|
||||||
channel.channel.index === action.payload.message.packet.channel,
|
console.log(
|
||||||
|
`${action.payload.message.packet.from} -> ${action.payload.message.packet.to}`,
|
||||||
);
|
);
|
||||||
state.radio.channels[channelIndex].messages.push(action.payload);
|
state.chats[action.payload.message.packet.channel].lastInterraction =
|
||||||
state.radio.channels[channelIndex].lastChatInterraction = new Date();
|
new Date();
|
||||||
|
|
||||||
|
if (action.payload.message.packet.to === 0xffffffff) {
|
||||||
|
console.log('boradcast');
|
||||||
|
|
||||||
|
state.chats[action.payload.message.packet.channel].messages.push(
|
||||||
|
action.payload,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log('dm');
|
||||||
|
|
||||||
|
const dmIndex =
|
||||||
|
action.payload.message.packet.from === state.radio.hardware.myNodeNum
|
||||||
|
? action.payload.message.packet.to
|
||||||
|
: action.payload.message.packet.from;
|
||||||
|
|
||||||
|
state.chats[dmIndex].messages.push(action.payload);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ackMessage: (
|
ackMessage: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ channel: number; messageId: number }>,
|
action: PayloadAction<{ chatIndex: number; messageId: number }>,
|
||||||
) => {
|
) => {
|
||||||
const channelIndex = state.radio.channels.findIndex(
|
console.log(action.payload);
|
||||||
(channel) => channel.channel.index === action.payload.channel,
|
|
||||||
);
|
state.chats[action.payload.chatIndex].messages.map((message) => {
|
||||||
state.radio.channels[channelIndex].messages.map((message) => {
|
|
||||||
if (message.message.packet.id === action.payload.messageId) {
|
if (message.message.packet.id === action.payload.messageId) {
|
||||||
message.ack = true;
|
message.ack = true;
|
||||||
}
|
}
|
||||||
@@ -269,10 +259,11 @@ export const meshtasticSlice = createSlice({
|
|||||||
// connection.disconnect();
|
// connection.disconnect();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addChat: (state, action: PayloadAction<Chat>) => {
|
addChat: (state, action: PayloadAction<number>) => {
|
||||||
if (state.chats.findIndex((chat) => chat.id === action.payload.id)) {
|
state.chats[action.payload] = {
|
||||||
state.chats.push(action.payload);
|
messages: [],
|
||||||
}
|
lastInterraction: new Date(),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
resetState: (state) => {
|
resetState: (state) => {
|
||||||
state.deviceStatus = Types.DeviceStatusEnum.DEVICE_DISCONNECTED;
|
state.deviceStatus = Types.DeviceStatusEnum.DEVICE_DISCONNECTED;
|
||||||
@@ -300,6 +291,7 @@ export const {
|
|||||||
updateLastInteraction,
|
updateLastInteraction,
|
||||||
setHostOverrideEnabled,
|
setHostOverrideEnabled,
|
||||||
setHostOverride,
|
setHostOverride,
|
||||||
|
addChat,
|
||||||
resetState,
|
resetState,
|
||||||
} = meshtasticSlice.actions;
|
} = meshtasticSlice.actions;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React from 'react';
|
|||||||
import { m } from 'framer-motion';
|
import { m } from 'framer-motion';
|
||||||
import JSONPretty from 'react-json-pretty';
|
import JSONPretty from 'react-json-pretty';
|
||||||
|
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector.js';
|
import { useAppSelector } from '@app/hooks/useAppSelector';
|
||||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||||
|
|
||||||
export const Info = (): JSX.Element => {
|
export const Info = (): JSX.Element => {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const MapContainer = (): JSX.Element => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-full w-full">
|
<div className="relative flex h-full w-full">
|
||||||
<div className="absolute right-0 z-20 m-4 space-y-2 rounded-md border border-gray-300 bg-white p-2 shadow-md dark:border-gray-600 dark:bg-primaryDark">
|
<div className="absolute right-0 z-10 m-4 space-y-2 rounded-md border border-gray-300 bg-white p-2 shadow-md dark:border-gray-600 dark:bg-primaryDark">
|
||||||
<IconButton
|
<IconButton
|
||||||
active={mapState.style === 'Satellite'}
|
active={mapState.style === 'Satellite'}
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { m } from 'framer-motion';
|
||||||
|
import { FiSettings } from 'react-icons/fi';
|
||||||
|
import { MdPublic } from 'react-icons/md';
|
||||||
|
import TimeAgo from 'timeago-react';
|
||||||
|
|
||||||
|
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
||||||
|
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||||
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
|
import { IconButton, Tooltip } from '@meshtastic/components';
|
||||||
|
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
|
export interface ChannelChatProps {
|
||||||
|
channel: Protobuf.Channel;
|
||||||
|
selectedIndex: number;
|
||||||
|
setSelectedIndex: (index: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChannelChat = ({
|
||||||
|
channel,
|
||||||
|
selectedIndex,
|
||||||
|
setSelectedIndex,
|
||||||
|
}: ChannelChatProps): JSX.Element => {
|
||||||
|
const myNodeNum = useAppSelector(
|
||||||
|
(state) => state.meshtastic.radio.hardware,
|
||||||
|
).myNodeNum;
|
||||||
|
const nodes = useAppSelector((state) => state.meshtastic.nodes).filter(
|
||||||
|
(node) => node.number !== myNodeNum,
|
||||||
|
);
|
||||||
|
const chats = useAppSelector((state) => state.meshtastic.chats);
|
||||||
|
const channels = useAppSelector(
|
||||||
|
(state) => state.meshtastic.radio.channels,
|
||||||
|
).filter((ch) => ch.role !== Protobuf.Channel_Role.DISABLED);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarItem
|
||||||
|
key={channel.index}
|
||||||
|
selected={channel.index === selectedIndex}
|
||||||
|
setSelected={(): void => {
|
||||||
|
setSelectedIndex(channel.index);
|
||||||
|
}}
|
||||||
|
actions={<IconButton icon={<FiSettings />} />}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
content={
|
||||||
|
channel.settings?.name.length
|
||||||
|
? channel.settings.name
|
||||||
|
: `CH: ${channel.index}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex h-8 w-8 rounded-full bg-gray-200 dark:bg-primaryDark dark:text-white">
|
||||||
|
<div className="m-auto">
|
||||||
|
{channel.role === Protobuf.Channel_Role.PRIMARY ? (
|
||||||
|
<MdPublic />
|
||||||
|
) : (
|
||||||
|
<p>
|
||||||
|
{channel.settings?.name.length
|
||||||
|
? channel.settings.name.substring(0, 3).toUpperCase()
|
||||||
|
: `CH: ${channel.index}`}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
{chats[channel.index]?.messages.length ? (
|
||||||
|
<>
|
||||||
|
<div className="mx-2 flex h-8">
|
||||||
|
{[
|
||||||
|
...new Set(
|
||||||
|
chats[channel.index]?.messages.flatMap(({ message }) => [
|
||||||
|
message.packet.from,
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.sort()
|
||||||
|
.map((nodeId) => {
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
key={nodeId}
|
||||||
|
content={
|
||||||
|
nodes.find((node) => node.number === nodeId)?.user
|
||||||
|
?.longName ?? 'UNK'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex h-full">
|
||||||
|
<m.div
|
||||||
|
whileHover={{ scale: 1.1 }}
|
||||||
|
className="my-auto -ml-2"
|
||||||
|
>
|
||||||
|
<Hashicon value={nodeId.toString()} size={20} />
|
||||||
|
</m.div>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="my-auto ml-auto text-xs font-semibold dark:text-gray-400">
|
||||||
|
{chats[channel.index].messages.length ? (
|
||||||
|
<TimeAgo datetime={chats[channel.index].lastInterraction} />
|
||||||
|
) : (
|
||||||
|
<div>No messages</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="my-auto dark:text-white">No messages</div>
|
||||||
|
)}
|
||||||
|
</SidebarItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { FiSettings } from 'react-icons/fi';
|
||||||
|
|
||||||
|
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
||||||
|
import type { Node } from '@core/slices/meshtasticSlice';
|
||||||
|
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||||
|
import { IconButton } from '@meshtastic/components';
|
||||||
|
|
||||||
|
export interface DmChatProps {
|
||||||
|
node: Node;
|
||||||
|
selectedIndex: number;
|
||||||
|
setSelectedIndex: (index: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DmChat = ({
|
||||||
|
node,
|
||||||
|
selectedIndex,
|
||||||
|
setSelectedIndex,
|
||||||
|
}: DmChatProps): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<SidebarItem
|
||||||
|
key={node.number}
|
||||||
|
selected={node.number === selectedIndex}
|
||||||
|
setSelected={(): void => {
|
||||||
|
setSelectedIndex(node.number);
|
||||||
|
}}
|
||||||
|
actions={<IconButton icon={<FiSettings />} />}
|
||||||
|
>
|
||||||
|
<div className="flex dark:text-white">
|
||||||
|
<div className="m-auto">
|
||||||
|
<Hashicon value={node.number.toString()} size={32} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="my-auto mr-auto font-semibold dark:text-white">
|
||||||
|
{node.user?.longName ?? 'Unknown'}
|
||||||
|
</div>
|
||||||
|
</SidebarItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
|
import { FiClock } from 'react-icons/fi';
|
||||||
|
|
||||||
import type { Node } from '@app/core/slices/meshtasticSlice';
|
import type { Node } from '@app/core/slices/meshtasticSlice';
|
||||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||||
import { Tooltip } from '@meshtastic/components';
|
import { Tooltip } from '@meshtastic/components';
|
||||||
@@ -23,9 +25,11 @@ export const Message = ({
|
|||||||
<div className="group mb-3 hover:bg-gray-200 dark:hover:bg-primaryDark">
|
<div className="group mb-3 hover:bg-gray-200 dark:hover:bg-primaryDark">
|
||||||
{lastMsgSameUser ? (
|
{lastMsgSameUser ? (
|
||||||
<div
|
<div
|
||||||
className={`mx-6 -mt-3 flex gap-2 ${lastMsgSameUser ? '' : 'py-1'}`}
|
className={`mx-6 -mt-3 flex justify-between ${
|
||||||
|
lastMsgSameUser ? '' : 'py-1'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex">
|
<div className="flex gap-2">
|
||||||
<Tooltip content={rxTime.toString()}>
|
<Tooltip content={rxTime.toString()}>
|
||||||
<div className="my-auto ml-auto w-8 pt-1 text-xs text-transparent dark:group-hover:text-gray-400">
|
<div className="my-auto ml-auto w-8 pt-1 text-xs text-transparent dark:group-hover:text-gray-400">
|
||||||
{rxTime
|
{rxTime
|
||||||
@@ -37,7 +41,6 @@ export const Message = ({
|
|||||||
.replace('PM', '')}
|
.replace('PM', '')}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className={`my-auto dark:text-gray-300 ${
|
className={`my-auto dark:text-gray-300 ${
|
||||||
ack ? '' : 'animate-pulse dark:text-gray-500'
|
ack ? '' : 'animate-pulse dark:text-gray-500'
|
||||||
@@ -46,6 +49,13 @@ export const Message = ({
|
|||||||
{message}
|
{message}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Tooltip content="Response time">
|
||||||
|
<div className="flex gap-1 pt-1 text-xs text-transparent dark:group-hover:text-gray-400">
|
||||||
|
<FiClock className="mt-0.5" />
|
||||||
|
<div>25s</div>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="mx-6 flex gap-2">
|
<div className="mx-6 flex gap-2">
|
||||||
<div className="my-auto w-8">
|
<div className="my-auto w-8">
|
||||||
|
|||||||
@@ -7,23 +7,38 @@ import { useAppSelector } from '@hooks/useAppSelector';
|
|||||||
import { Input } from '@meshtastic/components';
|
import { Input } from '@meshtastic/components';
|
||||||
|
|
||||||
export interface MessageBarProps {
|
export interface MessageBarProps {
|
||||||
channelIndex: number;
|
chatIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MessageBar = ({ channelIndex }: MessageBarProps): JSX.Element => {
|
export const MessageBar = ({ chatIndex }: MessageBarProps): JSX.Element => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const ready = useAppSelector((state) => state.meshtastic.ready);
|
const meshtasticState = useAppSelector((state) => state.meshtastic);
|
||||||
|
|
||||||
|
const [isChannel, setIsChannel] = React.useState(false);
|
||||||
|
|
||||||
|
React.useState(() => {
|
||||||
|
setIsChannel(
|
||||||
|
meshtasticState.radio.channels.findIndex(
|
||||||
|
(channel) => channel.index === chatIndex,
|
||||||
|
) !== -1,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const [currentMessage, setCurrentMessage] = React.useState('');
|
const [currentMessage, setCurrentMessage] = React.useState('');
|
||||||
const sendMessage = (): void => {
|
const sendMessage = (): void => {
|
||||||
if (ready) {
|
if (meshtasticState.ready) {
|
||||||
void connection.sendText(
|
void connection.sendText(
|
||||||
currentMessage,
|
currentMessage,
|
||||||
undefined,
|
isChannel ? undefined : chatIndex,
|
||||||
true,
|
true,
|
||||||
channelIndex--,
|
isChannel ? chatIndex-- : 0,
|
||||||
(id) => {
|
(id) => {
|
||||||
dispatch(ackMessage({ channel: channelIndex--, messageId: id }));
|
dispatch(
|
||||||
|
ackMessage({
|
||||||
|
chatIndex: isChannel ? chatIndex-- : chatIndex,
|
||||||
|
messageId: id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
@@ -45,7 +60,7 @@ export const MessageBar = ({ channelIndex }: MessageBarProps): JSX.Element => {
|
|||||||
type="text"
|
type="text"
|
||||||
minLength={2}
|
minLength={2}
|
||||||
placeholder="Enter Message"
|
placeholder="Enter Message"
|
||||||
disabled={!ready}
|
disabled={!meshtasticState.ready}
|
||||||
value={currentMessage}
|
value={currentMessage}
|
||||||
onChange={(e): void => {
|
onChange={(e): void => {
|
||||||
setCurrentMessage(e.target.value);
|
setCurrentMessage(e.target.value);
|
||||||
|
|||||||
+47
-106
@@ -1,27 +1,30 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { m } from 'framer-motion';
|
import { FiHash, FiMessageCircle } from 'react-icons/fi';
|
||||||
import { FiHash, FiMessageCircle, FiSettings } from 'react-icons/fi';
|
|
||||||
import { MdPublic } from 'react-icons/md';
|
|
||||||
import TimeAgo from 'timeago-react';
|
|
||||||
|
|
||||||
import { Layout } from '@app/components/layout';
|
import { Layout } from '@app/components/layout';
|
||||||
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
|
||||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { IconButton, Tooltip } from '@meshtastic/components';
|
import { IconButton } from '@meshtastic/components';
|
||||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
|
import { ChannelChat } from './ChannelChat';
|
||||||
|
import { DmChat } from './DmChat';
|
||||||
import { Message } from './Message';
|
import { Message } from './Message';
|
||||||
import { MessageBar } from './MessageBar';
|
import { MessageBar } from './MessageBar';
|
||||||
|
|
||||||
export const Messages = (): JSX.Element => {
|
export const Messages = (): JSX.Element => {
|
||||||
|
const [selectedChatIndex, setSelectedChatIndex] = React.useState<number>(0);
|
||||||
|
|
||||||
|
const chatRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const myNodeNum = useAppSelector(
|
||||||
|
(state) => state.meshtastic.radio.hardware,
|
||||||
|
).myNodeNum;
|
||||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||||
|
const chats = useAppSelector((state) => state.meshtastic.chats);
|
||||||
const channels = useAppSelector(
|
const channels = useAppSelector(
|
||||||
(state) => state.meshtastic.radio.channels,
|
(state) => state.meshtastic.radio.channels,
|
||||||
).filter((ch) => ch.channel.role !== Protobuf.Channel_Role.DISABLED);
|
).filter((ch) => ch.role !== Protobuf.Channel_Role.DISABLED);
|
||||||
const [channelIndex, setChannelIndex] = React.useState(0);
|
|
||||||
const chatRef = React.useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (chatRef.current) {
|
if (chatRef.current) {
|
||||||
@@ -35,91 +38,28 @@ export const Messages = (): JSX.Element => {
|
|||||||
icon={<FiMessageCircle />}
|
icon={<FiMessageCircle />}
|
||||||
sidebarContents={
|
sidebarContents={
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{nodes.map((node) => (
|
{nodes
|
||||||
<SidebarItem
|
.filter((node) => node.number !== myNodeNum)
|
||||||
|
.map((node) => (
|
||||||
|
<DmChat
|
||||||
key={node.number}
|
key={node.number}
|
||||||
selected={false}
|
node={node}
|
||||||
setSelected={(): void => {
|
selectedIndex={selectedChatIndex}
|
||||||
void Promise.resolve();
|
setSelectedIndex={setSelectedChatIndex}
|
||||||
}}
|
|
||||||
actions={<IconButton icon={<FiSettings />} />}
|
|
||||||
>
|
|
||||||
<div className="flex dark:text-white">
|
|
||||||
<div className="m-auto">
|
|
||||||
<Hashicon value={node.number.toString()} size={32} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="my-auto mr-auto font-semibold dark:text-white">
|
|
||||||
{node.user?.longName ?? 'Unknown'}
|
|
||||||
</div>
|
|
||||||
</SidebarItem>
|
|
||||||
))}
|
|
||||||
<div className="mx-2 rounded-md border-2 border-gray-300 dark:border-gray-600" />
|
|
||||||
{channels.map((channel) => (
|
|
||||||
<SidebarItem
|
|
||||||
key={channel.channel.index}
|
|
||||||
selected={channelIndex === channel.channel.index}
|
|
||||||
setSelected={(): void => {
|
|
||||||
setChannelIndex(channel.channel.index);
|
|
||||||
}}
|
|
||||||
actions={<IconButton icon={<FiSettings />} />}
|
|
||||||
>
|
|
||||||
<div className="flex h-8 w-8 rounded-full bg-gray-200 dark:bg-primaryDark dark:text-white">
|
|
||||||
<div className="m-auto">
|
|
||||||
{channel.channel.role === Protobuf.Channel_Role.PRIMARY ? (
|
|
||||||
<MdPublic />
|
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
{channel.channel.settings?.name.length
|
|
||||||
? channel.channel.settings.name
|
|
||||||
.substring(0, 3)
|
|
||||||
.toUpperCase()
|
|
||||||
: `CH: ${channel.channel.index}`}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{channel.messages.length ? (
|
|
||||||
<>
|
|
||||||
<div className="mx-2 flex h-8">
|
|
||||||
{[
|
|
||||||
...new Set(
|
|
||||||
channel.messages.flatMap(({ message }) => [
|
|
||||||
message.packet.from,
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
.sort()
|
|
||||||
.map((nodeId) => {
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
key={nodeId}
|
|
||||||
content={
|
|
||||||
nodes.find((node) => node.number === nodeId)?.user
|
|
||||||
?.longName ?? 'UNK'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="flex h-full">
|
|
||||||
<m.div
|
|
||||||
whileHover={{ scale: 1.1 }}
|
|
||||||
className="my-auto -ml-2"
|
|
||||||
>
|
|
||||||
<Hashicon value={nodeId.toString()} size={20} />
|
|
||||||
</m.div>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<TimeAgo
|
|
||||||
className="my-auto ml-auto text-xs font-semibold dark:text-gray-400"
|
|
||||||
datetime={channel.lastChatInterraction}
|
|
||||||
/>
|
/>
|
||||||
</>
|
))}
|
||||||
) : (
|
{nodes.length !== 0 && channels.length !== 0 && (
|
||||||
<div className="my-auto dark:text-white">No messages</div>
|
<div className="mx-2 rounded-md border-2 border-gray-300 dark:border-gray-600" />
|
||||||
)}
|
)}
|
||||||
</SidebarItem>
|
{channels
|
||||||
|
.filter((channel) => channel.settings?.name !== 'admin')
|
||||||
|
.map((channel) => (
|
||||||
|
<ChannelChat
|
||||||
|
key={channel.index}
|
||||||
|
channel={channel}
|
||||||
|
selectedIndex={selectedChatIndex}
|
||||||
|
setSelectedIndex={setSelectedChatIndex}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -128,14 +68,13 @@ export const Messages = (): JSX.Element => {
|
|||||||
<div className="flex w-full justify-between border-b border-gray-300 px-2 dark:border-gray-600 dark:text-gray-300">
|
<div className="flex w-full justify-between border-b border-gray-300 px-2 dark:border-gray-600 dark:text-gray-300">
|
||||||
<div className="my-auto flex gap-2 py-2 text-sm">
|
<div className="my-auto flex gap-2 py-2 text-sm">
|
||||||
<IconButton icon={<FiHash className="h-4 w-4" />} />
|
<IconButton icon={<FiHash className="h-4 w-4" />} />
|
||||||
<div className="my-auto">
|
{/* <div className="my-auto">
|
||||||
{channels[channelIndex]?.channel.settings?.name.length
|
{channels[channelIndex]?.settings?.name.length
|
||||||
? channels[channelIndex]?.channel.settings?.name
|
? channels[channelIndex]?.settings?.name
|
||||||
: channels[channelIndex]?.channel.role ===
|
: channels[channelIndex]?.role === Protobuf.Channel_Role.PRIMARY
|
||||||
Protobuf.Channel_Role.PRIMARY
|
|
||||||
? 'Primary'
|
? 'Primary'
|
||||||
: `Channel: ${channels[channelIndex]?.channel.index}`}
|
: `Channel: ${channels[channelIndex]?.index}`}
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -143,7 +82,7 @@ export const Messages = (): JSX.Element => {
|
|||||||
className="flex flex-grow flex-col space-y-2 overflow-y-auto border-b border-gray-300 bg-white pb-6 dark:border-gray-600 dark:bg-secondaryDark"
|
className="flex flex-grow flex-col space-y-2 overflow-y-auto border-b border-gray-300 bg-white pb-6 dark:border-gray-600 dark:bg-secondaryDark"
|
||||||
>
|
>
|
||||||
<div className="mt-auto">
|
<div className="mt-auto">
|
||||||
{channels[channelIndex]?.messages.map((message, index) => (
|
{chats[selectedChatIndex]?.messages.map((message, index) => (
|
||||||
<Message
|
<Message
|
||||||
key={index}
|
key={index}
|
||||||
message={message.message.data}
|
message={message.message.data}
|
||||||
@@ -152,17 +91,19 @@ export const Messages = (): JSX.Element => {
|
|||||||
lastMsgSameUser={
|
lastMsgSameUser={
|
||||||
index === 0
|
index === 0
|
||||||
? false
|
? false
|
||||||
: channels[channelIndex]?.messages[index - 1].message.packet
|
: chats[selectedChatIndex].messages[index - 1].message
|
||||||
.from === message.message.packet.from
|
.packet.from === message.message.packet.from
|
||||||
}
|
}
|
||||||
sender={nodes.find(
|
sender={nodes.find((node) => {
|
||||||
(node) => node.number === message.message.packet.from,
|
console.log(message);
|
||||||
)}
|
|
||||||
|
return node.number === message.message.packet.from;
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MessageBar channelIndex={channelIndex} />
|
<MessageBar chatIndex={selectedChatIndex} />
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export const NodeCard = ({
|
|||||||
);
|
);
|
||||||
}, [node.currentPosition]);
|
}, [node.currentPosition]);
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
selected={selected}
|
selected={selected}
|
||||||
setSelected={setSelected}
|
setSelected={setSelected}
|
||||||
@@ -105,6 +106,7 @@ export const NodeCard = ({
|
|||||||
})
|
})
|
||||||
: 'Never'}
|
: 'Never'}
|
||||||
</div>
|
</div>
|
||||||
|
</SidebarItem>
|
||||||
<SidebarOverlay
|
<SidebarOverlay
|
||||||
title={`Node ${node.user?.longName ?? 'UNK'} `}
|
title={`Node ${node.user?.longName ?? 'UNK'} `}
|
||||||
open={infoOpen}
|
open={infoOpen}
|
||||||
@@ -134,6 +136,6 @@ export const NodeCard = ({
|
|||||||
</>
|
</>
|
||||||
</CollapsibleSection>
|
</CollapsibleSection>
|
||||||
</SidebarOverlay>
|
</SidebarOverlay>
|
||||||
</SidebarItem>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user