Browse Source

more state changes

pull/101/head
Sacha Weatherstone 3 years ago
parent
commit
0894fd76f5
No known key found for this signature in database GPG Key ID: 7AB2D7E206124B31
  1. 1
      src/components/Dialog/DialogManager.tsx
  2. 1
      src/components/Dialog/ImportDialog.tsx
  3. 13
      src/components/Dialog/QRDialog.tsx
  4. 18
      src/components/Form/DynamicForm.tsx
  5. 187
      src/components/PageComponents/Channel.tsx
  6. 16
      src/core/stores/deviceStore.ts
  7. 11
      src/pages/Channels.tsx
  8. 49
      src/pages/Messages.tsx
  9. 19
      src/validation/channel.ts
  10. 10
      src/validation/config/network.ts

1
src/components/Dialog/DialogManager.tsx

@ -22,7 +22,6 @@ export const DialogManager = (): JSX.Element => {
onOpenChange={(open) => {
setDialogOpen("import", open);
}}
channels={channels}
loraConfig={config.lora}
/>
<ShutdownDialog

1
src/components/Dialog/ImportDialog.tsx

@ -20,7 +20,6 @@ export interface ImportDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
loraConfig?: Protobuf.Config_LoRaConfig;
channels: Protobuf.Channel[];
}
export const ImportDialog = ({

13
src/components/Dialog/QRDialog.tsx

@ -12,14 +12,14 @@ import {
DialogTitle
} from "@components/UI/Dialog.js";
import { ClipboardIcon } from "lucide-react";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { Protobuf, Types } from "@meshtastic/meshtasticjs";
import { Label } from "@components/UI/Label.js";
export interface QRDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
loraConfig?: Protobuf.Config_LoRaConfig;
channels: Protobuf.Channel[];
channels: Map<Types.ChannelNumber, Protobuf.Channel>;
}
export const QRDialog = ({
@ -31,9 +31,12 @@ export const QRDialog = ({
const [selectedChannels, setSelectedChannels] = useState<number[]>([0]);
const [QRCodeURL, setQRCodeURL] = useState<string>("");
const filteredChannels = Array.from(channels.values()).filter((channel) =>
selectedChannels.includes(channel.index)
);
useEffect(() => {
const channelsToEncode = channels
.filter((channel) => selectedChannels.includes(channel.index))
const channelsToEncode = filteredChannels
.map((channel) => channel.settings)
.filter((ch): ch is Protobuf.ChannelSettings => !!ch);
const encoded = new Protobuf.ChannelSet(
@ -62,7 +65,7 @@ export const QRDialog = ({
<div className="grid gap-4 py-4">
<div className="flex gap-3 px-4 py-5 sm:p-6">
<div className="flex w-40 flex-col gap-1">
{channels.map((channel) => (
{filteredChannels.map((channel) => (
<div key={channel.index}>
<Label>
{channel.settings?.name.length

18
src/components/Form/DynamicForm.tsx

@ -10,6 +10,7 @@ import { H4 } from "@components/UI/Typography/H4.js";
import { Subtle } from "@components/UI/Typography/Subtle.js";
import { DynamicFormField, FieldProps } from "./DynamicFormField.js";
import { FieldWrapper } from "./FormWrapper.js";
import { Button } from "../UI/Button.js";
interface DisabledBy<T> {
fieldName: Path<T>;
@ -33,6 +34,8 @@ export interface GenericFormElementProps<T extends FieldValues, Y> {
export interface DynamicFormProps<T extends FieldValues> {
onSubmit: SubmitHandler<T>;
submitType?: "onChange" | "onSubmit";
hasSubmitButton?: boolean;
defaultValues?: DeepPartial<T>;
fieldGroups: {
label: string;
@ -42,12 +45,14 @@ export interface DynamicFormProps<T extends FieldValues> {
}
export function DynamicForm<T extends FieldValues>({
fieldGroups,
onSubmit,
defaultValues
submitType = "onChange",
hasSubmitButton,
defaultValues,
fieldGroups
}: DynamicFormProps<T>) {
const { handleSubmit, control, getValues } = useForm<T>({
mode: "onChange",
mode: submitType,
defaultValues: defaultValues
});
@ -68,7 +73,11 @@ export function DynamicForm<T extends FieldValues>({
return (
<form
className="space-y-8 divide-y divide-gray-200"
onChange={handleSubmit(onSubmit)}
{...(submitType === "onSubmit"
? { onSubmit: handleSubmit(onSubmit) }
: {
onChange: handleSubmit(onSubmit)
})}
>
{fieldGroups.map((fieldGroup, index) => (
<div
@ -92,6 +101,7 @@ export function DynamicForm<T extends FieldValues>({
))}
</div>
))}
{hasSubmitButton && <Button type="submit">Submit</Button>}
</form>
);
}

187
src/components/PageComponents/Channel.tsx

@ -1,7 +1,5 @@
import { useEffect, useState } from "react";
import { fromByteArray, toByteArray } from "base64-js";
import type { ChannelSettingsValidation } from "@app/validation/channelSettings.js";
import { useDevice } from "@core/stores/deviceStore.js";
import type { ChannelValidation } from "@app/validation/channel.js";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { DynamicForm } from "../Form/DynamicForm.js";
@ -10,10 +8,6 @@ export interface SettingsPanelProps {
}
export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
const { connection, addChannel } = useDevice();
const [keySize, setKeySize] = useState<128 | 256>(256);
const [pskHidden, setPskHidden] = useState(true);
// const {
// register,
// handleSubmit,
@ -81,116 +75,23 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
// );
// });
const onSubmit = (data: ChannelSettingsValidation) => {
const onSubmit = (data: ChannelValidation) => {
console.log(data);
};
return (
// <div className="p-3">
// <form onSubmit={onSubmit}>
// {channel?.index !== 0 && (
// <>
// <Controller
// name="enabled"
// control={control}
// render={({ field: { value, ...rest } }) => (
// <>
// <Label>Enabled</Label>
// <Switch
// // label="Enabled"
// // description="Description"
// checked={value}
// {...rest}
// />
// </>
// )}
// />
// <Label>Name</Label>
// <Input
// // description="Max transmit power in dBm"
// // error={errors.name?.message}
// {...register("name")}
// />
// </>
// )}
// {/* <Select
// label="Key Size"
// description="Desired size of generated key."
// value={keySize}
// onChange={(e): void => {
// setKeySize(parseInt(e.target.value) as 128 | 256);
// }}
// action={{
// icon: <RefreshCwIcon size={16} />,
// action: () => {
// const key = new Uint8Array(keySize / 8);
// crypto.getRandomValues(key);
// setValue("psk", fromByteArray(key), {
// shouldDirty: true
// });
// }
// }}
// >
// <option value={128}>128 Bit</option>
// <option value={256}>256 Bit</option>
// </Select> */}
// <Label>Pre-Shared Key</Label>
// <Input
// // width="100%"
// // label="Pre-Shared Key"
// // description="Channel key to encrypt data"
// type={pskHidden ? "password" : "text"}
// action={{
// icon: pskHidden ? EyeIcon : EyeOffIcon,
// onClick: () => {
// setPskHidden(!pskHidden);
// }
// }}
// // error={errors.psk?.message}
// {...register("psk")}
// />
// <Controller
// name="uplinkEnabled"
// control={control}
// render={({ field: { value, ...rest } }) => (
// <>
// <Label>Uplink Enabled</Label>
// <Switch
// // label="Uplink Enabled"
// // description="Send packets to designated MQTT server"
// checked={value}
// {...rest}
// />
// </>
// )}
// />
// <Controller
// name="downlinkEnabled"
// control={control}
// render={({ field: { value, ...rest } }) => (
// <>
// <Label>Downlink Enabled</Label>
// <Switch
// // label="Downlink Enabled"
// // description="Recieve packets to designated MQTT server"
// checked={value}
// {...rest}
// />
// </>
// )}
// />
// </form>
<DynamicForm<ChannelSettingsValidation>
<DynamicForm<ChannelValidation>
onSubmit={onSubmit}
submitType="onSubmit"
hasSubmitButton={true}
defaultValues={{
enabled: [
Protobuf.Channel_Role.SECONDARY,
Protobuf.Channel_Role.PRIMARY
].find((role) => role === channel?.role)
? true
: false,
...channel?.settings,
psk: fromByteArray(channel?.settings?.psk ?? new Uint8Array(0))
...channel,
...{
settings: {
...channel?.settings,
psk: fromByteArray(channel?.settings?.psk ?? new Uint8Array(0))
}
}
}}
fieldGroups={[
{
@ -198,80 +99,52 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
description: "Settings for the Bluetooth module",
fields: [
{
type: "toggle",
name: "enabled",
label: "Enabled",
description: "Description"
type: "select",
name: "role",
label: "Role",
description: "Description",
properties: {
enumValue: Protobuf.Channel_Role
}
},
{
type: "password",
name: "psk",
name: "settings.psk",
label: "pre-Shared Key",
description: "Description",
disabledBy: [
{
fieldName: "enabled"
}
],
properties: {
// act
}
},
{
type: "number",
name: "channelNum",
name: "settings.channelNum",
label: "Channel Number",
description: "Description",
disabledBy: [
{
fieldName: "enabled"
}
]
description: "Description"
},
{
type: "text",
name: "name",
name: "settings.name",
label: "Name",
description: "Description",
disabledBy: [
{
fieldName: "enabled"
}
]
description: "Description"
},
{
type: "number",
name: "id",
name: "settings.id",
label: "ID",
description: "Description",
disabledBy: [
{
fieldName: "enabled"
}
]
description: "Description"
},
{
type: "toggle",
name: "uplinkEnabled",
name: "settings.uplinkEnabled",
label: "Uplink Enabled",
description: "Description",
disabledBy: [
{
fieldName: "enabled"
}
]
description: "Description"
},
{
type: "toggle",
name: "downlinkEnabled",
name: "settings.downlinkEnabled",
label: "Downlink Enabled",
description: "Description",
disabledBy: [
{
fieldName: "enabled"
}
]
description: "Description"
}
]
}

16
src/core/stores/deviceStore.ts

@ -30,7 +30,7 @@ export type DialogVariant =
export interface Device {
id: number;
status: Types.DeviceStatusEnum;
channels: Protobuf.Channel[];
channels: Map<Types.ChannelNumber, Protobuf.Channel>;
config: Protobuf.LocalConfig;
moduleConfig: Protobuf.LocalModuleConfig;
workingConfig: Protobuf.Config[];
@ -108,7 +108,7 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
draft.devices.set(id, {
id,
status: Types.DeviceStatusEnum.DEVICE_DISCONNECTED,
channels: [],
channels: new Map(),
config: new Protobuf.LocalConfig(),
moduleConfig: new Protobuf.LocalModuleConfig(),
workingConfig: [],
@ -336,16 +336,10 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
set(
produce<DeviceState>((draft) => {
const device = draft.devices.get(id);
if (device) {
const channelIndex = device.channels.findIndex(
(c) => c.index === channel.index
);
if (channelIndex !== -1) {
device.channels[channelIndex] = channel;
} else {
device.channels.push(channel);
}
if (!device) {
return;
}
device.channels.set(channel.index, channel);
})
);
},

11
src/pages/Channels.tsx

@ -26,14 +26,15 @@ export const ChannelsPage = (): JSX.Element => {
Types.ChannelNumber.PRIMARY
);
const currentChannel = channels.get(activeChannel);
const allChannels = Array.from(channels.values());
return (
<>
<Sidebar></Sidebar>
<PageLayout
label={`Channel: ${
channels[activeChannel]
? getChannelName(channels[activeChannel])
: "Loading..."
currentChannel ? getChannelName(currentChannel) : "Loading..."
}`}
actions={[
{
@ -54,13 +55,13 @@ export const ChannelsPage = (): JSX.Element => {
>
<Tabs defaultValue="0">
<TabsList>
{channels.map((channel) => (
{allChannels.map((channel) => (
<TabsTrigger key={channel.index} value={channel.index.toString()}>
{getChannelName(channel)}
</TabsTrigger>
))}
</TabsList>
{channels.map((channel) => (
{allChannels.map((channel) => (
<TabsContent key={channel.index} value={channel.index.toString()}>
<Channel key={channel.index} channel={channel} />
</TabsContent>

49
src/pages/Messages.tsx

@ -20,31 +20,34 @@ export const MessagesPage = (): JSX.Element => {
const filteredNodes = Array.from(nodes.values()).filter(
(n) => n.num !== hardware.myNodeNum
);
const allChannels = Array.from(channels.values());
const filteredChannels = allChannels.filter(
(ch) => ch.role !== Protobuf.Channel_Role.DISABLED
);
const currentChannel = channels.get(activeChat);
return (
<>
<Sidebar>
<SidebarSection label="Channels">
{channels
.filter((ch) => ch.role !== Protobuf.Channel_Role.DISABLED)
.map((channel) => (
<SidebarButton
key={channel.index}
label={
channel.settings?.name.length
? channel.settings?.name
: channel.index === 0
? "Primary"
: `Ch ${channel.index}`
}
active={activeChat === channel.index}
onClick={() => {
setChatType("broadcast");
setActiveChat(channel.index);
}}
element={<HashIcon size={16} className="mr-2" />}
/>
))}
{filteredChannels.map((channel) => (
<SidebarButton
key={channel.index}
label={
channel.settings?.name.length
? channel.settings?.name
: channel.index === 0
? "Primary"
: `Ch ${channel.index}`
}
active={activeChat === channel.index}
onClick={() => {
setChatType("broadcast");
setActiveChat(channel.index);
}}
element={<HashIcon size={16} className="mr-2" />}
/>
))}
</SidebarSection>
<SidebarSection label="Peers">
{filteredNodes.map((node) => (
@ -63,14 +66,14 @@ export const MessagesPage = (): JSX.Element => {
</Sidebar>
<PageLayout
label={`Messages: ${
chatType === "broadcast" && channels[activeChat]
? getChannelName(channels[activeChat])
chatType === "broadcast" && currentChannel
? getChannelName(currentChannel)
: chatType === "direct" && nodes.get(activeChat)
? nodes.get(activeChat)?.user?.longName ?? "Unknown"
: "Loading..."
}`}
>
{channels.map(
{allChannels.map(
(channel) =>
activeChat === channel.index && (
<ChannelChat

19
src/validation/channelSettings.ts → src/validation/channel.ts

@ -7,15 +7,24 @@ import {
Length
} from "class-validator";
import type { Protobuf } from "@meshtastic/meshtasticjs";
import { Protobuf } from "@meshtastic/meshtasticjs";
export class ChannelSettingsValidation
export class ChannelValidation
implements Omit<Protobuf.Channel, keyof Protobuf.native.Message | "settings">
{
@IsNumber()
index: number;
settings: Channel_SettingsValidation;
@IsEnum(Protobuf.Channel_Role)
role: Protobuf.Channel_Role;
}
export class Channel_SettingsValidation
implements
Omit<Protobuf.ChannelSettings, keyof Protobuf.native.Message | "psk">
{
@IsBoolean()
enabled: boolean;
@IsNumber()
channelNum: number;

10
src/validation/config/network.ts

@ -1,4 +1,11 @@
import { IsBoolean, IsEnum, IsIP, IsOptional, Length } from "class-validator";
import {
IsBoolean,
IsEnum,
IsIP,
IsOptional,
IsString,
Length
} from "class-validator";
import { Protobuf } from "@meshtastic/meshtasticjs";
@ -31,6 +38,7 @@ export class NetworkValidation
ipv4Config: NetworkValidation_IpV4Config;
@IsString()
rsyslogServer: string;
}

Loading…
Cancel
Save